From 70607f57bfda1f300d4825bf939ccbf6e4f8483d Mon Sep 17 00:00:00 2001 From: Ausbeth Chiemeka Date: Wed, 9 Oct 2024 14:13:20 +0100 Subject: [PATCH] Ausbeth reinstall (#1) * Created reinstall file * Update settings.yml * reinstall functionality * Update __init__.py * Update reinstall.py * Update reinstall.py * Update reinstall.py * cleaning up * Adding boolean operations to uninstall function, and making the its message user friendly * Making sure the module to reinstall exists before reinstalling * fixing user messages * Modifying uninstall function to keep container. Also adding flaggers for reinstall (complete and all) * flagger for complete reinstallation * fixing bug * fixing bug (install) * fixing issue where keep_container is not being recognised * fixing typo * fixing bug why complete was not being recognised * Reinstall all * Changing tcl back to lmod in settings, rename refactoring and simplying code structure * removing unnecessary arguements from reinstall * updating help description * Attempting to keep container when reinstalling * removing boolean return values in uninstall function * adding user message during reinstallation regarding keeping containers * restoring software to view during reinstallation * fixing logger messages * rename * testing error message * fixing error message * checking for invalid recipe * Tests for Reinstall * fixing bug * fixing bug * adding reinstall tests to test_client.sh * testing * testing * Testing only reinstall functionality before adding it to client test * fixing issue * fixing issue * fixing issue * fixing issue * fixing issue * fixing issue * fixing issue * minor refactoring * updating help message * adding author * Implementing Guoying and Matthieu's suggested changes * Performing precommit locally and removing temp file * TODO: fix test case ("lmod", "module.lua", "podman", False, False) for test_reinstall_specific_software_version * fixing linting issue * reverting * Update shpc/client/reinstall.py making comment clearer Co-authored-by: Matthieu Muffato --------- Co-authored-by: Matthieu Muffato --- shpc/client/__init__.py | 31 ++++ shpc/client/help.py | 39 +++++ shpc/client/reinstall.py | 129 ++++++++++++++ shpc/main/client.py | 10 ++ shpc/main/modules/base.py | 19 +- shpc/tests/test_client.py | 357 ++++++++++++++++++++++++++++++++++++++ shpc/tests/test_client.sh | 13 ++ 7 files changed, 590 insertions(+), 8 deletions(-) create mode 100644 shpc/client/reinstall.py diff --git a/shpc/client/__init__.py b/shpc/client/__init__.py index bd7bd2871..2f093d0ae 100644 --- a/shpc/client/__init__.py +++ b/shpc/client/__init__.py @@ -126,6 +126,34 @@ def get_parser(): action="store_true", ) + # Reinstall an installed software + reinstall = subparsers.add_parser( + "reinstall", + description=help.reinstall_description, + formatter_class=argparse.RawTextHelpFormatter, + ) + reinstall.add_argument( + "reinstall_recipe", + help="recipe to reinstall", + nargs="?", + ) + + reinstall.add_argument( + "--update-containers", + "-u", + dest="update_containers", + help="Also reinstall the containers (by default, containers are preserved)", + default=False, + action="store_true", + ) + + reinstall.add_argument( + "--all", + "-a", + help="reinstall all installed software", + action="store_true", + ) + # List installed modules listing = subparsers.add_parser( "list", @@ -397,6 +425,7 @@ def get_parser(): install, listing, remove, + reinstall, shell, test, uninstall, @@ -535,6 +564,8 @@ def help(return_code=0): from .pull import main elif args.command == "remove": from .remove import main + elif args.command == "reinstall": + from .reinstall import main elif args.command == "shell": from .shell import main elif args.command == "show": diff --git a/shpc/client/help.py b/shpc/client/help.py index b8364e476..0c86ff209 100644 --- a/shpc/client/help.py +++ b/shpc/client/help.py @@ -39,6 +39,45 @@ $ shpc install python:3.9.5-alpine """ +reinstall_description = """Reinstall a software. Containers are kept by default + + # Reinstall a specific version of a software + $ shpc reinstall quay.io/biocontainers/samtools:1.20--h50ea8bc_1 + + # Reinstall all versions of a software + $ shpc reinstall quay.io/biocontainers/samtools + + # Reinstall all installed software + $ shpc reinstall --all + OR + $ shpc reinstall -a + + # Completely reinstall a specific version of a software without keeping the container + $ shpc reinstall quay.io/biocontainers/samtools:1.20--h50ea8bc_1 --update_container + OR + $ shpc reinstall quay.io/biocontainers/samtools:1.20--h50ea8bc_1 -u + + # Completely reinstall all versions of a software without keeping the container + $ shpc reinstall quay.io/biocontainers/samtools --update_container + OR + $ shpc reinstall quay.io/biocontainers/samtools -u + + # Completely reinstall all software without keeping the container + $ shpc reinstall --all --update_container + OR + $ shpc reinstall -a -u + + # Invalid arguement combinations: + $ shpc reinstall quay.io/biocontainers/samtools:1.20--h50ea8bc_1 --all + OR + $ shpc reinstall quay.io/biocontainers/samtools:1.20--h50ea8bc_1 -a + + $ shpc reinstall quay.io/biocontainers/samtools --all + OR + $ shpc reinstall quay.io/biocontainers/samtools -a +""" + + listing_description = """List installed modules. # Show installed modules diff --git a/shpc/client/reinstall.py b/shpc/client/reinstall.py new file mode 100644 index 000000000..9d324af3b --- /dev/null +++ b/shpc/client/reinstall.py @@ -0,0 +1,129 @@ +__author__ = "Vanessa Sochat" +__copyright__ = "Copyright 2021-2024, 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) + + # Check if user entered an incomplete command + if not args.reinstall_recipe and not args.all: + subparser.error( + "You must specify a recipe to reinstall or use --all to reinstall all installed software." + ) + + # Reinstall all software + if args.all: + # Check if the user typed an invalid argument combination + if args.reinstall_recipe: + logger.exit( + "You cannot specify a recipe with --all. Use shpc reinstall --all to reinstall all installed software." + ) + + # Check if the user has any software installed + installed_software = cli.list(return_modules=True) + if not installed_software: + logger.exit("You currently don't have any installed software to reinstall.") + + # Reinstall all installed software + print("Reinstalling all installed software...") + for software in installed_software.keys(): + reinstall(software, cli, args, update_containers=args.update_containers) + logger.info("All software reinstalled.") + + # Reinstall a specific software + else: + # Add namespace + name = cli.add_namespace(args.reinstall_recipe) + + # Reinstall the software + reinstall(name, cli, args, update_containers=args.update_containers) + + +def reinstall(name, cli, args, update_containers=False): + """ + Reinstall a specific version or all versions of a software. + """ + # Check if the provided recipe is known in any registry + try: + cli._load_container(name) + except SystemExit: + # Give additional messages relating to shpc reinstall, to the original exit message in _load_container function + logger.exit( + "This means it cannot be reinstalled because it is not installed, and cannot be installed because it is not known in any registry.\nPlease check the name or try a different recipe." + ) + + # Check if the software or version is installed + installed_versions = cli.list(return_modules=True).get(name.split(":")[0], []) + if not installed_versions: + logger.exit( + f"You currently don't have '{name}' installed.\nTry: shpc install", 0 + ) + + # Determine if a specific version is requested + specific_version = ":" in name + if specific_version and name.split(":")[1] not in installed_versions: + logger.exit( + f"You currently don't have '{name}' installed.\nTry: shpc install", 0 + ) + + # Handle reinstallation logic + if specific_version: + print(f"Reinstalling {name}...") + reinstall_version(name, cli, args, update_containers) + logger.info(f"Successfully reinstalled {name}.") + else: + print(f"Reinstalling all versions of {name}...") + for version in installed_versions: + version_name = f"{name}:{version}" + reinstall_version(version_name, cli, args, update_containers) + logger.info(f"Successfully reinstalled all versions of {name}.") + + +def reinstall_version(name, cli, args, update_containers): + """ + Sub-function to handle the actual reinstallation + """ + # Get the list of views the software was in + views_with_module = set() + views_dir = cli.new_module(name).module_dir + for view_name, entry in cli.views.items(): + if entry.exists(views_dir): + views_with_module.add(view_name) + + # Uninstallation process. By default, uninstall without prompting the user and keep the container except the user wants a complete reinstall + cli.uninstall(name, force=True, keep_container=not update_containers) + + # Display a helpful message to the user about the state of the container during reinstall process + if not update_containers: + print( + "Container was successfully preserved, module files and wrapper scripts will be overwritten..." + ) + else: + print("No container was preserved, all files will be overwritten...") + + # Installation process + cli.install(name) + + # Restore the software to the captured views + print( + f"Restoring {name} to the views it was uninstalled from during reinstallation" + ) + for view_name in views_with_module: + cli.view_install(view_name, name) + logger.info(f"Restored {name} to view: {view_name}") diff --git a/shpc/main/client.py b/shpc/main/client.py index 7ef602b92..565daa723 100644 --- a/shpc/main/client.py +++ b/shpc/main/client.py @@ -62,6 +62,16 @@ def install(self, name, tag=None, **kwargs): """ raise NotImplementedError + def reinstall(self, name, update_containers=False): + """ + Reinstall an installed software + """ + from shpc.client.reinstall import reinstall + + cli = self + args = {} + reinstall(name, cli, args, update_containers=update_containers) + def uninstall(self, name, tag=None): """ Uninstall must also implemented by the subclass (e.g., lmod) diff --git a/shpc/main/modules/base.py b/shpc/main/modules/base.py index fa26ba987..4f998b361 100644 --- a/shpc/main/modules/base.py +++ b/shpc/main/modules/base.py @@ -85,7 +85,7 @@ def view_uninstall(self, view, name, force=False): logger.exit("View %s does not exist, cannot uninstall." % view) return self.views[view].uninstall(module.module_dir) - def uninstall(self, name, force=False): + def uninstall(self, name, force=False, keep_container=False): """ Given a unique resource identifier, uninstall a module. Set "force" to True to bypass the confirmation prompt. @@ -100,7 +100,7 @@ def uninstall(self, name, force=False): # Ask before deleting anything! if not force: - msg = name + "?" + msg = "Do you wish to uninstall " + name + "?" if views_with_module: msg += ( "\nThis will uninstall the module from views:\n %s\nAre you sure?" @@ -110,14 +110,17 @@ def uninstall(self, name, force=False): return # Podman needs image deletion - self.container.delete(module.name) + if not keep_container: # For reinstall + self.container.delete(module.name) if module.container_dir != module.module_dir: - self._uninstall( - module.container_dir, - self.container_base, - "$container_base/%s" % module.name, - ) + if not keep_container: + self._uninstall( + module.container_dir, + self.container_base, + "$container_base/%s" % module.name, + ) + self._uninstall( module.module_dir, self.settings.module_base, diff --git a/shpc/tests/test_client.py b/shpc/tests/test_client.py index 6cdfc85cf..203390419 100644 --- a/shpc/tests/test_client.py +++ b/shpc/tests/test_client.py @@ -12,6 +12,7 @@ import pytest +import shpc.main.modules.views as views import shpc.main.registry as registry import shpc.utils @@ -410,3 +411,359 @@ def test_remove(tmp_path): # Remove the module (with force) client.remove(module, force=True) assert client.registry.exists(module) is None + + +@pytest.mark.parametrize( + "module_sys,module_file,container_tech,remote,update_containers", + [ + ("lmod", "module.lua", "singularity", False, False), + ("lmod", "module.lua", "podman", False, False), + ("tcl", "module.tcl", "singularity", False, False), + ("tcl", "module.tcl", "podman", False, False), + ("lmod", "module.lua", "singularity", True, False), + ("lmod", "module.lua", "podman", True, False), + ("tcl", "module.tcl", "singularity", True, False), + ("tcl", "module.tcl", "podman", True, False), + ("lmod", "module.lua", "singularity", False, True), + ("lmod", "module.lua", "podman", False, True), + ("tcl", "module.tcl", "singularity", False, True), + ("tcl", "module.tcl", "podman", False, True), + ("lmod", "module.lua", "singularity", True, True), + ("lmod", "module.lua", "podman", True, True), + ("tcl", "module.tcl", "singularity", True, True), + ("tcl", "module.tcl", "podman", True, True), + ], +) +def test_reinstall_specific_software_version( + tmp_path, module_sys, module_file, container_tech, remote, update_containers +): + """ + Test reinstalling a specific version of a software. + """ + client = init_client(str(tmp_path), module_sys, container_tech, remote=remote) + + # Install a specific version of a software + client.install("quay.io/biocontainers/samtools:1.20--h50ea8bc_0") + + # Create the default view if it doesn't exist + view_handler = views.ViewsHandler( + settings_file=client.settings.settings_file, module_sys=module_sys + ) + assert "mpi" not in client.views + view_handler.create("mpi") + client.detect_views() + assert "mpi" in client.views + view = client.views["mpi"] + assert view.path == os.path.join(tmp_path, "views", "mpi") and os.path.exists( + view.path + ) + assert os.path.exists(view.config_path) + assert view._config["view"]["name"] == "mpi" + assert not view._config["view"]["modules"] + assert not view._config["view"]["system_modules"] + + # Install the specific version to a view + client.view_install("mpi", "quay.io/biocontainers/samtools:1.20--h50ea8bc_0") + + # Verify its container's existence + container_dir = os.path.join( + client.settings.container_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_0", + ) + assert os.path.exists(container_dir) + + # Get modification time of its container before reinstall + container_mtime_before = os.path.getmtime(container_dir) + + # Reinstall the specific version + client.reinstall( + "quay.io/biocontainers/samtools:1.20--h50ea8bc_0", + update_containers=update_containers, + ) + + # Verify that it was reinstalled + module_dir = os.path.join( + client.settings.module_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_0", + ) + assert os.path.exists(module_dir) + + # Verify that its module files were reinstalled + module_file_path = os.path.join(module_dir, module_file) + assert os.path.exists(module_file_path) + + # Get modification time of the container after reinstall + container_mtime_after = os.path.getmtime(container_dir) + + # Verify that its container was preserved or updated depending on update_container flag + if update_containers: + assert ( + container_mtime_after > container_mtime_before + ), "Container should be updated when update_containers=True." + else: + assert ( + container_mtime_after == container_mtime_before + ), "Container should be preserved when update_containers=False." + + # Check if it was restored to its views + for view_name in client.views.keys(): + assert client.views[view_name].exists( + module_dir + ), f"Software was not restored to view: {view_name}" + + +@pytest.mark.parametrize( + "module_sys,module_file,container_tech,remote,update_containers", + [ + ("lmod", "module.lua", "singularity", False, False), + ("lmod", "module.lua", "podman", False, False), + ("tcl", "module.tcl", "singularity", False, False), + ("tcl", "module.tcl", "podman", False, False), + ("lmod", "module.lua", "singularity", True, False), + ("lmod", "module.lua", "podman", True, False), + ("tcl", "module.tcl", "singularity", True, False), + ("tcl", "module.tcl", "podman", True, False), + ("lmod", "module.lua", "singularity", False, True), + ("lmod", "module.lua", "podman", False, True), + ("tcl", "module.tcl", "singularity", False, True), + ("tcl", "module.tcl", "podman", False, True), + ("lmod", "module.lua", "singularity", True, True), + ("lmod", "module.lua", "podman", True, True), + ("tcl", "module.tcl", "singularity", True, True), + ("tcl", "module.tcl", "podman", True, True), + ], +) +def test_reinstall_all_software_versions( + tmp_path, module_sys, module_file, container_tech, remote, update_containers +): + """ + Test reinstalling all versions of a specific software. + """ + client = init_client(str(tmp_path), module_sys, container_tech, remote=remote) + + # Install two versions of a software + client.install("quay.io/biocontainers/samtools:1.20--h50ea8bc_0") + client.install("quay.io/biocontainers/samtools:1.20--h50ea8bc_1") + + # Create the default view if it doesn't exist + view_handler = views.ViewsHandler( + settings_file=client.settings.settings_file, module_sys=module_sys + ) + assert "mpi" not in client.views + view_handler.create("mpi") + client.detect_views() + assert "mpi" in client.views + view = client.views["mpi"] + assert view.path == os.path.join(tmp_path, "views", "mpi") and os.path.exists( + view.path + ) + assert os.path.exists(view.config_path) + assert view._config["view"]["name"] == "mpi" + assert not view._config["view"]["modules"] + assert not view._config["view"]["system_modules"] + + # Install both versions to a view + client.view_install("mpi", "quay.io/biocontainers/samtools:1.20--h50ea8bc_0") + client.view_install("mpi", "quay.io/biocontainers/samtools:1.20--h50ea8bc_1") + + # Verify their container's existence + container_0_dir = os.path.join( + client.settings.container_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_0", + ) + container_1_dir = os.path.join( + client.settings.container_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_1", + ) + assert os.path.exists(container_0_dir) + assert os.path.exists(container_1_dir) + + # Get modification time of their container before reinstall + container_0_mtime_before = os.path.getmtime(container_0_dir) + container_1_mtime_before = os.path.getmtime(container_1_dir) + + # Reinstall all versions of the specific software + client.reinstall( + "quay.io/biocontainers/samtools", update_containers=update_containers + ) + + # Verify that both versions exist after reinstall + module_0_dir = os.path.join( + client.settings.module_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_0", + ) + module_1_dir = os.path.join( + client.settings.module_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_1", + ) + assert os.path.exists(module_0_dir) + assert os.path.exists(module_1_dir) + + # Verify if their module files were reinstalled + module_0_file_path = os.path.join(module_0_dir, module_file) + module_1_file_path = os.path.join(module_1_dir, module_file) + assert os.path.exists(module_0_file_path) + assert os.path.exists(module_1_file_path) + + # Get modification time of their container after reinstall + container_0_mtime_after = os.path.getmtime(container_0_dir) + container_1_mtime_after = os.path.getmtime(container_1_dir) + + # Verify that their containers were preserved or updated depending on update_containers + if update_containers: + assert ( + container_0_mtime_after > container_0_mtime_before + ), "Container should be updated when update_containers=True." + assert ( + container_1_mtime_after > container_1_mtime_before + ), "Container should be updated when update_containers=True." + else: + assert ( + container_0_mtime_after == container_0_mtime_before + ), "Container should be preserved when update_containers=False." + assert ( + container_1_mtime_after == container_1_mtime_before + ), "Container should be preserved when update_containers=False." + + # Check if both versions were restored to their views + for view_name in client.views.keys(): + assert client.views[view_name].exists( + module_0_dir + ), f"Software was not restored to view: {view_name}" + for view_name in client.views.keys(): + assert client.views[view_name].exists( + module_1_dir + ), f"Software was not restored to view: {view_name}" + + +@pytest.mark.parametrize( + "module_sys,module_file,container_tech,remote,update_containers", + [ + ("lmod", "module.lua", "singularity", False, False), + ("lmod", "module.lua", "podman", False, False), + ("tcl", "module.tcl", "singularity", False, False), + ("tcl", "module.tcl", "podman", False, False), + ("lmod", "module.lua", "singularity", True, False), + ("lmod", "module.lua", "podman", True, False), + ("tcl", "module.tcl", "singularity", True, False), + ("tcl", "module.tcl", "podman", True, False), + ("lmod", "module.lua", "singularity", False, True), + ("lmod", "module.lua", "podman", False, True), + ("tcl", "module.tcl", "singularity", False, True), + ("tcl", "module.tcl", "podman", False, True), + ("lmod", "module.lua", "singularity", True, True), + ("lmod", "module.lua", "podman", True, True), + ("tcl", "module.tcl", "singularity", True, True), + ("tcl", "module.tcl", "podman", True, True), + ], +) +def test_reinstall_all_software( + tmp_path, module_sys, module_file, container_tech, remote, update_containers +): + """ + Test reinstalling all installed software. + """ + client = init_client(str(tmp_path), module_sys, container_tech, remote=remote) + + # Install two different software + client.install("quay.io/biocontainers/samtools:1.20--h50ea8bc_0") + client.install("quay.io/biocontainers/bwa:0.7.18--he4a0461_0") + + # Create the default view if it doesn't exist + view_handler = views.ViewsHandler( + settings_file=client.settings.settings_file, module_sys=module_sys + ) + assert "mpi" not in client.views + view_handler.create("mpi") + client.detect_views() + assert "mpi" in client.views + view = client.views["mpi"] + assert view.path == os.path.join(tmp_path, "views", "mpi") and os.path.exists( + view.path + ) + assert os.path.exists(view.config_path) + assert view._config["view"]["name"] == "mpi" + assert not view._config["view"]["modules"] + assert not view._config["view"]["system_modules"] + + # Install both software to a view + client.view_install("mpi", "quay.io/biocontainers/samtools:1.20--h50ea8bc_0") + client.view_install("mpi", "quay.io/biocontainers/bwa:0.7.18--he4a0461_0") + + # Verify the existence of their containers + container_samtools_dir = os.path.join( + client.settings.container_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_0", + ) + container_bwa_dir = os.path.join( + client.settings.container_base, + "quay.io/biocontainers/bwa", + "0.7.18--he4a0461_0", + ) + assert os.path.exists(container_samtools_dir) + assert os.path.exists(container_bwa_dir) + + # Get modification time of their container before reinstall + container_samtools_mtime_before = os.path.getmtime(container_samtools_dir) + container_bwa_mtime_before = os.path.getmtime(container_bwa_dir) + + # Reinstall all software + installed_software = client.list(return_modules=True) + for software in installed_software.keys(): + client.reinstall(software, update_containers=update_containers) + + # Verify that both modules exist after reinstall + module_samtools_dir = os.path.join( + client.settings.module_base, + "quay.io/biocontainers/samtools", + "1.20--h50ea8bc_0", + ) + module_bwa_dir = os.path.join( + client.settings.module_base, "quay.io/biocontainers/bwa", "0.7.18--he4a0461_0" + ) + assert os.path.exists(module_samtools_dir) + assert os.path.exists(module_bwa_dir) + + # Verify if their module files were reinstalled + module_samtools_file_path = os.path.join(module_samtools_dir, module_file) + module_bwa_file_path = os.path.join(module_bwa_dir, module_file) + assert os.path.exists(module_samtools_file_path) + assert os.path.exists(module_bwa_file_path) + + # Get modification time of their container after reinstall + container_samtools_mtime_after = os.path.getmtime(container_samtools_dir) + container_bwa_mtime_after = os.path.getmtime(container_bwa_dir) + + # Verify that their containers were preserved or updated depending on update_containers + if update_containers: + assert ( + container_samtools_mtime_after > container_samtools_mtime_before + ), "Container should be updated when update_containers=True." + assert ( + container_bwa_mtime_after > container_bwa_mtime_before + ), "Container should be updated when update_containers=True." + + else: + assert ( + container_samtools_mtime_after == container_samtools_mtime_before + ), "Container should be preserved when update_containers=False." + assert ( + container_bwa_mtime_after == container_bwa_mtime_before + ), "Container should be preserved when update_containers=False." + + # Check if both software were restored to their views + for view_name in client.views.keys(): + assert client.views[view_name].exists( + module_samtools_dir + ), f"Software was not restored to view: {view_name}" + for view_name in client.views.keys(): + assert client.views[view_name].exists( + module_bwa_dir + ), f"Software was not restored to view: {view_name}" diff --git a/shpc/tests/test_client.sh b/shpc/tests/test_client.sh index c7db9ece2..9290c071d 100755 --- a/shpc/tests/test_client.sh +++ b/shpc/tests/test_client.sh @@ -49,6 +49,19 @@ runTest 0 $output shpc --settings-file $settings show --help runTest 0 $output shpc --settings-file $settings show runTest 0 $output shpc --settings-file $settings show python +echo +echo "#### Testing reinstall " +runTest 0 $output shpc --settings-file $settings reinstall --help +runTest 0 $output shpc --settings-file $settings install quay.io/biocontainers/samtools:1.20--h50ea8bc_0 +runTest 0 $output shpc --settings-file $settings install quay.io/biocontainers/samtools:1.20--h50ea8bc_1 +runTest 0 $output shpc --settings-file $settings install quay.io/biocontainers/bwa:0.7.18--he4a0461_0 +runTest 0 $output shpc --settings-file $settings reinstall quay.io/biocontainers/samtools:1.20--h50ea8bc_0 +runTest 0 $output shpc --settings-file $settings reinstall quay.io/biocontainers/samtools:1.20--h50ea8bc_0 --update-containers +runTest 0 $output shpc --settings-file $settings reinstall quay.io/biocontainers/samtools +runTest 0 $output shpc --settings-file $settings reinstall quay.io/biocontainers/samtools --update-containers +runTest 0 $output shpc --settings-file $settings reinstall --all +runTest 0 $output shpc --settings-file $settings reinstall --all --update-containers + echo echo "#### Testing add local " runTest 0 $output shpc --settings-file $settings add --help