Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runner.singularity: Explicitly support Apptainer's singularity when checking versions #343

Merged
merged 1 commit into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ This release drops support for Python versions 3.6 and 3.7 and adds support for
when the default shell is sh.
([#321](https://github.com/nextstrain/cli/pull/321))

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


# 7.4.0 (21 September 2023)

Expand Down
31 changes: 26 additions & 5 deletions nextstrain/cli/runner/singularity.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@

SINGULARITY_MINIMUM_VERSION = "3.0.0"

APPTAINER_MINIMUM_VERSION = "1.0.0" # forked from Singularity 3.9.5

SINGULARITY_CONFIG_ENV = {
# Store image caches in our runtime root instead of ~/.singularity/…
"SINGULARITY_CACHEDIR": str(CACHE),
Expand Down Expand Up @@ -158,7 +160,7 @@ def SINGULARITY_EXEC_ARGS(): return [
# --writable-tmpfs (3.0.0)
# --no-init (3.0.0)
# --no-umask (3.7.0)
# --no-eval (3.10.0)
# --no-eval (3.10.0, Apptainer 1.1.0)
#
# We opt not to use the --compat bundle option itself mainly for broader
# version compatibility but also because what it includes will likely
Expand Down Expand Up @@ -208,7 +210,7 @@ def SINGULARITY_EXEC_ARGS(): return [
# Don't evaluate the entrypoint command line (e.g. arguments passed via
# `nextstrain build`) before exec-ing the entrypoint. It leads to unwanted
# substitutions that happen too early.
*(["--no-eval"] if singularity_version_at_least("3.10.0") else []),
*(["--no-eval"] if singularity_version_at_least("3.10.0", apptainer="1.1.0") else []),

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


@lru_cache(maxsize = None)
def singularity_version_at_least(min_version: str) -> bool:
def singularity_version_at_least(min_version: str, *, apptainer: str) -> bool:
version = singularity_version()

if not version:
return False

if singularity_is_apptainer():
min_version = apptainer

return version >= Version(min_version)


Expand All @@ -571,3 +576,19 @@ def singularity_version() -> Optional[Version]:
return Version(re.sub(r'-.+$', '', raw_version))
except InvalidVersion:
return None


@lru_cache(maxsize = None)
def singularity_is_apptainer() -> Optional[bool]:
singularity = shutil.which("singularity")

if not singularity:
return None

if not Path(singularity).is_symlink():
return False

try:
return Path(os.readlink(singularity)).name == "apptainer"
except OSError:
return None