Skip to content

Commit f7efad1

Browse files
authored
Merge pull request #4 from khillion/master
Allow to pull images with Singularity
2 parents 27e2557 + 47ba7b5 commit f7efad1

File tree

11 files changed

+223
-41
lines changed

11 files changed

+223
-41
lines changed

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ services:
1313
install:
1414
- true
1515
script:
16-
- docker build . -f .travis/${TARGET}.docker
16+
- docker build --tag ${TARGET} . -f .travis/${TARGET}.docker
17+
- docker run -v /var/run/docker.sock:/var/run/docker.sock ${TARGET} /bin/bash -c "$(cat .travis/${TARGET}.run)"

.travis/.singularity_key.txt

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
-----BEGIN PGP PUBLIC KEY BLOCK-----
2+
3+
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
69+
=fAJZ
70+
-----END PGP PUBLIC KEY BLOCK-----

.travis/mypy.run

Whitespace-only changes.

.travis/pep8.run

Whitespace-only changes.

.travis/py3.docker

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
FROM kernsuite/base:5
2-
RUN docker-apt-install python3-pip
2+
ADD .travis/.singularity_key.txt /
3+
RUN docker-apt-install wget
4+
RUN wget -O/etc/apt/sources.list.d/neurodebian.sources.list http://neuro.debian.net/lists/bionic.us-nh.full && apt-key add /.singularity_key.txt
5+
RUN docker-apt-install python3-pip singularity-container docker.io
36
ADD . /code
47
WORKDIR /code
58
RUN pip3 install .
6-
RUN python3 setup.py test

.travis/py3.run

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
service docker start && cd /code && python3 setup.py test

README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,41 @@
1+
# cwl-utils
2+
13
A collection of scripts to demonstrate the use of the [new Python classes for loading and parsing CWL v1.0 documents](https://github.com/common-workflow-language/cwl-utils/blob/master/cwl_utils/parser_v1_0.py).
24

35

46
`cwl_utils/parser_v1_0.py` was created via
57
`schema-salad-tool --codegen python https://github.com/common-workflow-language/common-workflow-language/raw/master/v1.0/CommonWorkflowLanguage.yml`
68

9+
## Install
10+
711
Requires Python 3.6.x or Python 3.7
812

913
``` bash
1014
git clone https://github.com/common-workflow-language/cwl-utils.git
1115
virtualenv -p python3.6 venv3.6
1216
source venv3.6/bin/activate
1317
pip install cwl-utils
14-
python docker-extract.py path_to_my_workflow.cwl
1518
```
1619

17-
to regenerate install `schema_salad` package and run:
20+
## Usage
21+
22+
### Pull the image with Docker
23+
24+
This is the default behaviour:
25+
26+
```bash
27+
python docker_extract.py DIRECTORY path_to_my_workflow.cwl
28+
```
29+
30+
### Pull the image with Singularity
31+
32+
```bash
33+
python docker_extract.py --singularity DIRECTORY path_to_my_workflow.cwl
34+
```
35+
36+
## Regenerate
37+
38+
To regenerate install `schema_salad` package and run:
1839

1940
```
2041
schema-salad-tool --codegen python \

cwl_utils/docker_extract.py

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
#!/usr/bin/env python3
2-
import sys
2+
import argparse
33
import os
4+
import sys
5+
6+
from cwl_utils.image_puller import DockerImagePuller, SingularityImagePuller
47
import cwl_utils.parser_v1_0 as cwl
5-
import subprocess
6-
import argparse
78

89

910
def parse_args():
1011
parser = argparse.ArgumentParser(
11-
description='Tool to save docker images from a cwl workflow \n '
12-
'and generate udocker loading commands.')
12+
description='Tool to save docker images from a cwl workflow.')
1313
parser.add_argument('dir', help='Directory in which to save images')
1414
parser.add_argument('input', help='Input CWL workflow')
15+
parser.add_argument('-s', '--singularity', help='Use singularity to pull the image', action='store_true')
1516
return parser.parse_args()
1617

1718

@@ -22,32 +23,11 @@ def main():
2223
top = cwl.load_document(args.input)
2324

2425
for req in set(traverse(top)):
25-
image_name = get_image_name(req)
26-
save_docker_image(req, image_name, args.dir)
27-
28-
print(load_docker_image(image_name))
29-
30-
31-
def get_image_name(req):
32-
return ''.join(req.split('/')) + '.tar'
33-
34-
35-
def load_docker_image(image_name):
36-
return f'udocker load -i {image_name}'
37-
38-
39-
def save_docker_image(req, image_name, image_dir):
40-
cmd_pull = ['docker', 'pull', req]
41-
try:
42-
subprocess.run(cmd_pull, check=True, stdout=subprocess.PIPE,
43-
stderr=subprocess.STDOUT)
44-
except subprocess.CalledProcessError as err:
45-
if err.output:
46-
raise subprocess.SubprocessError(err.output)
47-
raise err
48-
cmd_save = ['docker', 'save', '-o', os.path.join(image_dir, image_name),
49-
req]
50-
subprocess.run(cmd_save, check=True)
26+
if args.singularity:
27+
image_puller = SingularityImagePuller(req, args.dir)
28+
else:
29+
image_puller = DockerImagePuller(req, args.dir)
30+
image_puller.save_docker_image()
5131

5232

5333
def extract_docker_requirements(process: cwl.Process):

cwl_utils/image_puller.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from abc import ABC, abstractmethod
2+
import logging
3+
import os
4+
import subprocess
5+
6+
logging.basicConfig(level=logging.INFO)
7+
_LOGGER = logging.getLogger(__name__)
8+
9+
10+
class ImagePuller(ABC):
11+
12+
def __init__(self, req, save_directory):
13+
self.req = req
14+
self.save_directory = save_directory
15+
16+
@abstractmethod
17+
def get_image_name(self):
18+
pass
19+
20+
@abstractmethod
21+
def save_docker_image(self):
22+
pass
23+
24+
@staticmethod
25+
def _run_command_pull(cmd_pull):
26+
try:
27+
subprocess.run(cmd_pull, check=True, stdout=subprocess.PIPE,
28+
stderr=subprocess.STDOUT)
29+
except subprocess.CalledProcessError as err:
30+
if err.output:
31+
raise subprocess.SubprocessError(err.output)
32+
raise err
33+
34+
35+
class DockerImagePuller(ImagePuller):
36+
"""
37+
Pull docker image with Docker
38+
"""
39+
40+
def get_image_name(self):
41+
return ''.join(self.req.split('/')) + '.tar'
42+
43+
def generate_udocker_loading_command(self):
44+
return f'udocker load -i {self.get_image_name()}'
45+
46+
def save_docker_image(self):
47+
_LOGGER.info(f"Pulling {self.req} with Docker...")
48+
cmd_pull = ['docker', 'pull', self.req]
49+
ImagePuller._run_command_pull(cmd_pull)
50+
cmd_save = ['docker', 'save', '-o', os.path.join(self.save_directory,
51+
self.get_image_name()),
52+
self.req]
53+
subprocess.run(cmd_save, check=True)
54+
_LOGGER.info(f"Image successfully pulled: {self.save_directory}/{self.get_image_name()}")
55+
print(self.generate_udocker_loading_command())
56+
57+
58+
class SingularityImagePuller(ImagePuller):
59+
"""
60+
Pull docker image with Singularity
61+
"""
62+
CHARS_TO_REPLACE = ['/', ':']
63+
NEW_CHAR = '-'
64+
65+
def __init__(self, req, save_directory):
66+
super(SingularityImagePuller, self).__init__(req, save_directory)
67+
version = subprocess.check_output(["singularity", "--version"], universal_newlines=True)
68+
if version.startswith("singularity version "):
69+
version = version[20:]
70+
self.version = version
71+
72+
def _is_version_2_6(self): # type: ()->bool
73+
return self.version.startswith("2.6")
74+
75+
def _is_version_3_or_newer(self): # type: ()->bool
76+
return int(self.version[0]) >= 3
77+
78+
def get_image_name(self):
79+
image_name = self.req
80+
for char in self.CHARS_TO_REPLACE:
81+
image_name = image_name.replace(char, self.NEW_CHAR)
82+
if self._is_version_2_6():
83+
suffix = ".img"
84+
elif self._is_version_3_or_newer():
85+
suffix = ".sif"
86+
else:
87+
raise Exception("Don't know how to handle this version of singularity: {}.".format(self.version))
88+
return f'{image_name}.{suffix}'
89+
90+
def save_docker_image(self):
91+
_LOGGER.info(f"Pulling {self.req} with Singularity...")
92+
cmd_pull = ['singularity', 'pull', '--name', os.path.join(self.save_directory,
93+
self.get_image_name()),
94+
f'docker://{self.req}']
95+
ImagePuller._run_command_pull(cmd_pull)
96+
_LOGGER.info(f"Image successfully pulled: {self.save_directory}/{self.get_image_name()}")

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@
1818
setup_requires=['pytest-runner'],
1919
tests_require=['pytest'],
2020
test_suite='tests',
21+
scripts=['cwl_utils/docker_extract.py'],
2122
)

0 commit comments

Comments
 (0)