38
38
pass
39
39
40
40
_USERNS = None
41
+ _SINGULARITY_VERSION = None
41
42
42
43
def _singularity_supports_userns (): # type: ()->bool
43
44
global _USERNS # pylint: disable=global-statement
@@ -53,10 +54,30 @@ def _singularity_supports_userns(): # type: ()->bool
53
54
_USERNS = False
54
55
return _USERNS
55
56
57
+
58
+ def get_version (): # type: ()->Text
59
+ global _SINGULARITY_VERSION # pylint: disable=global-statement
60
+ if not _SINGULARITY_VERSION :
61
+ _SINGULARITY_VERSION = check_output (["singularity" , "--version" ], universal_newlines = True )
62
+ if _SINGULARITY_VERSION .startswith ("singularity version " ):
63
+ _SINGULARITY_VERSION = _SINGULARITY_VERSION [20 :]
64
+ return _SINGULARITY_VERSION
65
+
66
+ def is_version_2_6 (): # type: ()->bool
67
+ return get_version ().startswith ("2.6" )
68
+
69
+ def is_version_3_or_newer (): # type: ()->bool
70
+ return int (get_version ()[0 ]) >= 3
71
+
72
+ def is_version_3_1_or_newer (): # type: ()->bool
73
+ version = get_version ().split ('.' )
74
+ return int (version [0 ]) >= 4 or (int (version [0 ]) == 3 and int (version [1 ]) >= 1 )
75
+
56
76
def _normalize_image_id (string ): # type: (Text)->Text
57
- candidate = re .sub (pattern = r'([a-z]*://)' , repl = r'' , string = string )
58
- return re .sub (pattern = r'[:/]' , repl = r'-' , string = candidate ) + ".img"
77
+ return string .replace ('/' , '_' ) + '.img'
59
78
79
+ def _normalize_sif_id (string ): # type: (Text)->Text
80
+ return string .replace ('/' , '_' ) + '.sif'
60
81
61
82
class SingularityCommandLineJob (ContainerCommandLineJob ):
62
83
@@ -77,51 +98,97 @@ def get_image(dockerRequirement, # type: Dict[Text, Text]
77
98
78
99
candidates = []
79
100
101
+ cache_folder = None
102
+ if "CWL_SINGULARITY_CACHE" in os .environ :
103
+ cache_folder = os .environ ["CWL_SINGULARITY_CACHE" ]
104
+ elif is_version_2_6 () and "SINGULARITY_PULLFOLDER" in os .environ :
105
+ cache_folder = os .environ ["SINGULARITY_PULLFOLDER" ]
106
+
80
107
if "dockerImageId" not in dockerRequirement and "dockerPull" in dockerRequirement :
81
108
match = re .search (pattern = r'([a-z]*://)' , string = dockerRequirement ["dockerPull" ])
82
- candidate = _normalize_image_id (dockerRequirement ['dockerPull' ])
83
- candidates .append (candidate )
84
- dockerRequirement ['dockerImageId' ] = candidate
109
+ img_name = _normalize_image_id (dockerRequirement ['dockerPull' ])
110
+ candidates .append (img_name )
111
+ if is_version_3_or_newer ():
112
+ sif_name = _normalize_sif_id (dockerRequirement ['dockerPull' ])
113
+ candidates .append (sif_name )
114
+ dockerRequirement ["dockerImageId" ] = sif_name
115
+ else :
116
+ dockerRequirement ["dockerImageId" ] = img_name
85
117
if not match :
86
118
dockerRequirement ["dockerPull" ] = "docker://" + dockerRequirement ["dockerPull" ]
87
119
elif "dockerImageId" in dockerRequirement :
88
120
candidates .append (dockerRequirement ['dockerImageId' ])
89
121
candidates .append (_normalize_image_id (dockerRequirement ['dockerImageId' ]))
122
+ if is_version_3_or_newer ():
123
+ candidates .append (_normalize_sif_id (dockerRequirement ['dockerPull' ]))
90
124
91
- # check if Singularity image is available in $SINGULARITY_CACHEDIR
92
125
targets = [os .getcwd ()]
93
- for env in ("SINGULARITY_CACHEDIR" , "SINGULARITY_PULLFOLDER" ):
94
- if env in os .environ :
95
- targets .append (os .environ [env ])
126
+ if "CWL_SINGULARITY_CACHE" in os .environ :
127
+ targets .append (os .environ ["CWL_SINGULARITY_CACHE" ])
128
+ if is_version_2_6 () and "SINGULARITY_PULLFOLDER" in os .environ :
129
+ targets .append (os .environ ["SINGULARITY_PULLFOLDER" ])
96
130
for target in targets :
97
- for candidate in candidates :
98
- path = os .path .join (target , candidate )
99
- if os .path .isfile (path ):
100
- _logger .info (
101
- "Using local copy of Singularity image found in %s" ,
102
- target )
103
- dockerRequirement ["dockerImageId" ] = path
104
- found = True
105
-
131
+ for dirpath , subdirs , files in os .walk (target ):
132
+ for entry in files :
133
+ if entry in candidates :
134
+ path = os .path .join (dirpath , entry )
135
+ if os .path .isfile (path ):
136
+ _logger .info (
137
+ "Using local copy of Singularity image found in %s" ,
138
+ dirpath )
139
+ dockerRequirement ["dockerImageId" ] = path
140
+ found = True
106
141
if (force_pull or not found ) and pull_image :
107
142
cmd = [] # type: List[Text]
108
143
if "dockerPull" in dockerRequirement :
109
- cmd = ["singularity" , "pull" , "--force" , "--name" ,
110
- str (dockerRequirement ["dockerImageId" ]),
111
- str (dockerRequirement ["dockerPull" ])]
112
- _logger .info (Text (cmd ))
113
- check_call (cmd , stdout = sys .stderr ) # nosec
114
- found = True
144
+ if cache_folder :
145
+ env = os .environ .copy ()
146
+ if is_version_2_6 ():
147
+ env ['SINGULARITY_PULLFOLDER' ] = cache_folder
148
+ cmd = ["singularity" , "pull" , "--force" , "--name" ,
149
+ dockerRequirement ["dockerImageId" ],
150
+ str (dockerRequirement ["dockerPull" ])]
151
+ else :
152
+ cmd = ["singularity" , "pull" , "--force" , "--name" ,
153
+ "{}/{}" .format (
154
+ cache_folder ,
155
+ dockerRequirement ["dockerImageId" ]),
156
+ str (dockerRequirement ["dockerPull" ])]
157
+
158
+ _logger .info (Text (cmd ))
159
+ check_call (cmd , env = env , stdout = sys .stderr ) # nosec
160
+ dockerRequirement ["dockerImageId" ] = '{}/{}' .format (
161
+ cache_folder , dockerRequirement ["dockerImageId" ])
162
+ found = True
163
+ else :
164
+ cmd = ["singularity" , "pull" , "--force" , "--name" ,
165
+ str (dockerRequirement ["dockerImageId" ]),
166
+ str (dockerRequirement ["dockerPull" ])]
167
+ _logger .info (Text (cmd ))
168
+ check_call (cmd , stdout = sys .stderr ) # nosec
169
+ found = True
170
+
115
171
elif "dockerFile" in dockerRequirement :
116
172
raise WorkflowException (SourceLine (
117
173
dockerRequirement , 'dockerFile' ).makeError (
118
174
"dockerFile is not currently supported when using the "
119
175
"Singularity runtime for Docker containers." ))
120
176
elif "dockerLoad" in dockerRequirement :
177
+ if is_version_3_1_or_newer ():
178
+ if 'dockerImageId' in dockerRequirement :
179
+ name = "{}.sif" .format (dockerRequirement ["dockerImageId" ])
180
+ else :
181
+ name = "{}.sif" .format (dockerRequirement ["dockerLoad" ])
182
+ cmd = ["singularity" , "build" , name ,
183
+ "docker-archive://{}" .format (dockerRequirement ["dockerLoad" ])]
184
+ _logger .info (Text (cmd ))
185
+ check_call (cmd , stdout = sys .stderr ) # nosec
186
+ found = True
187
+ dockerRequirement ['dockerImageId' ] = name
121
188
raise WorkflowException (SourceLine (
122
189
dockerRequirement , 'dockerLoad' ).makeError (
123
190
"dockerLoad is not currently supported when using the "
124
- "Singularity runtime for Docker containers." ))
191
+ "Singularity runtime (version less than 3.1) for Docker containers." ))
125
192
elif "dockerImport" in dockerRequirement :
126
193
raise WorkflowException (SourceLine (
127
194
dockerRequirement , 'dockerImport' ).makeError (
@@ -252,8 +319,8 @@ def add_writable_directory_volume(self,
252
319
253
320
254
321
def create_runtime (self ,
255
- env , # type: MutableMapping[Text, Text]
256
- runtime_context # type: RuntimeContext
322
+ env , # type: MutableMapping[Text, Text]
323
+ runtime_context # type: RuntimeContext
257
324
): # type: (...) -> Tuple[List, Optional[Text]]
258
325
""" Returns the Singularity runtime list of commands and options."""
259
326
any_path_okay = self .builder .get_requirement ("DockerRequirement" )[1 ] \
@@ -262,10 +329,16 @@ def create_runtime(self,
262
329
u"--ipc" ]
263
330
if _singularity_supports_userns ():
264
331
runtime .append (u"--userns" )
265
- runtime .append (u"--bind" )
266
- runtime .append (u"{}:{}:rw" .format (
267
- docker_windows_path_adjust (os .path .realpath (self .outdir )),
268
- self .builder .outdir ))
332
+ if is_version_3_1_or_newer ():
333
+ runtime .append (u"--home" )
334
+ runtime .append (u"{}:{}" .format (
335
+ docker_windows_path_adjust (os .path .realpath (self .outdir )),
336
+ self .builder .outdir ))
337
+ else :
338
+ runtime .append (u"--bind" )
339
+ runtime .append (u"{}:{}:rw" .format (
340
+ docker_windows_path_adjust (os .path .realpath (self .outdir )),
341
+ self .builder .outdir ))
269
342
runtime .append (u"--bind" )
270
343
tmpdir = "/tmp" # nosec
271
344
runtime .append (u"{}:{}:rw" .format (
@@ -283,6 +356,7 @@ def create_runtime(self,
283
356
runtime .append (u"--pwd" )
284
357
runtime .append (u"%s" % (docker_windows_path_adjust (self .builder .outdir )))
285
358
359
+
286
360
if runtime_context .custom_net :
287
361
raise UnsupportedRequirement (
288
362
"Singularity implementation does not support custom networking" )
0 commit comments