Skip to content

Commit 8d8d531

Browse files
committed
runner.singularity: Explicitly support Apptainer's singularity when checking versions
My intent was (and remains) to try to maintain compatibility with both SingularityCE and Apptainer after the Singularity fork. I didn't realize/recall that Apptainer restarted version numbering when I added this version inspection! It would be most ideal if Apptainer's `singularity` executable reported the equivalent/compatible SingularityCE version, but that's understandably difficult and so I get why it reports the Apptainer version instead. Thanks to @osageorange for raising this issue, testing this patch, and reporting back that it works! As SingularityCE and Apptainer continue to diverge, a more complicated treatment of them may be needed, but for now this should suffice. Resolves <#329>.
1 parent 67adaaf commit 8d8d531

File tree

2 files changed

+33
-5
lines changed

2 files changed

+33
-5
lines changed

CHANGES.md

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ This release drops support for Python versions 3.6 and 3.7 and adds support for
3636
when the default shell is sh.
3737
([#321](https://github.com/nextstrain/cli/pull/321))
3838

39+
* The Singularity runtime once again supports Apptainer's `singularity`
40+
executable. The minimum version checking added in 7.0.0 broke usage of the
41+
Singularity runtime with Apptainer (compared with SingularityCE). Our intent
42+
is to support both lineages of Singularity. Thanks to @osageorange for
43+
raising this issue and testing the fix!
44+
([#343](https://github.com/nextstrain/cli/pull/343))
45+
3946

4047
# 7.4.0 (21 September 2023)
4148

nextstrain/cli/runner/singularity.py

+26-5
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@
119119

120120
SINGULARITY_MINIMUM_VERSION = "3.0.0"
121121

122+
APPTAINER_MINIMUM_VERSION = "1.0.0" # forked from Singularity 3.9.5
123+
122124
SINGULARITY_CONFIG_ENV = {
123125
# Store image caches in our runtime root instead of ~/.singularity/…
124126
"SINGULARITY_CACHEDIR": str(CACHE),
@@ -158,7 +160,7 @@ def SINGULARITY_EXEC_ARGS(): return [
158160
# --writable-tmpfs (3.0.0)
159161
# --no-init (3.0.0)
160162
# --no-umask (3.7.0)
161-
# --no-eval (3.10.0)
163+
# --no-eval (3.10.0, Apptainer 1.1.0)
162164
#
163165
# We opt not to use the --compat bundle option itself mainly for broader
164166
# version compatibility but also because what it includes will likely
@@ -208,7 +210,7 @@ def SINGULARITY_EXEC_ARGS(): return [
208210
# Don't evaluate the entrypoint command line (e.g. arguments passed via
209211
# `nextstrain build`) before exec-ing the entrypoint. It leads to unwanted
210212
# substitutions that happen too early.
211-
*(["--no-eval"] if singularity_version_at_least("3.10.0") else []),
213+
*(["--no-eval"] if singularity_version_at_least("3.10.0", apptainer="1.1.0") else []),
212214

213215
# Since we use --no-home above, avoid warnings about not being able to cd
214216
# to $HOME (the default behaviour). run() will override this by specifying
@@ -335,8 +337,8 @@ def test_run():
335337
return [
336338
("singularity is installed",
337339
shutil.which("singularity") is not None),
338-
(f"singularity version {singularity_version()}{SINGULARITY_MINIMUM_VERSION}",
339-
singularity_version_at_least(SINGULARITY_MINIMUM_VERSION)),
340+
(f"singularity version {singularity_version()}{SINGULARITY_MINIMUM_VERSION} ({APPTAINER_MINIMUM_VERSION} for Apptainer)",
341+
singularity_version_at_least(SINGULARITY_MINIMUM_VERSION, apptainer=APPTAINER_MINIMUM_VERSION)),
340342
("singularity works",
341343
test_run()),
342344
]
@@ -546,12 +548,15 @@ def run_bash(script: str, image: str = DEFAULT_IMAGE) -> List[str]:
546548

547549

548550
@lru_cache(maxsize = None)
549-
def singularity_version_at_least(min_version: str) -> bool:
551+
def singularity_version_at_least(min_version: str, *, apptainer: str) -> bool:
550552
version = singularity_version()
551553

552554
if not version:
553555
return False
554556

557+
if singularity_is_apptainer():
558+
min_version = apptainer
559+
555560
return version >= Version(min_version)
556561

557562

@@ -571,3 +576,19 @@ def singularity_version() -> Optional[Version]:
571576
return Version(re.sub(r'-.+$', '', raw_version))
572577
except InvalidVersion:
573578
return None
579+
580+
581+
@lru_cache(maxsize = None)
582+
def singularity_is_apptainer() -> Optional[bool]:
583+
singularity = shutil.which("singularity")
584+
585+
if not singularity:
586+
return None
587+
588+
if not Path(singularity).is_symlink():
589+
return False
590+
591+
try:
592+
return Path(os.readlink(singularity)).name == "apptainer"
593+
except OSError:
594+
return None

0 commit comments

Comments
 (0)