Skip to content

Commit 60f0dac

Browse files
authored
support podman even when not installed as "docker" alias (#1769)
* support podman even when not installed as "docker" alias * fix memory usage gathering with podman * CI: install podman as needed * podman: also terminate any lingering containers
1 parent 0099f11 commit 60f0dac

21 files changed

+125
-41
lines changed

.github/workflows/ci-tests.yml

+5
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,15 @@ jobs:
136136
- uses: actions/checkout@v3
137137

138138
- name: Set up Singularity
139+
if: ${{ matrix.container == 'singularity' }}
139140
uses: eWaterCycle/setup-singularity@v7
140141
with:
141142
singularity-version: ${{ env.singularity_version }}
142143

144+
- name: Set up Podman
145+
if: ${{ matrix.container == 'podman' }}
146+
run: sudo rm -f /usr/bin/docker ; sudo apt-get install -y podman
147+
143148
- name: Set up Python
144149
uses: actions/setup-python@v4
145150
with:

cwltool/builder.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
from cwl_utils import expression
1818
from cwl_utils.file_formats import check_format
1919
from rdflib import Graph
20-
from ruamel.yaml.comments import CommentedMap
2120
from schema_salad.avro.schema import Names, Schema, make_avsc_object
2221
from schema_salad.exceptions import ValidationException
2322
from schema_salad.sourceline import SourceLine
2423
from schema_salad.utils import convert_to_dict, json_dumps
2524
from schema_salad.validate import validate
2625
from typing_extensions import TYPE_CHECKING, Type # pylint: disable=unused-import
2726

27+
from ruamel.yaml.comments import CommentedMap
28+
2829
from .errors import WorkflowException
2930
from .loghandler import _logger
3031
from .mutation import MutationManager

cwltool/command_line_tool.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
)
3131

3232
import shellescape
33-
from ruamel.yaml.comments import CommentedMap, CommentedSeq
3433
from schema_salad.avro.schema import Schema
3534
from schema_salad.exceptions import ValidationException
3635
from schema_salad.ref_resolver import file_uri, uri_file_path
@@ -39,14 +38,16 @@
3938
from schema_salad.validate import validate_ex
4039
from typing_extensions import TYPE_CHECKING, Type
4140

41+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
42+
4243
from .builder import (
4344
INPUT_OBJ_VOCAB,
4445
Builder,
4546
content_limit_respected_read_bytes,
4647
substitute,
4748
)
4849
from .context import LoadingContext, RuntimeContext, getdefault
49-
from .docker import DockerCommandLineJob
50+
from .docker import DockerCommandLineJob, PodmanCommandLineJob
5051
from .errors import UnsupportedRequirement, WorkflowException
5152
from .flatten import flatten
5253
from .job import CommandLineJob, JobBase
@@ -460,6 +461,8 @@ def make_job_runner(self, runtimeContext: RuntimeContext) -> Type[JobBase]:
460461
raise UnsupportedRequirement(
461462
"Both Docker and MPI have been hinted - don't know what to do"
462463
)
464+
if runtimeContext.podman:
465+
return PodmanCommandLineJob
463466
return DockerCommandLineJob
464467
if dockerRequired:
465468
raise UnsupportedRequirement(

cwltool/context.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
Union,
1818
)
1919

20-
# move to a regular typing import when Python 3.3-3.6 is no longer supported
21-
from ruamel.yaml.comments import CommentedMap
2220
from schema_salad.avro.schema import Names
2321
from schema_salad.ref_resolver import Loader
2422
from schema_salad.utils import FetcherCallableType
2523
from typing_extensions import TYPE_CHECKING
2624

25+
# move to a regular typing import when Python 3.3-3.6 is no longer supported
26+
from ruamel.yaml.comments import CommentedMap
27+
2728
from .builder import Builder
2829
from .mpi import MpiConfig
2930
from .mutation import MutationManager

cwltool/cwlrdf.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44

55
from rdflib import Graph
66
from rdflib.query import ResultRow
7-
from ruamel.yaml.comments import CommentedMap
87
from schema_salad.jsonld_context import makerdf
98
from schema_salad.utils import ContextType
109

10+
from ruamel.yaml.comments import CommentedMap
11+
1112
from .cwlviewer import CWLViewer
1213
from .process import Process
1314

cwltool/docker.py

+29-11
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ def __init__(
9090
) -> None:
9191
"""Initialize a command line builder using the Docker software container engine."""
9292
super().__init__(builder, joborder, make_path_mapper, requirements, hints, name)
93+
self.docker_exec = "docker"
9394

94-
@staticmethod
9595
def get_image(
96+
self,
9697
docker_requirement: Dict[str, str],
9798
pull_image: bool,
9899
force_pull: bool,
@@ -117,7 +118,7 @@ def get_image(
117118

118119
for line in (
119120
subprocess.check_output( # nosec
120-
["docker", "images", "--no-trunc", "--all"]
121+
[self.docker_exec, "images", "--no-trunc", "--all"]
121122
)
122123
.decode("utf-8")
123124
.splitlines()
@@ -151,7 +152,7 @@ def get_image(
151152
if (force_pull or not found) and pull_image:
152153
cmd = [] # type: List[str]
153154
if "dockerPull" in docker_requirement:
154-
cmd = ["docker", "pull", str(docker_requirement["dockerPull"])]
155+
cmd = [self.docker_exec, "pull", str(docker_requirement["dockerPull"])]
155156
_logger.info(str(cmd))
156157
subprocess.check_call(cmd, stdout=sys.stderr) # nosec
157158
found = True
@@ -160,7 +161,7 @@ def get_image(
160161
with open(os.path.join(dockerfile_dir, "Dockerfile"), "w") as dfile:
161162
dfile.write(docker_requirement["dockerFile"])
162163
cmd = [
163-
"docker",
164+
self.docker_exec,
164165
"build",
165166
"--tag=%s" % str(docker_requirement["dockerImageId"]),
166167
dockerfile_dir,
@@ -169,7 +170,7 @@ def get_image(
169170
subprocess.check_call(cmd, stdout=sys.stderr) # nosec
170171
found = True
171172
elif "dockerLoad" in docker_requirement:
172-
cmd = ["docker", "load"]
173+
cmd = [self.docker_exec, "load"]
173174
_logger.info(str(cmd))
174175
if os.path.exists(docker_requirement["dockerLoad"]):
175176
_logger.info(
@@ -203,7 +204,7 @@ def get_image(
203204
found = True
204205
elif "dockerImport" in docker_requirement:
205206
cmd = [
206-
"docker",
207+
self.docker_exec,
207208
"import",
208209
str(docker_requirement["dockerImport"]),
209210
str(docker_requirement["dockerImageId"]),
@@ -225,8 +226,8 @@ def get_from_requirements(
225226
force_pull: bool,
226227
tmp_outdir_prefix: str,
227228
) -> Optional[str]:
228-
if not shutil.which("docker"):
229-
raise WorkflowException("docker executable is not available")
229+
if not shutil.which(self.docker_exec):
230+
raise WorkflowException(f"{self.docker_exec} executable is not available")
230231

231232
if self.get_image(
232233
cast(Dict[str, str], r), pull_image, force_pull, tmp_outdir_prefix
@@ -341,10 +342,10 @@ def create_runtime(
341342
runtime = [user_space_docker_cmd, "--quiet", "run", "--nobanner"]
342343
else:
343344
runtime = [user_space_docker_cmd, "run"]
344-
elif runtimeContext.podman:
345-
runtime = ["podman", "run", "-i", "--userns=keep-id"]
346345
else:
347-
runtime = ["docker", "run", "-i"]
346+
runtime = [self.docker_exec, "run", "-i"]
347+
if runtimeContext.podman:
348+
runtime.append("--userns=keep-id")
348349
self.append_volume(
349350
runtime, os.path.realpath(self.outdir), self.builder.outdir, writable=True
350351
)
@@ -460,3 +461,20 @@ def create_runtime(
460461
)
461462

462463
return runtime, cidfile_path
464+
465+
466+
class PodmanCommandLineJob(DockerCommandLineJob):
467+
"""Runs a CommandLineJob in a software container using the podman engine."""
468+
469+
def __init__(
470+
self,
471+
builder: Builder,
472+
joborder: CWLObjectType,
473+
make_path_mapper: Callable[..., PathMapper],
474+
requirements: List[CWLObjectType],
475+
hints: List[CWLObjectType],
476+
name: str,
477+
) -> None:
478+
"""Initialize a command line builder using the Podman software container engine."""
479+
super().__init__(builder, joborder, make_path_mapper, requirements, hints, name)
480+
self.docker_exec = "podman"

cwltool/job.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,7 @@ def run(
874874
cidfile,
875875
runtimeContext.tmpdir_prefix,
876876
not bool(runtimeContext.cidfile_dir),
877+
"podman" if runtimeContext.podman else "docker",
877878
)
878879
elif runtimeContext.user_space_docker_cmd:
879880
monitor_function = functools.partial(self.process_monitor)
@@ -884,6 +885,7 @@ def docker_monitor(
884885
cidfile: str,
885886
tmpdir_prefix: str,
886887
cleanup_cidfile: bool,
888+
docker_exe: str,
887889
process, # type: subprocess.Popen[str]
888890
) -> None:
889891
"""Record memory usage of the running Docker container."""
@@ -901,7 +903,7 @@ def docker_monitor(
901903
os.remove(cidfile)
902904
except OSError as exc:
903905
_logger.warning(
904-
"Ignored error cleaning up Docker cidfile: %s", exc
906+
"Ignored error cleaning up %s cidfile: %s", docker_exe, exc
905907
)
906908
return
907909
try:
@@ -915,15 +917,19 @@ def docker_monitor(
915917
stats_file_name = stats_file.name
916918
try:
917919
with open(stats_file_name, mode="w") as stats_file_handle:
920+
cmds = [docker_exe, "stats"]
921+
if "podman" not in docker_exe:
922+
cmds.append("--no-trunc")
923+
cmds.extend(["--format", "{{.MemPerc}}", cid])
918924
stats_proc = subprocess.Popen( # nosec
919-
["docker", "stats", "--no-trunc", "--format", "{{.MemPerc}}", cid],
925+
cmds,
920926
stdout=stats_file_handle,
921927
stderr=subprocess.DEVNULL,
922928
)
923929
process.wait()
924930
stats_proc.kill()
925931
except OSError as exc:
926-
_logger.warning("Ignored error with docker stats: %s", exc)
932+
_logger.warning("Ignored error with %s stats: %s", docker_exe, exc)
927933
return
928934
max_mem_percent = 0 # type: float
929935
mem_percent = 0 # type: float
@@ -938,8 +944,10 @@ def docker_monitor(
938944
)
939945
if mem_percent > max_mem_percent:
940946
max_mem_percent = mem_percent
941-
except ValueError:
942-
break
947+
except ValueError as exc:
948+
_logger.debug(
949+
"%s stats parsing error in line %s: %s", docker_exe, line, exc
950+
)
943951
_logger.info(
944952
"[job %s] Max memory used: %iMiB",
945953
self.name,

cwltool/load_tool.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
)
2222

2323
from cwl_utils.parser import cwl_v1_2, cwl_v1_2_utils
24-
from ruamel.yaml.comments import CommentedMap, CommentedSeq
2524
from schema_salad.exceptions import ValidationException
2625
from schema_salad.ref_resolver import Loader, file_uri
2726
from schema_salad.schema import validate_doc
@@ -34,6 +33,8 @@
3433
json_dumps,
3534
)
3635

36+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
37+
3738
from . import CWL_CONTENT_TYPES, process, update
3839
from .context import LoadingContext
3940
from .errors import GraphTargetMissingException

cwltool/main.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@
3636
import argcomplete
3737
import coloredlogs
3838
import pkg_resources # part of setuptools
39-
import ruamel.yaml
40-
from ruamel.yaml.comments import CommentedMap, CommentedSeq
41-
from ruamel.yaml.main import YAML
4239
from schema_salad.exceptions import ValidationException
4340
from schema_salad.ref_resolver import Loader, file_uri, uri_file_path
4441
from schema_salad.sourceline import cmap, strip_dup_lineno
4542
from schema_salad.utils import ContextType, FetcherCallableType, json_dumps, yaml_no_ts
4643

44+
import ruamel.yaml
45+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
46+
from ruamel.yaml.main import YAML
47+
4748
from . import CWL_CONTENT_TYPES, workflow
4849
from .argparser import arg_parser, generate_parser, get_default_args
4950
from .context import LoadingContext, RuntimeContext, getdefault
@@ -105,6 +106,8 @@
105106
)
106107
from .workflow import Workflow
107108

109+
docker_exe: str
110+
108111

109112
def _terminate_processes() -> None:
110113
"""Kill all spawned processes.
@@ -117,6 +120,7 @@ def _terminate_processes() -> None:
117120
continuing to execute while it kills the processes that they've
118121
spawned. This may occasionally lead to unexpected behaviour.
119122
"""
123+
global docker_exe
120124
# It's possible that another thread will spawn a new task while
121125
# we're executing, so it's not safe to use a for loop here.
122126
while processes_to_kill:
@@ -130,7 +134,7 @@ def _terminate_processes() -> None:
130134
try:
131135
with open(cidfile[0]) as inp_stream:
132136
p = subprocess.Popen( # nosec
133-
["docker", "kill", inp_stream.read()], shell=False # nosec
137+
[docker_exe, "kill", inp_stream.read()], shell=False # nosec
134138
)
135139
try:
136140
p.wait(timeout=10)
@@ -1009,6 +1013,7 @@ def main(
10091013
stderr_handler = _logger.handlers[-1]
10101014
workflowobj = None
10111015
prov_log_handler: Optional[logging.StreamHandler[ProvOut]] = None
1016+
global docker_exe
10121017
try:
10131018
if args is None:
10141019
if argsl is None:
@@ -1030,6 +1035,10 @@ def main(
10301035
else:
10311036
runtimeContext = runtimeContext.copy()
10321037

1038+
if runtimeContext.podman:
1039+
docker_exe = "podman"
1040+
else:
1041+
docker_exe = "docker"
10331042
# If caller parsed its own arguments, it may not include every
10341043
# cwltool option, so fill in defaults to avoid crashing when
10351044
# dereferencing them in args.

cwltool/pack.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@
1414
cast,
1515
)
1616

17-
from ruamel.yaml.comments import CommentedMap, CommentedSeq
1817
from schema_salad.ref_resolver import Loader, SubLoader
1918
from schema_salad.utils import ResolveType
2019

20+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
21+
2122
from .context import LoadingContext
2223
from .load_tool import fetch_document, resolve_and_validate_document
2324
from .process import shortname, uniquename

cwltool/process.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
from mypy_extensions import mypyc_attr
3636
from pkg_resources import resource_stream
3737
from rdflib import Graph
38-
from ruamel.yaml.comments import CommentedMap, CommentedSeq
3938
from schema_salad.avro.schema import (
4039
Names,
4140
Schema,
@@ -50,6 +49,8 @@
5049
from schema_salad.validate import avro_type_name, validate_ex
5150
from typing_extensions import TYPE_CHECKING
5251

52+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
53+
5354
from .builder import INPUT_OBJ_VOCAB, Builder
5455
from .context import LoadingContext, RuntimeContext, getdefault
5556
from .errors import UnsupportedRequirement, WorkflowException

cwltool/procgenerator.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import copy
22
from typing import Dict, Optional, Tuple, cast
33

4-
from ruamel.yaml.comments import CommentedMap
54
from schema_salad.exceptions import ValidationException
65
from schema_salad.sourceline import indent
76

7+
from ruamel.yaml.comments import CommentedMap
8+
89
from .context import LoadingContext, RuntimeContext
910
from .errors import WorkflowException
1011
from .load_tool import load_tool

cwltool/update.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
cast,
1212
)
1313

14-
from ruamel.yaml.comments import CommentedMap, CommentedSeq
1514
from schema_salad.exceptions import ValidationException
1615
from schema_salad.ref_resolver import Loader
1716
from schema_salad.sourceline import SourceLine
1817

18+
from ruamel.yaml.comments import CommentedMap, CommentedSeq
19+
1920
from .loghandler import _logger
2021
from .utils import CWLObjectType, CWLOutputType, aslist, visit_class, visit_field
2122

0 commit comments

Comments
 (0)