Skip to content

Commit 3a885ab

Browse files
tjgalvintgalvinTim Galvin
authored
Expose the output_type option when streaming, and allow both stdout/stderr to be captured (#209)
* allow both output stdout stderr to be emitted. Expose option to allow capture * updated changelog, version bump, error message * added a basic stream test * linting and pin black to 23.3.0 Signed-off-by: vsoch <[email protected]> Co-authored-by: tgalvin <[email protected]> Co-authored-by: Tim Galvin <[email protected]>
1 parent a220599 commit 3a885ab

27 files changed

+34
-55
lines changed

.github/dev-requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
pre-commit
2-
black
2+
black==23.3.0
33
isort
44
flake8

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The client here will eventually be released as "spython" (and eventually to
1717
singularity on pypi), and the versions here will coincide with these releases.
1818

1919
## [master](https://github.com/singularityhub/singularity-cli/tree/master)
20+
- exposed the stream type option, and ability to capture both stdout and stderr when stream=True (0.3.1)
2021
- dropping support for Singularity 2.x (0.3.0)
2122
- add comment out of STOPSIGNAL (0.2.14)
2223
- sudo `-E` flag should not be provided by default (0.2.13)

setup.py

-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ def get_requirements(lookup=None):
7575

7676

7777
if __name__ == "__main__":
78-
7978
INSTALL_REQUIRES = get_requirements(lookup)
8079
TESTS_REQUIRES = get_requirements(lookup)
8180

spython/client/__init__.py

-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414

1515
def get_parser():
16-
1716
parser = argparse.ArgumentParser(
1817
description="Singularity Client",
1918
formatter_class=argparse.RawTextHelpFormatter,
@@ -147,7 +146,6 @@ def version():
147146

148147

149148
def main():
150-
151149
parser = get_parser()
152150

153151
def print_help(return_code=0):

spython/client/recipe.py

-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ def main(args, options, parser):
7474
force = True
7575

7676
if args.json:
77-
7877
if outfile is not None:
7978
if not os.path.exists(outfile):
8079
if force:
@@ -85,7 +84,6 @@ def main(args, options, parser):
8584
print(json.dumps(recipeParser.recipe.json(), indent=4))
8685

8786
else:
88-
8987
# Do the conversion
9088
recipeWriter = writer(recipeParser.recipe)
9189
result = recipeWriter.convert(runscript=entrypoint, force=force)

spython/instance/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ def _update_metadata(self, kwargs=None):
9393

9494
# Add acceptable arguments
9595
for arg in ["pid", "name", "ip_address", "log_err_path", "log_out_path", "img"]:
96-
9796
# Skip over non-iterables:
9897
if arg in kwargs:
9998
setattr(self, arg, kwargs[arg])

spython/instance/cmd/start.py

-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def start(
4949

5050
# If an image isn't provided, we have an initialized instance
5151
if image is None:
52-
5352
# Not having this means it was called as a command, without an image
5453
if not hasattr(self, "_image"):
5554
bot.exit("Please provide an image, or create an Instance first.")

spython/main/base/command.py

-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ def generate_bind_list(self, bindlist=None):
6666
bindlist = bindlist.split(" ")
6767

6868
for bind in bindlist:
69-
7069
# Still cannot be None
7170
if bind:
7271
bot.debug("Adding bind %s" % bind)
@@ -113,7 +112,6 @@ def run_command(
113112
environ=None,
114113
background=False,
115114
):
116-
117115
"""
118116
Run_command is a wrapper for the global run_command, checking first
119117
for sudo and exiting on error if needed. The message is returned as

spython/main/base/generate.py

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212

1313
class RobotNamer:
14-
1514
_descriptors = [
1615
"chunky",
1716
"buttery",

spython/main/build.py

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ def build(
3131
sudo_options=None,
3232
singularity_options=None,
3333
):
34-
3534
"""build a singularity image, optionally for an isolated build
3635
(requires sudo). If you specify to stream, expect the image name
3736
and an iterator to be returned.

spython/main/execute.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def execute(
2929
sudo_options=None,
3030
quiet=True,
3131
environ=None,
32+
stream_type="stdout",
3233
):
3334
"""execute: send a command to a container
3435
@@ -51,6 +52,7 @@ def execute(
5152
and message result not (default)
5253
quiet: Do not print verbose output.
5354
environ: extra environment to add.
55+
stream_type: Sets which output stream from the singularity command should be return. Values are 'stdout', 'stderr', 'both'.
5456
"""
5557
from spython.utils import check_install
5658

@@ -68,7 +70,6 @@ def execute(
6870
image = None
6971

7072
if command is not None:
71-
7273
# No image provided, default to use the client's loaded image
7374
if image is None:
7475
image = self._get_uri()
@@ -115,7 +116,9 @@ def execute(
115116
quiet=quiet,
116117
environ=environ,
117118
)
118-
return stream_command(cmd, sudo=sudo, sudo_options=sudo_options)
119+
return stream_command(
120+
cmd, sudo=sudo, sudo_options=sudo_options, output_type=stream_type
121+
)
119122

120123
bot.exit("Please include a command (list) to execute.")
121124

spython/main/export.py

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def export(
1919
sudo=False,
2020
singularity_options=None,
2121
):
22-
2322
"""export will export an image, sudo must be used. Since we have Singularity
2423
versions after 3, export is replaced with building into a sandbox.
2524

spython/main/instances.py

-2
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,13 @@ def list_instances(
6363
# Success, we have instances
6464

6565
if output["return_code"] == 0:
66-
6766
instances = json.loads(output["message"][0]).get("instances", {})
6867

6968
# Does the user want instance objects instead?
7069
listing = []
7170

7271
if not return_json:
7372
for i in instances:
74-
7573
# If the user has provided a name, only add instance matches
7674
if name is not None:
7775
if name != i["instance"]:

spython/main/parse/parsers/base.py

-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ def __init__(self, filename, load=True):
4848
self.recipe = {"spython-base": Recipe(self.filename)}
4949

5050
if self.filename:
51-
5251
# Read in the raw lines of the file
5352
self.lines = read_file(self.filename)
5453

@@ -69,7 +68,6 @@ def _run_checks(self):
6968
attempting parsing.
7069
"""
7170
if self.filename is not None:
72-
7371
# Does the recipe provided exist?
7472
if not os.path.exists(self.filename):
7573
bot.exit("Cannot find %s, is the path correct?" % self.filename)

spython/main/parse/parsers/docker.py

-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515

1616
class DockerParser(ParserBase):
17-
1817
name = "docker"
1918

2019
def __init__(self, filename="Dockerfile", load=True):
@@ -49,7 +48,6 @@ def parse(self):
4948
previous = None
5049

5150
for line in self.lines:
52-
5351
parser = self._get_mapping(line, parser, previous)
5452

5553
# Parse it, if appropriate
@@ -147,7 +145,6 @@ def _arg(self, line):
147145

148146
# Try to extract arguments from the line
149147
for arg in line:
150-
151148
# An undefined arg cannot be used
152149
if "=" not in arg:
153150
bot.warning(
@@ -197,15 +194,13 @@ def parse_env(self, envlist):
197194
exports = []
198195

199196
for env in envlist:
200-
201197
pieces = re.split("( |\\\".*?\\\"|'.*?')", env)
202198
pieces = [p for p in pieces if p.strip()]
203199

204200
while pieces:
205201
current = pieces.pop(0)
206202

207203
if current.endswith("="):
208-
209204
# Case 1: ['A='] --> A=
210205
nextone = ""
211206

@@ -243,7 +238,6 @@ def _copy(self, lines):
243238
lines = self._setup("COPY", lines)
244239

245240
for line in lines:
246-
247241
# Take into account multistage builds
248242
layer = None
249243
if line.startswith("--from"):

spython/main/parse/parsers/singularity.py

-6
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414

1515
class SingularityParser(ParserBase):
16-
1716
name = "singularity"
1817

1918
def __init__(self, filename="Singularity", load=True):
@@ -51,7 +50,6 @@ def _setup(self, lines):
5150
bot.warning("SETUP is error prone, please check output.")
5251

5352
for line in lines:
54-
5553
# For all lines, replace rootfs with actual root /
5654
line = re.sub("[$]{?SINGULARITY_ROOTFS}?", "", "$SINGULARITY_ROOTFS")
5755

@@ -171,7 +169,6 @@ def _run(self, lines):
171169

172170
# Multiple line runscript needs multiple lines written to script
173171
if len(lines) > 1:
174-
175172
bot.warning("More than one line detected for runscript!")
176173
bot.warning("These will be echoed into a single script to call.")
177174
self._write_script("/entrypoint.sh", lines)
@@ -257,7 +254,6 @@ def _load_section(self, lines, section, layer=None):
257254
members = []
258255

259256
while True:
260-
261257
if not lines:
262258
break
263259
next_line = lines[0]
@@ -278,7 +274,6 @@ def _load_section(self, lines, section, layer=None):
278274

279275
# Add the list to the config
280276
if members and section is not None:
281-
282277
# Get the correct parsing function
283278
parser = self._get_mapping(section)
284279

@@ -309,7 +304,6 @@ def load_recipe(self):
309304
comments = []
310305

311306
while lines:
312-
313307
# Clean up white trailing/leading space
314308
line = lines.pop(0)
315309
stripped = line.strip()

spython/main/parse/recipe.py

-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ class Recipe:
2323
"""
2424

2525
def __init__(self, recipe=None, layer=1):
26-
2726
self.cmd = None
2827
self.comments = []
2928
self.entrypoint = None

spython/main/parse/writers/docker.py

-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545

4646

4747
class DockerWriter(WriterBase):
48-
4948
name = "docker"
5049

5150
def __init__(self, recipe=None): # pylint: disable=useless-super-delegation
@@ -161,7 +160,6 @@ def write_lines(label, lines):
161160
result = []
162161
continued = False
163162
for line in lines:
164-
165163
# Skip comments and empty lines
166164
if line.strip() == "" or line.strip().startswith("#"):
167165
continue

spython/main/parse/writers/singularity.py

-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414

1515
class SingularityWriter(WriterBase):
16-
1716
name = "singularity"
1817

1918
def __init__(self, recipe=None): # pylint: disable=useless-super-delegation
@@ -52,7 +51,6 @@ def convert(self, runscript="/bin/bash", force=False):
5251

5352
# Write each layer to new file
5453
for stage, parser in self.recipe.items():
55-
5654
# Set the first and active stage
5755
self.stage = stage
5856

@@ -111,9 +109,7 @@ def _create_runscript(self, default="/bin/bash", force=False):
111109

112110
# Only look at Docker if not enforcing default
113111
if not force:
114-
115112
if self.recipe[self.stage].entrypoint is not None:
116-
117113
# The provided entrypoint can be a string or a list
118114
if isinstance(self.recipe[self.stage].entrypoint, list):
119115
entrypoint = " ".join(self.recipe[self.stage].entrypoint)

spython/main/pull.py

-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ def pull(
2424
quiet=False,
2525
singularity_options=None,
2626
):
27-
2827
"""pull will pull a singularity hub or Docker image
2928
3029
Parameters
@@ -92,7 +91,6 @@ def pull(
9291

9392
# Option 3: A custom name we can predict (not commit/hash) and can also show
9493
else:
95-
9694
# As of Singularity 3.x (at least 3.8) output goes to stderr
9795
return final_image, stream_command(cmd, sudo=False, output_type="stderr")
9896

spython/oci/__init__.py

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010

1111
class OciImage(ImageBase):
12-
1312
# Default functions of client don't use sudo
1413
sudo = False
1514

spython/oci/cmd/actions.py

-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def run(
1919
singularity_options=None,
2020
log_format="kubernetes",
2121
):
22-
2322
"""run is a wrapper to create, start, attach, and delete a container.
2423
2524
Equivalent command line example:
@@ -57,7 +56,6 @@ def create(
5756
log_format="kubernetes",
5857
singularity_options=None,
5958
):
60-
6159
"""use the client to create a container from a bundle directory. The bundle
6260
directory should have a config.json. You must be the root user to
6361
create a runtime.
@@ -104,7 +102,6 @@ def _run(
104102
log_format="kubernetes",
105103
singularity_options=None,
106104
):
107-
108105
"""_run is the base function for run and create, the only difference
109106
between the two being that run does not have an option for sync_socket.
110107

0 commit comments

Comments
 (0)