From d4f637386093dfd67c9669cb31d2f322628b3302 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Mon, 19 Sep 2022 21:39:51 +0100 Subject: [PATCH 01/18] shpc install now does a clean reinstallation --- shpc/client/__init__.py | 2 +- shpc/main/modules/base.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/shpc/client/__init__.py b/shpc/client/__init__.py index 4309ae47e..6fdc01b22 100644 --- a/shpc/client/__init__.py +++ b/shpc/client/__init__.py @@ -125,7 +125,7 @@ def get_parser(): "--force", "-f", dest="force", - help="replace existing symlinks", + help="Go through the installation, deleting old files, replacing existing symlinks, etc.", default=False, action="store_true", ) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index 30b9b0b39..bf0575a0b 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -393,6 +393,24 @@ def install( # We always load overrides for an install module.load_override_file() + # Check previous installations of this module + installed_modules = self._get_module_lookup( + self.settings.module_base, self.modulefile, module.name + ) + if (module.name in installed_modules) and (module.config.tag.name in installed_modules[module.name]): + if not force: + logger.exit( + "%s:%s is already installed. Add --force to proceed with a reinstallation." + % (module.name, module.config.tag.name) + ) + logger.info("%s:%s is already installed. Reinstalling." % (module.name, module.config.tag.name)) + # Don't explicitly remove the container, since we still need it, + # though it may still happen if shpc is configured to store + # containers and modules in the same directory + self._uninstall( + module.module_dir, self.settings.module_base, "$module_base/%s" % module.name + ) + # Create the module and container directory utils.mkdirp([module.module_dir, module.container_dir]) From 7d9aa90e0718458c60239c1abf91a50ca711040d Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Mon, 19 Sep 2022 23:21:31 +0100 Subject: [PATCH 02/18] Updated the tests --- shpc/tests/test_client.py | 40 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/shpc/tests/test_client.py b/shpc/tests/test_client.py index c1b3e31e8..27a1f93dd 100644 --- a/shpc/tests/test_client.py +++ b/shpc/tests/test_client.py @@ -51,8 +51,6 @@ def test_install_get(tmp_path, module_sys, module_file, container_tech, remote): assert client.get("python:3.9.2-alpine") - client.install("python:3.9.2-alpine") - @pytest.mark.parametrize( "module_sys,module_file,remote", @@ -377,3 +375,41 @@ def test_add(tmp_path, module_sys, remote): client.get("dinosaur/salad:latest") client.install("dinosaur/salad:latest") assert client.get("dinosaur/salad:latest") + + +@pytest.mark.parametrize( + "module_sys,module_file,container_tech,remote", + [ + ("lmod", "module.lua", "singularity", False), + ("lmod", "module.lua", "podman", False), + ("tcl", "module.tcl", "singularity", False), + ("tcl", "module.tcl", "podman", False), + ("lmod", "module.lua", "singularity", True), + ("lmod", "module.lua", "podman", True), + ("tcl", "module.tcl", "singularity", True), + ("tcl", "module.tcl", "podman", True), + ], +) +def test_reinstall(tmp_path, module_sys, module_file, container_tech, remote): + """ + Test install and reinstall + """ + client = init_client(str(tmp_path), module_sys, container_tech, remote=remote) + + # Install known tag + client.install("python:3.9.2-alpine") + module_dir = os.path.join(client.settings.module_base, "python", "3.9.2-alpine") + env_file = os.path.join(module_dir, client.settings.environment_file) + dummy = os.path.join(module_dir, "dummy.sh") + # Ensure the content is initially as expected + assert os.path.exists(env_file) + assert not os.path.exists(dummy) + # Modify it + os.unlink(env_file) + shpc.utils.write_file(module_file, "") + assert not os.path.exists(env_file) + assert os.path.exists(dummy) + # The reinstallation should restore everything + client.install("python:3.9.2-alpine", force=True) + assert os.path.exists(env_file) + assert not os.path.exists(dummy) From ea7627b230c4a09f02225ded730541e67f0eb918 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Mon, 19 Sep 2022 11:37:36 +0100 Subject: [PATCH 03/18] Function to find all the modules that can be upgraded --- shpc/main/modules/base.py | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index bf0575a0b..8b0fffec8 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -458,3 +458,47 @@ def view_install(self, view_name, name, force=False, container_image=None): # Don't continue if it exists, unless force is True view.confirm_install(module.module_dir, force=force) view.install(module.module_dir) + + def reinstall_all(self, pattern=False, force=False, **kwargs): + """ + Reinstall (and possibly upgrade) all the current modules, possibly filtered by pattern. + """ + modules = self._get_module_lookup( + self.settings.module_base, self.modulefile, pattern + ) + + # If we don't have modules, exit early + if not modules: + logger.exit("You don't have any install modules. Try shpc show.", 0) + + unavailable_modules = False + for module_name, versions in modules.items(): + result = self.registry.find(module_name) + if result: + valid_tags = container.ContainerConfig(result).tags + new_versions = set() + for version in versions: + if version in valid_tags: + new_versions.add(version) + else: + latest_version = valid_tags.latest.name + if latest_version in versions: + logger.info( + "%s:%s is not available anymore. The latest version, %s, is already installed and will be reinstalled." + % (module_name, version, latest_version) + ) + else: + logger.warning( + "%s:%s is not available anymore. The latest version, %s, will be installed instead." + % (module_name, version, valid_tags.latest.name) + ) + new_versions.add(valid_tags.latest.name) + unavailable_modules = True + else: + logger.warning( + "%s is not available anymore and will be skipped" % module_name + ) + if unavailable_modules and not force: + logger.exit( + "Some modules could not be found. Add --force to go through with the proposed upgrades." + ) From 4ceacf59c07bd5f454b003883871f375f899ac7c Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sat, 1 Oct 2022 22:45:15 +0100 Subject: [PATCH 04/18] Skeleton for the reinstall and upgrade modes --- shpc/client/__init__.py | 30 ++++++++++++++++++++++++++++++ shpc/client/reinstall.py | 37 +++++++++++++++++++++++++++++++++++++ shpc/main/modules/base.py | 28 +++++++++++----------------- 3 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 shpc/client/reinstall.py diff --git a/shpc/client/__init__.py b/shpc/client/__init__.py index 6fdc01b22..d95ef4f5e 100644 --- a/shpc/client/__init__.py +++ b/shpc/client/__init__.py @@ -130,6 +130,33 @@ def get_parser(): action="store_true", ) + # Reinstall already installed recipes + reinstall = subparsers.add_parser( + "reinstall", + description="reinstall a recipe.", + formatter_class=argparse.RawTextHelpFormatter, + ) + reinstall.add_argument( + "reinstall_recipe", + nargs="?", + help="recipe to reinstall\nshpc reinstall python\nshpc reinstall python:3.9.5-alpine", + default=None, + ) + reinstall.add_argument( + "--all", + dest="all", + help="reinstall all currently installed modules.", + action="store_true", + ) + reinstall.add_argument( + "--force", + "-f", + dest="force", + help="Ignore and leave intact the versions that don't exist in the registry anymore.", + default=False, + action="store_true", + ) + # List installed modules listing = subparsers.add_parser("list", description="list installed modules.") listing.add_argument("pattern", help="filter to a pattern", nargs="?") @@ -372,6 +399,7 @@ def get_parser(): inspect, install, listing, + reinstall, shell, test, uninstall, @@ -491,6 +519,8 @@ def help(return_code=0): from .get import main elif args.command == "install": from .install import main + elif args.command == "reinstall": + from .reinstall import main elif args.command == "inspect": from .inspect import main elif args.command == "list": diff --git a/shpc/client/reinstall.py b/shpc/client/reinstall.py new file mode 100644 index 000000000..b9d4a555c --- /dev/null +++ b/shpc/client/reinstall.py @@ -0,0 +1,37 @@ +__author__ = "Vanessa Sochat" +__copyright__ = "Copyright 2021-2022, Vanessa Sochat" +__license__ = "MPL 2.0" + +import shpc.utils +from shpc.logger import logger + + +def main(args, parser, extra, subparser): + + from shpc.main import get_client + + shpc.utils.ensure_no_extra(extra) + + cli = get_client( + quiet=args.quiet, + settings_file=args.settings_file, + module_sys=args.module_sys, + container_tech=args.container_tech, + ) + + # Update config settings on the fly + cli.settings.update_params(args.config_params) + + # It doesn't make sense to give a module name and --all + if args.reinstall_recipe and args.all: + logger.exit("Conflicting arguments reinstall_recipe and --all, choose one.") + # One option must be present + if not args.reinstall_recipe and not args.all: + logger.exit("Missing arguments: provide reinstall_recipe or --all.") + + # And do the reinstall + cli.reinstall( + args.reinstall_recipe, + upgrade=False, + force=args.force, + ) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index 8b0fffec8..aa4f244d4 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -459,7 +459,7 @@ def view_install(self, view_name, name, force=False, container_image=None): view.confirm_install(module.module_dir, force=force) view.install(module.module_dir) - def reinstall_all(self, pattern=False, force=False, **kwargs): + def reinstall(self, pattern, upgrade=False, force=False, **kwargs): """ Reinstall (and possibly upgrade) all the current modules, possibly filtered by pattern. """ @@ -477,28 +477,22 @@ def reinstall_all(self, pattern=False, force=False, **kwargs): if result: valid_tags = container.ContainerConfig(result).tags new_versions = set() - for version in versions: - if version in valid_tags: - new_versions.add(version) - else: - latest_version = valid_tags.latest.name - if latest_version in versions: - logger.info( - "%s:%s is not available anymore. The latest version, %s, is already installed and will be reinstalled." - % (module_name, version, latest_version) - ) + if upgrade: + new_versions.add(valid_tags.latest.name) + else: + for version in versions: + if version in valid_tags: + new_versions.add(version) else: logger.warning( - "%s:%s is not available anymore. The latest version, %s, will be installed instead." - % (module_name, version, valid_tags.latest.name) + "%s:%s is not available anymore and will be skipped" + % (module_name, version) ) - new_versions.add(valid_tags.latest.name) unavailable_modules = True else: logger.warning( "%s is not available anymore and will be skipped" % module_name ) + unavailable_modules = True if unavailable_modules and not force: - logger.exit( - "Some modules could not be found. Add --force to go through with the proposed upgrades." - ) + logger.exit("Some modules could not be found. Add --force to proceed.") From ae97aebf0b0044e2cbd22c60c009de3987b2b1c6 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sat, 1 Oct 2022 23:33:10 +0100 Subject: [PATCH 05/18] First working version --- shpc/main/modules/base.py | 92 ++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 31 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index aa4f244d4..b21dc35a5 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -459,40 +459,70 @@ def view_install(self, view_name, name, force=False, container_image=None): view.confirm_install(module.module_dir, force=force) view.install(module.module_dir) - def reinstall(self, pattern, upgrade=False, force=False, **kwargs): + def reinstall(self, module_name, upgrade=False, force=False): """ Reinstall (and possibly upgrade) all the current modules, possibly filtered by pattern. """ - modules = self._get_module_lookup( - self.settings.module_base, self.modulefile, pattern - ) + if module_name: + tag = None + if ":" in module_name: + module_name, tag = module_name.split(":", 1) + modules = self._get_module_lookup( + self.settings.module_base, self.modulefile, module_name + ) + assert module_name in modules + self._reinstall(module_name, [tag] if tag else modules[module_name], upgrade=upgrade, force=force) + else: + modules = self._get_module_lookup(self.settings.module_base, self.modulefile) + for module_name, versions in modules.items(): + self._reinstall(module_name, versions, upgrade=upgrade, force=force) - # If we don't have modules, exit early - if not modules: - logger.exit("You don't have any install modules. Try shpc show.", 0) + def _reinstall(self, module_name, versions, upgrade=False, force=False): + """ + Reinstall (and possibly upgrade) all the current modules, possibly filtered by pattern. + """ + result = self.registry.find(module_name) + if result: + + + valid_tags = container.ContainerConfig(result).tags + if upgrade: + + views_with_module = set() + for version in versions: + module_dir = os.path.join(self.settings.module_base, module_name, version) + for view_name, entry in self.views.items(): + if entry.exists(module_dir): + views_with_module.add(view_name) + + latest = valid_tags.latest.name + self.install(module_name, tag=latest, view=None, force=True) + for view in views_with_module: + self.views[view].install(module_dir) + + for version in versions: + if version != latest: + self.uninstall(module_name + ":" + version, force=True) - unavailable_modules = False - for module_name, versions in modules.items(): - result = self.registry.find(module_name) - if result: - valid_tags = container.ContainerConfig(result).tags - new_versions = set() - if upgrade: - new_versions.add(valid_tags.latest.name) - else: - for version in versions: - if version in valid_tags: - new_versions.add(version) - else: - logger.warning( - "%s:%s is not available anymore and will be skipped" - % (module_name, version) - ) - unavailable_modules = True else: - logger.warning( - "%s is not available anymore and will be skipped" % module_name - ) - unavailable_modules = True - if unavailable_modules and not force: - logger.exit("Some modules could not be found. Add --force to proceed.") + for version in versions: + if version in valid_tags: + module_dir = os.path.join(self.settings.module_base, module_name, version) + these_views = [] + # TODO: switch to .values() + for view_name, entry in self.views.items(): + if entry.exists(module_dir): + these_views.append(entry) + self.install(module_name, tag=version, view=None, force=True) + for view in these_views: + view.install(module_dir) + + else: + logger.warning( + "%s:%s is not available anymore and will be skipped" + % (module_name, version) + ) + else: + logger.warning( + "%s is not available anymore and will be skipped" % module_name + ) From 197c6d57c7d34a4c6ad4ede17001d2c365d4e6f2 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 16 Oct 2022 03:48:24 +0100 Subject: [PATCH 06/18] Restore initial explanation --- shpc/client/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shpc/client/__init__.py b/shpc/client/__init__.py index d95ef4f5e..2626dde2e 100644 --- a/shpc/client/__init__.py +++ b/shpc/client/__init__.py @@ -125,7 +125,7 @@ def get_parser(): "--force", "-f", dest="force", - help="Go through the installation, deleting old files, replacing existing symlinks, etc.", + help="replace existing symlinks", default=False, action="store_true", ) From 7e90d75169911a348deee007f02be5ccb9457cfb Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 16 Oct 2022 03:49:06 +0100 Subject: [PATCH 07/18] The --force argument is not used for core installations --- shpc/client/install.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shpc/client/install.py b/shpc/client/install.py index 57aca791f..d2d3de563 100644 --- a/shpc/client/install.py +++ b/shpc/client/install.py @@ -27,7 +27,6 @@ def main(args, parser, extra, subparser): # And do the install cli.install( args.install_recipe, - force=args.force, container_image=args.container_image, keep_path=args.keep_path, ) From 11a44936e92b506b50e74b5cd26c0e98c1dc3f90 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 16 Oct 2022 03:50:39 +0100 Subject: [PATCH 08/18] Much simpler implementation --- shpc/main/modules/base.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index b21dc35a5..ab69792f6 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -375,7 +375,7 @@ def get_module(self, name, container_image=None, keep_path=False): return module def install( - self, name, force=False, container_image=None, keep_path=False, **kwargs + self, name, allow_reinstall=False, container_image=None, keep_path=False, **kwargs ): """ Given a unique resource identifier, install a recipe. @@ -383,7 +383,6 @@ def install( For lmod, this means creating a subfolder in modules, pulling the container to it, and writing a module file there. We've already grabbed the name from docker (which is currently the only supported). - "force" is currently not used. """ # Create a new module module = self.get_module( @@ -394,16 +393,10 @@ def install( module.load_override_file() # Check previous installations of this module - installed_modules = self._get_module_lookup( - self.settings.module_base, self.modulefile, module.name - ) - if (module.name in installed_modules) and (module.config.tag.name in installed_modules[module.name]): - if not force: - logger.exit( - "%s:%s is already installed. Add --force to proceed with a reinstallation." - % (module.name, module.config.tag.name) - ) - logger.info("%s:%s is already installed. Reinstalling." % (module.name, module.config.tag.name)) + if os.path.exists(module.module_dir): + if not allow_reinstall: + logger.exit("%s is already installed. Do `shpc reinstall` to proceed with a reinstallation." % module.tagged_name) + logger.info("%s is already installed. Reinstalling." % module.tagged_name) # Don't explicitly remove the container, since we still need it, # though it may still happen if shpc is configured to store # containers and modules in the same directory From aa93eedfe5fa75aceeae53c208f1e11a5e257eff Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 16 Oct 2022 03:52:09 +0100 Subject: [PATCH 09/18] Call install --- shpc/main/modules/base.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index ab69792f6..b9448b109 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -452,23 +452,27 @@ def view_install(self, view_name, name, force=False, container_image=None): view.confirm_install(module.module_dir, force=force) view.install(module.module_dir) - def reinstall(self, module_name, upgrade=False, force=False): + def reinstall(self, module_name, force=False): """ - Reinstall (and possibly upgrade) all the current modules, possibly filtered by pattern. + Reinstall the module, or all modules """ if module_name: - tag = None if ":" in module_name: module_name, tag = module_name.split(":", 1) - modules = self._get_module_lookup( - self.settings.module_base, self.modulefile, module_name - ) - assert module_name in modules - self._reinstall(module_name, [tag] if tag else modules[module_name], upgrade=upgrade, force=force) + self.install(module_name, tag=tag, allow_reinstall=True, force=force) + else: + modules = self._get_module_lookup( + self.settings.module_base, self.modulefile, module_name + ) + if module_name not in modules: + logger.exit("%s is not installed. Nothing to reinstall." % module_name) + for version in modules[module_name]: + self.install(module_name, tag=version, allow_reinstall=True, force=force) else: modules = self._get_module_lookup(self.settings.module_base, self.modulefile) for module_name, versions in modules.items(): - self._reinstall(module_name, versions, upgrade=upgrade, force=force) + for version in versions: + self.install(module_name, tag=version, allow_reinstall=True, force=force) def _reinstall(self, module_name, versions, upgrade=False, force=False): """ From d1cf57d676fab4a0c2bf016573e785aa81028ff3 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 6 Nov 2022 22:49:23 +0000 Subject: [PATCH 10/18] Upgrade the code to the latest pre-commit requirements --- shpc/main/modules/base.py | 42 +++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index b9448b109..bb9ad2dc4 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -375,7 +375,12 @@ def get_module(self, name, container_image=None, keep_path=False): return module def install( - self, name, allow_reinstall=False, container_image=None, keep_path=False, **kwargs + self, + name, + allow_reinstall=False, + container_image=None, + keep_path=False, + **kwargs ): """ Given a unique resource identifier, install a recipe. @@ -395,13 +400,18 @@ def install( # Check previous installations of this module if os.path.exists(module.module_dir): if not allow_reinstall: - logger.exit("%s is already installed. Do `shpc reinstall` to proceed with a reinstallation." % module.tagged_name) + logger.exit( + "%s is already installed. Do `shpc reinstall` to proceed with a reinstallation." + % module.tagged_name + ) logger.info("%s is already installed. Reinstalling." % module.tagged_name) # Don't explicitly remove the container, since we still need it, # though it may still happen if shpc is configured to store # containers and modules in the same directory self._uninstall( - module.module_dir, self.settings.module_base, "$module_base/%s" % module.name + module.module_dir, + self.settings.module_base, + "$module_base/%s" % module.name, ) # Create the module and container directory @@ -465,14 +475,22 @@ def reinstall(self, module_name, force=False): self.settings.module_base, self.modulefile, module_name ) if module_name not in modules: - logger.exit("%s is not installed. Nothing to reinstall." % module_name) + logger.exit( + "%s is not installed. Nothing to reinstall." % module_name + ) for version in modules[module_name]: - self.install(module_name, tag=version, allow_reinstall=True, force=force) + self.install( + module_name, tag=version, allow_reinstall=True, force=force + ) else: - modules = self._get_module_lookup(self.settings.module_base, self.modulefile) + modules = self._get_module_lookup( + self.settings.module_base, self.modulefile + ) for module_name, versions in modules.items(): for version in versions: - self.install(module_name, tag=version, allow_reinstall=True, force=force) + self.install( + module_name, tag=version, allow_reinstall=True, force=force + ) def _reinstall(self, module_name, versions, upgrade=False, force=False): """ @@ -481,13 +499,14 @@ def _reinstall(self, module_name, versions, upgrade=False, force=False): result = self.registry.find(module_name) if result: - valid_tags = container.ContainerConfig(result).tags if upgrade: views_with_module = set() for version in versions: - module_dir = os.path.join(self.settings.module_base, module_name, version) + module_dir = os.path.join( + self.settings.module_base, module_name, version + ) for view_name, entry in self.views.items(): if entry.exists(module_dir): views_with_module.add(view_name) @@ -504,7 +523,9 @@ def _reinstall(self, module_name, versions, upgrade=False, force=False): else: for version in versions: if version in valid_tags: - module_dir = os.path.join(self.settings.module_base, module_name, version) + module_dir = os.path.join( + self.settings.module_base, module_name, version + ) these_views = [] # TODO: switch to .values() for view_name, entry in self.views.items(): @@ -519,6 +540,7 @@ def _reinstall(self, module_name, versions, upgrade=False, force=False): "%s:%s is not available anymore and will be skipped" % (module_name, version) ) + else: logger.warning( "%s is not available anymore and will be skipped" % module_name From b936038189172f6572827007df85a3d44f15f23e Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 6 Nov 2022 22:52:32 +0000 Subject: [PATCH 11/18] Synchronised with the latest version of install --- shpc/main/modules/base.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index bb9ad2dc4..6891deb5a 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -468,8 +468,7 @@ def reinstall(self, module_name, force=False): """ if module_name: if ":" in module_name: - module_name, tag = module_name.split(":", 1) - self.install(module_name, tag=tag, allow_reinstall=True, force=force) + self.install(module_name, allow_reinstall=True) else: modules = self._get_module_lookup( self.settings.module_base, self.modulefile, module_name @@ -479,18 +478,14 @@ def reinstall(self, module_name, force=False): "%s is not installed. Nothing to reinstall." % module_name ) for version in modules[module_name]: - self.install( - module_name, tag=version, allow_reinstall=True, force=force - ) + self.install(module_name + ":" + version, allow_reinstall=True) else: modules = self._get_module_lookup( self.settings.module_base, self.modulefile ) for module_name, versions in modules.items(): for version in versions: - self.install( - module_name, tag=version, allow_reinstall=True, force=force - ) + self.install(module_name + ":" + version, allow_reinstall=True) def _reinstall(self, module_name, versions, upgrade=False, force=False): """ @@ -511,13 +506,12 @@ def _reinstall(self, module_name, versions, upgrade=False, force=False): if entry.exists(module_dir): views_with_module.add(view_name) - latest = valid_tags.latest.name - self.install(module_name, tag=latest, view=None, force=True) + self.install(module_name, allow_reinstall=True) for view in views_with_module: self.views[view].install(module_dir) for version in versions: - if version != latest: + if version != valid_tags.latest.name: self.uninstall(module_name + ":" + version, force=True) else: @@ -531,7 +525,7 @@ def _reinstall(self, module_name, versions, upgrade=False, force=False): for view_name, entry in self.views.items(): if entry.exists(module_dir): these_views.append(entry) - self.install(module_name, tag=version, view=None, force=True) + self.install(module_name + ":" + version, allow_reinstall=True) for view in these_views: view.install(module_dir) From 6173ee66288a30d6e18d29369ede89ba0efb8579 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 6 Nov 2022 23:18:39 +0000 Subject: [PATCH 12/18] Currently not in use --- shpc/client/reinstall.py | 1 - 1 file changed, 1 deletion(-) diff --git a/shpc/client/reinstall.py b/shpc/client/reinstall.py index b9d4a555c..cae9fcb57 100644 --- a/shpc/client/reinstall.py +++ b/shpc/client/reinstall.py @@ -32,6 +32,5 @@ def main(args, parser, extra, subparser): # And do the reinstall cli.reinstall( args.reinstall_recipe, - upgrade=False, force=args.force, ) From 562bb5f45a561dd7c153862fdb3f6036fe110f57 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 6 Nov 2022 23:18:51 +0000 Subject: [PATCH 13/18] More comments --- shpc/main/modules/base.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index 6891deb5a..b909f22c9 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -468,8 +468,10 @@ def reinstall(self, module_name, force=False): """ if module_name: if ":" in module_name: + # Reinstall this one version self.install(module_name, allow_reinstall=True) else: + # Find all the versions currently installed modules = self._get_module_lookup( self.settings.module_base, self.modulefile, module_name ) @@ -477,9 +479,11 @@ def reinstall(self, module_name, force=False): logger.exit( "%s is not installed. Nothing to reinstall." % module_name ) + # Reinstall them one by one for version in modules[module_name]: self.install(module_name + ":" + version, allow_reinstall=True) else: + # Reinstall everything that is currently installed modules = self._get_module_lookup( self.settings.module_base, self.modulefile ) From 8d637f49abdc856f86bb41d67b949fea3c78535c Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Sun, 6 Nov 2022 23:34:27 +0000 Subject: [PATCH 14/18] Completed the bulk of reinstall --- shpc/main/modules/base.py | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index b909f22c9..78de5bf5d 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -462,32 +462,30 @@ def view_install(self, view_name, name, force=False, container_image=None): view.confirm_install(module.module_dir, force=force) view.install(module.module_dir) - def reinstall(self, module_name, force=False): + def reinstall(self, module, force=False): """ Reinstall the module, or all modules """ - if module_name: - if ":" in module_name: - # Reinstall this one version - self.install(module_name, allow_reinstall=True) - else: - # Find all the versions currently installed - modules = self._get_module_lookup( - self.settings.module_base, self.modulefile, module_name - ) - if module_name not in modules: - logger.exit( - "%s is not installed. Nothing to reinstall." % module_name - ) - # Reinstall them one by one - for version in modules[module_name]: - self.install(module_name + ":" + version, allow_reinstall=True) + if module: + module_name, _, version = module.partition(":") + # Find all the versions currently installed + installed_modules = self._get_module_lookup( + self.settings.module_base, self.modulefile, module_name + ) + if (module_name not in installed_modules) or ( + version and version not in installed_modules[module_name] + ): + logger.exit("%s is not installed. Nothing to reinstall." % module) + versions = [version] if version else installed_modules[module_name] + # Reinstall the required version(s) one by one + for version in versions: + self.install(module_name + ":" + version, allow_reinstall=True) else: # Reinstall everything that is currently installed - modules = self._get_module_lookup( + installed_modules = self._get_module_lookup( self.settings.module_base, self.modulefile ) - for module_name, versions in modules.items(): + for module_name, versions in installed_modules.items(): for version in versions: self.install(module_name + ":" + version, allow_reinstall=True) From 0048c1783624d1795d0b8af0604eecbd8ba1dfc9 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Mon, 7 Nov 2022 00:00:01 +0000 Subject: [PATCH 15/18] Not used --- shpc/client/install.py | 1 - shpc/main/modules/base.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/shpc/client/install.py b/shpc/client/install.py index d2d3de563..fd0e65d6d 100644 --- a/shpc/client/install.py +++ b/shpc/client/install.py @@ -35,5 +35,4 @@ def main(args, parser, extra, subparser): cli.settings.default_view, args.install_recipe, force=args.force, - container_image=args.container_image, ) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index 78de5bf5d..c534c92d9 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -442,12 +442,12 @@ def install( logger.info("Module %s was created." % module.tagged_name) return module.container_path - def view_install(self, view_name, name, force=False, container_image=None): + def view_install(self, view_name, name, force=False): """ Install a module in a view. The module must already be installed. Set "force" to True to allow overwriting existing symlinks. """ - module = self.get_module(name, container_image=container_image) + module = self.get_module(name) # A view is a symlink under views_base/$view/$module if view_name not in self.views: From c70eb850160e1be94c03438aa0274f1f0c990641 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Mon, 7 Nov 2022 00:22:46 +0000 Subject: [PATCH 16/18] Implemented --ignore-missing and --uninstall-missing --- shpc/client/__init__.py | 14 +++++++++++--- shpc/client/reinstall.py | 12 +++++++++++- shpc/main/modules/base.py | 28 +++++++++++++++++++++------- 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/shpc/client/__init__.py b/shpc/client/__init__.py index 2626dde2e..6844ea3dc 100644 --- a/shpc/client/__init__.py +++ b/shpc/client/__init__.py @@ -149,13 +149,21 @@ def get_parser(): action="store_true", ) reinstall.add_argument( - "--force", - "-f", - dest="force", + "--ignore-missing", + "-i", + dest="ignore_missing", help="Ignore and leave intact the versions that don't exist in the registry anymore.", default=False, action="store_true", ) + reinstall.add_argument( + "--uninstall-missing", + "-u", + dest="uninstall_missing", + help="Uninstall the versions that don't exist in the registry anymore.", + default=False, + action="store_true", + ) # List installed modules listing = subparsers.add_parser("list", description="list installed modules.") diff --git a/shpc/client/reinstall.py b/shpc/client/reinstall.py index cae9fcb57..d0187ecfa 100644 --- a/shpc/client/reinstall.py +++ b/shpc/client/reinstall.py @@ -28,9 +28,19 @@ def main(args, parser, extra, subparser): # One option must be present if not args.reinstall_recipe and not args.all: logger.exit("Missing arguments: provide reinstall_recipe or --all.") + if args.ignore_missing and args.uninstall_missing: + logger.exit( + "Conflicting arguments --ignore-missing and --uninstall-missing, choose one." + ) # And do the reinstall cli.reinstall( args.reinstall_recipe, - force=args.force, + when_missing=( + "ignore" + if args.ignore_missing + else "uninstall" + if args.uninstall_missing + else None + ), ) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index c534c92d9..44fd95d56 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -462,12 +462,12 @@ def view_install(self, view_name, name, force=False): view.confirm_install(module.module_dir, force=force) view.install(module.module_dir) - def reinstall(self, module, force=False): + def reinstall(self, name, when_missing=None): """ Reinstall the module, or all modules """ - if module: - module_name, _, version = module.partition(":") + if name: + module_name, _, version = name.partition(":") # Find all the versions currently installed installed_modules = self._get_module_lookup( self.settings.module_base, self.modulefile, module_name @@ -475,11 +475,11 @@ def reinstall(self, module, force=False): if (module_name not in installed_modules) or ( version and version not in installed_modules[module_name] ): - logger.exit("%s is not installed. Nothing to reinstall." % module) + logger.exit("%s is not installed. Nothing to reinstall." % name) versions = [version] if version else installed_modules[module_name] # Reinstall the required version(s) one by one for version in versions: - self.install(module_name + ":" + version, allow_reinstall=True) + self._reinstall(module_name, version, when_missing) else: # Reinstall everything that is currently installed installed_modules = self._get_module_lookup( @@ -487,12 +487,26 @@ def reinstall(self, module, force=False): ) for module_name, versions in installed_modules.items(): for version in versions: - self.install(module_name + ":" + version, allow_reinstall=True) + self._reinstall(module_name, version, when_missing) - def _reinstall(self, module_name, versions, upgrade=False, force=False): + def _reinstall(self, module_name, version, when_missing): """ Reinstall (and possibly upgrade) all the current modules, possibly filtered by pattern. """ + config = self.load_registry_config(module_name) + if version in config.tags: + self.install(module_name + ":" + version, allow_reinstall=True) + elif when_missing == "ignore": + pass + elif when_missing == "uninstall": + self.uninstall(module_name + ":" + version, force=True) + else: + logger.exit( + "%s is not in the Registry any more. Add --uninstall-missing or --ignore-missing." + % module_name + ) + + def _upgrade(self, module_name, versions, upgrade=False, force=False): result = self.registry.find(module_name) if result: From a82ff27a2bc0a436cae5e1e6249af047cd9b4699 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Mon, 7 Nov 2022 01:08:55 +0000 Subject: [PATCH 17/18] Do the checks at the tool level as well --- shpc/main/modules/base.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index 44fd95d56..817de3e78 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -493,17 +493,28 @@ def _reinstall(self, module_name, version, when_missing): """ Reinstall (and possibly upgrade) all the current modules, possibly filtered by pattern. """ - config = self.load_registry_config(module_name) - if version in config.tags: - self.install(module_name + ":" + version, allow_reinstall=True) - elif when_missing == "ignore": - pass - elif when_missing == "uninstall": - self.uninstall(module_name + ":" + version, force=True) + result = self.registry.find(module_name) + if result: + config = container.ContainerConfig(result) + if version in config.tags: + return self.install(module_name + ":" + version, allow_reinstall=True) + else: + missing = module_name + ":" + version + else: + missing = module_name + + if when_missing: + if when_missing == "ignore": + logger.info( + "%s is not in the Registry any more. Ignoring as instructed." + % missing + ) + elif when_missing == "uninstall": + self.uninstall(module_name + ":" + version, force=True) else: logger.exit( "%s is not in the Registry any more. Add --uninstall-missing or --ignore-missing." - % module_name + % missing ) def _upgrade(self, module_name, versions, upgrade=False, force=False): From 04b5b3154a511953ab95742af3f6166c1207f625 Mon Sep 17 00:00:00 2001 From: Matthieu Muffato Date: Wed, 9 Nov 2022 04:28:10 +0000 Subject: [PATCH 18/18] Not needed anymore --- shpc/main/modules/base.py | 50 --------------------------------------- 1 file changed, 50 deletions(-) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index 817de3e78..a468799a8 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -516,53 +516,3 @@ def _reinstall(self, module_name, version, when_missing): "%s is not in the Registry any more. Add --uninstall-missing or --ignore-missing." % missing ) - - def _upgrade(self, module_name, versions, upgrade=False, force=False): - result = self.registry.find(module_name) - if result: - - valid_tags = container.ContainerConfig(result).tags - if upgrade: - - views_with_module = set() - for version in versions: - module_dir = os.path.join( - self.settings.module_base, module_name, version - ) - for view_name, entry in self.views.items(): - if entry.exists(module_dir): - views_with_module.add(view_name) - - self.install(module_name, allow_reinstall=True) - for view in views_with_module: - self.views[view].install(module_dir) - - for version in versions: - if version != valid_tags.latest.name: - self.uninstall(module_name + ":" + version, force=True) - - else: - for version in versions: - if version in valid_tags: - module_dir = os.path.join( - self.settings.module_base, module_name, version - ) - these_views = [] - # TODO: switch to .values() - for view_name, entry in self.views.items(): - if entry.exists(module_dir): - these_views.append(entry) - self.install(module_name + ":" + version, allow_reinstall=True) - for view in these_views: - view.install(module_dir) - - else: - logger.warning( - "%s:%s is not available anymore and will be skipped" - % (module_name, version) - ) - - else: - logger.warning( - "%s is not available anymore and will be skipped" % module_name - )