|
1 | 1 | """Support for executing Docker format containers using Singularity {2,3}.x or Apptainer 1.x.""" |
2 | 2 |
|
| 3 | +import json |
3 | 4 | import logging |
4 | 5 | import os |
5 | 6 | import os.path |
6 | 7 | import re |
7 | 8 | import shutil |
| 9 | +import subprocess |
8 | 10 | import sys |
9 | 11 | from collections.abc import Callable, MutableMapping |
10 | | -from subprocess import check_call, check_output # nosec |
| 12 | +from subprocess import DEVNULL, check_call, check_output, run # nosec |
11 | 13 | from typing import cast |
12 | 14 |
|
13 | 15 | from schema_salad.sourceline import SourceLine |
@@ -144,6 +146,27 @@ def _normalize_sif_id(string: str) -> str: |
144 | 146 | string += "_latest" |
145 | 147 | return string.replace("/", "_") + ".sif" |
146 | 148 |
|
| 149 | +def _inspect_singularity_image(path: str) -> bool: |
| 150 | + """Inspect singularity image to be sure it is not an empty directory.""" |
| 151 | + cmd = [ |
| 152 | + "singularity", |
| 153 | + "inspect", |
| 154 | + "--json", |
| 155 | + path, |
| 156 | + ] |
| 157 | + try: |
| 158 | + result = run(cmd, capture_output=True, text=True) |
| 159 | + except Exception: |
| 160 | + return False |
| 161 | + |
| 162 | + if result.returncode == 0: |
| 163 | + try: |
| 164 | + output = json.loads(result.stdout) |
| 165 | + except json.JSONDecodeError: |
| 166 | + return False |
| 167 | + if output.get('data', {}).get('attributes', {}): |
| 168 | + return True |
| 169 | + return False |
147 | 170 |
|
148 | 171 | class SingularityCommandLineJob(ContainerCommandLineJob): |
149 | 172 | def __init__( |
@@ -229,24 +252,40 @@ def get_image( |
229 | 252 | ) |
230 | 253 | found = True |
231 | 254 | elif "dockerImageId" not in dockerRequirement and "dockerPull" in dockerRequirement: |
232 | | - match = re.search(pattern=r"([a-z]*://)", string=dockerRequirement["dockerPull"]) |
233 | | - img_name = _normalize_image_id(dockerRequirement["dockerPull"]) |
234 | | - candidates.append(img_name) |
235 | | - if is_version_3_or_newer(): |
236 | | - sif_name = _normalize_sif_id(dockerRequirement["dockerPull"]) |
237 | | - candidates.append(sif_name) |
238 | | - dockerRequirement["dockerImageId"] = sif_name |
| 255 | + # looking for local singularity sandbox image and handle it as a local image |
| 256 | + if os.path.isdir(dockerRequirement["dockerPull"]) and _inspect_singularity_image(dockerRequirement["dockerPull"]): |
| 257 | + dockerRequirement["dockerImageId"] = dockerRequirement["dockerPull"] |
| 258 | + _logger.info( |
| 259 | + "Using local Singularity sandbox image found in %s", |
| 260 | + dockerRequirement["dockerImageId"], |
| 261 | + ) |
| 262 | + return True |
239 | 263 | else: |
240 | | - dockerRequirement["dockerImageId"] = img_name |
241 | | - if not match: |
242 | | - dockerRequirement["dockerPull"] = "docker://" + dockerRequirement["dockerPull"] |
| 264 | + match = re.search(pattern=r"([a-z]*://)", string=dockerRequirement["dockerPull"]) |
| 265 | + img_name = _normalize_image_id(dockerRequirement["dockerPull"]) |
| 266 | + candidates.append(img_name) |
| 267 | + if is_version_3_or_newer(): |
| 268 | + sif_name = _normalize_sif_id(dockerRequirement["dockerPull"]) |
| 269 | + candidates.append(sif_name) |
| 270 | + dockerRequirement["dockerImageId"] = sif_name |
| 271 | + else: |
| 272 | + dockerRequirement["dockerImageId"] = img_name |
| 273 | + if not match: |
| 274 | + dockerRequirement["dockerPull"] = "docker://" + dockerRequirement["dockerPull"] |
243 | 275 | elif "dockerImageId" in dockerRequirement: |
244 | 276 | if os.path.isfile(dockerRequirement["dockerImageId"]): |
245 | 277 | found = True |
246 | | - candidates.append(dockerRequirement["dockerImageId"]) |
247 | | - candidates.append(_normalize_image_id(dockerRequirement["dockerImageId"])) |
248 | | - if is_version_3_or_newer(): |
249 | | - candidates.append(_normalize_sif_id(dockerRequirement["dockerImageId"])) |
| 278 | + candidates.append(dockerRequirement["dockerImageId"]) |
| 279 | + candidates.append(_normalize_image_id(dockerRequirement["dockerImageId"])) |
| 280 | + if is_version_3_or_newer(): |
| 281 | + candidates.append(_normalize_sif_id(dockerRequirement["dockerImageId"])) |
| 282 | + # handling local singularity sandbox image |
| 283 | + elif os.path.isdir(dockerRequirement["dockerImageId"]) and _inspect_singularity_image(dockerRequirement["dockerImageId"]): |
| 284 | + _logger.info( |
| 285 | + "Using local Singularity sandbox image found in %s", |
| 286 | + dockerRequirement["dockerImageId"], |
| 287 | + ) |
| 288 | + return True |
250 | 289 |
|
251 | 290 | targets = [os.getcwd()] |
252 | 291 | if "CWL_SINGULARITY_CACHE" in os.environ: |
|
0 commit comments