From 8bc7f13059dc638054532c3fe3769b5122f7c076 Mon Sep 17 00:00:00 2001 From: Sergey Serebryakov Date: Wed, 2 Aug 2023 10:24:17 -0700 Subject: [PATCH] Moving singularity version string parsing into its own method. (#328) - New `Version.from_version_string` method that parses version strings printed by `singulairty --version` commands. - Adding unit tests for this method. --- .../mlcube_singularity/singularity_client.py | 64 ++++++++++++------- .../tests/test_singularity_client.py | 56 ++++++++++++++++ 2 files changed, 96 insertions(+), 24 deletions(-) diff --git a/runners/mlcube_singularity/mlcube_singularity/singularity_client.py b/runners/mlcube_singularity/mlcube_singularity/singularity_client.py index ea09395..dcbf795 100644 --- a/runners/mlcube_singularity/mlcube_singularity/singularity_client.py +++ b/runners/mlcube_singularity/mlcube_singularity/singularity_client.py @@ -22,6 +22,18 @@ class Runtime(Enum): UNKNOWN = 0 APPTAINER = 1 SINGULARITY = 2 + """Singularity / SingularityCE + + SingularityCE + https://github.com/sylabs/singularity/releases/tag/v3.8.0 + This is the first release of SingularityCE 3.8.0, the Community Edition of the Singularity container runtime. + The package name for this release is now `singularity-ce`. + + Singularity + https://github.com/sylabs/singularity/releases/tag/v3.7.4 + Singularity 3.7.4 is the most recent stable release of Singularity prior to Sylabs' fork from + `github.com/hpcng/singularity`. + """ class Version: @@ -32,6 +44,33 @@ def __init__(self, runtime: Runtime, version: semver.VersionInfo) -> None: def __str__(self) -> str: return f"Version(runtime={self.runtime.name}, version={self.version})" + @classmethod + def from_version_string(cls, version_string: str) -> "Version": + version_string = version_string.strip() + if version_string.startswith("singularity version "): + runtime, version_string = ( + Runtime.SINGULARITY, + version_string[20:].strip(), + ) + elif version_string.startswith("singularity-ce version "): + runtime, version_string = ( + Runtime.SINGULARITY, + version_string[23:].strip(), + ) + elif version_string.startswith("apptainer version "): + runtime, version_string = Runtime.APPTAINER, version_string[18:].strip() + elif "/" in version_string: # Handle old stuff like "x.y.z-pull/123-0a5d" + runtime, version_string = Runtime.SINGULARITY, version_string.replace( + "/", "+", 1 + ) + else: + logger.warning( + "Version.from_version_string unrecognized container runtime (version_string: %s)", + version_string, + ) + runtime = Runtime.UNKNOWN + return Version(runtime, semver.VersionInfo.parse(version_string)) + class ImageSpec(Enum): """Build specification format for building singularity images. @@ -138,30 +177,7 @@ def init(self, force: bool = False) -> None: "version_cmd": version_cmd, }, ) - - if version_string.startswith("singularity version "): - runtime, version_string = ( - Runtime.SINGULARITY, - version_string[20:].strip(), - ) - elif version_string.startswith("singularity-ce version "): - runtime, version_string = ( - Runtime.SINGULARITY, - version_string[23:].strip(), - ) - elif version_string.startswith("apptainer version "): - runtime, version_string = Runtime.APPTAINER, version_string[18:].strip() - elif "/" in version_string: # Handle old stuff like "x.y.z-pull/123-0a5d" - runtime, version_string = Runtime.SINGULARITY, version_string.replace( - "/", "+", 1 - ) - else: - logger.warning( - "Client.init unrecognized container runtime (version_string: %s)", - version_string, - ) - runtime = Runtime.UNKNOWN - self.version = Version(runtime, semver.VersionInfo.parse(version_string)) + self.version = Version.from_version_string(version_string) logger.debug("Client.init version=%s", self.version) def build( diff --git a/runners/mlcube_singularity/mlcube_singularity/tests/test_singularity_client.py b/runners/mlcube_singularity/mlcube_singularity/tests/test_singularity_client.py index d7c076b..df4489d 100644 --- a/runners/mlcube_singularity/mlcube_singularity/tests/test_singularity_client.py +++ b/runners/mlcube_singularity/mlcube_singularity/tests/test_singularity_client.py @@ -38,3 +38,59 @@ def test_supports_fakeroot(self) -> None: Version(Runtime.SINGULARITY, semver.VersionInfo(3, 4, 9)), ) self.assertFalse(client.supports_fakeroot()) + + def inspect_version(self, version: Version, expected: Version) -> None: + self.assertIsInstance(version, Version) + + self.assertIsInstance(version.runtime, Runtime) + self.assertEqual(version.runtime, expected.runtime) + + self.assertIsInstance(version.version, semver.VersionInfo) + self.assertEqual(version.version, expected.version) + + def test_version__init__(self) -> None: + version = Version(Runtime.APPTAINER, semver.VersionInfo(3, 7, 5)) + + self.assertIsInstance(version.runtime, Runtime) + self.assertEqual(version.runtime, Runtime.APPTAINER) + + self.assertIsInstance(version.version, semver.VersionInfo) + self.assertEqual(version.version, semver.VersionInfo(3, 7, 5)) + + def test_version_from_version_string(self) -> None: + self.inspect_version( + Version.from_version_string("singularity version 3.7.4"), + Version(Runtime.SINGULARITY, semver.VersionInfo(3, 7, 4)), + ) + self.inspect_version( + Version.from_version_string("singularity-ce version 3.5.4"), + Version(Runtime.SINGULARITY, semver.VersionInfo(3, 5, 4)), + ) + self.inspect_version( + Version.from_version_string("singularity-ce version 3.11.0-rc.2"), + Version( + Runtime.SINGULARITY, + semver.VersionInfo(3, 11, 0, prerelease="rc.2", build=None), + ), + ) + self.inspect_version( + Version.from_version_string("apptainer version 1.1.9-1.el9"), + Version( + Runtime.APPTAINER, + semver.VersionInfo(1, 1, 9, prerelease="1.el9", build=None), + ), + ) + self.inspect_version( + Version.from_version_string("0.1.3-pull/123-0a5d"), + Version( + Runtime.SINGULARITY, + semver.VersionInfo(0, 1, 3, prerelease="pull", build="123-0a5d"), + ), + ) + self.inspect_version( + Version.from_version_string("1.0.32"), + Version( + Runtime.UNKNOWN, + semver.VersionInfo(1, 0, 32), + ), + )