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

SHPC New Feature - Upgrade Functionality #673

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
10 changes: 9 additions & 1 deletion shpc/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ def get_parser():
action="store_true",
)

install.add_argument(
"--upgrade",
"-u",
help="Check if the latest version of a software is available and install it if not installed.",
dest="upgrade",
action="store_true",
)

# List installed modules
listing = subparsers.add_parser(
"list",
Expand Down Expand Up @@ -377,7 +385,7 @@ def get_parser():
action="store_true",
)

for command in update, sync:
for command in update, install, sync:
command.add_argument(
"--dry-run",
"-d",
Expand Down
13 changes: 12 additions & 1 deletion shpc/client/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# Remove from the list
$ shpc -c rm:registry:/tmp/registry"""

install_description = """Install a registry recipe.
install_description = """Install a registry recipe or upgrade an installed software.

$ Install latest version
$ shpc install python
Expand All @@ -37,6 +37,17 @@

# Install a specific version from that set
$ shpc install python:3.9.5-alpine

# Upgrade a software to its latest version and give option to uninstall older versions or not.
# Do not include the version in the command
$ shpc install python --upgrade
OR
$ shpc install python -u

# Perform dry-run on a software to check if the latest is installed or not without upgrading it.
$ shpc install python --upgrade --dry-run
OR
$ shpc install python -u -d
"""

listing_description = """List installed modules.
Expand Down
130 changes: 120 additions & 10 deletions shpc/client/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
__license__ = "MPL 2.0"

import shpc.utils
from shpc.logger import logger


def main(args, parser, extra, subparser):
Expand All @@ -23,17 +24,126 @@ def main(args, parser, extra, subparser):
# Update config settings on the fly
cli.settings.update_params(args.config_params)

# And do the install
cli.install(
args.install_recipe,
force=args.force,
container_image=args.container_image,
keep_path=args.keep_path,
)
if cli.settings.default_view and not args.no_view:
cli.view_install(
cli.settings.default_view,
# Get the list of the user's installed software
installed_software = cli.list(return_modules=True)

# Upgrade a specific installed software
if args.upgrade:
# Check if the user specified a version
if ":" in args.install_recipe:
logger.exit(
"Please do not include the software version when using --upgrade argument."
)
# Check if the specific software is installed
if args.install_recipe not in installed_software:
logger.exit(
f"You cannot carry out an upgrade on {args.install_recipe} because you do not have it installed.\nInstall it first before attempting an upgrade.",
0,
)

# Does the user just want a dry-run?
if args.dryrun:
version_info = upgrade(
args.install_recipe, cli, args, dryrun=True
) # This returns the latest version if its available, else returns None
if version_info:
logger.info(
f"You do not have the latest version installed.\nLatest version avaiable is {version_info}"
)
else:
logger.info(
f"You have the latest version of {args.install_recipe} installed."
)

# Upgade the software
else:
upgrade(
args.install_recipe,
cli,
args,
dryrun=False,
)

# Install a new software
else:
cli.install(
args.install_recipe,
force=args.force,
container_image=args.container_image,
keep_path=args.keep_path,
)
if cli.settings.default_view and not args.no_view:
cli.view_install(
cli.settings.default_view,
args.install_recipe,
force=args.force,
container_image=args.container_image,
)


def upgrade(name, cli, args, dryrun=False):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This (to me) in how it's designed here looks like it better belongs in an associated helper script. It's not a core part of shpc, it's written as a kind of helper function that uses the client.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @vsoch, thank you for the response. Could you please shed more light on this? I assume you're saying upgrade() should be defined in a different script rather than in install.py?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't think I'm convinced this belongs in core shpc. The implementation here reflects that - it's a big block of code that uses shpc and would better serve as a supplementary helper script.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'm not disagreeing with your suggestion, I just wanted to understand your thought process more in order to know the next steps.
From your suggestion, where should the upgrade function be placed, just so we can all be on the same page @vsoch

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

@Ausbeth Ausbeth Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great thanks, will work on that.

Should I also add def get_latest_version(name, config), which I added to install.py for this upgrade feature in the new subdirectory? @vsoch

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @vsoch . If the functionality moves to a separate script outside of shpc install, then no need to name the option "install --upgrade", right ? We can change the name back to "shpc-upgrade.py".

@Ausbeth : compared to where things were in sanger-tol#3, you would need to:

Probably easier to start from 081dc2d rather than the current state of the branch

"""
Upgrade a software to its latest version. Or preview available upgrades from the user's software list
"""
# Add namespace
name = cli.add_namespace(name)

# Load the container configuration for the specified recipe
config = cli._load_container(name)

# Store the installed versions and the latest version tag
installed_versions = cli.list(pattern=name, return_modules=True)
latest_version_tag = get_latest_version(name, config)

# Compare the latest version with the user's installed version
if any(latest_version_tag in versions for versions in installed_versions.values()):
if not dryrun:
logger.info(f"You have the latest version of {name} installed already")
return None # No upgrade necessary

else:
if dryrun:
return (
latest_version_tag # Return the latest version for upgrade information
)
print(f"Upgrading {name} to its latest version. Version {latest_version_tag}")

# Get the list of views the software was in
views_with_module = set()
view_dir = cli.new_module(name).module_dir
for view_name, entry in cli.views.items():
if entry.exists(view_dir):
views_with_module.add(view_name)

# Ask if the user wants to unintall old versions
if not cli.uninstall(name):
logger.info(f"Old versions of {name} were preserved")

# Install the latest version
cli.install(name)

# Install the latest version to views where the outdated version was found
if views_with_module:
msg = f"Do you also want to install the latest version of {name} to the view(s) of the previous version(s)?"
if shpc.utils.confirm_action(msg):
for view_name in views_with_module:
cli.view_install(view_name, name)
logger.info(
f"Installed the latest version of {name} to view: {view_name}"
)

return latest_version_tag # Upgrade occured


def get_latest_version(name, config):
"""
Given an added namespace of a recipe and a loaded container configuration of that namespace,
Retrieve the latest version tag.
"""
latest_version_info = config.get("latest")
if not latest_version_info:
logger.exit(f"No latest version found for {name}")

# Extract the latest version tag
latest_version_tag = list(latest_version_info.keys())[0]
return latest_version_tag
1 change: 0 additions & 1 deletion shpc/main/container/update/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@


class DockerImage:

"""
A thin client for getting metadata about an image.
"""
Expand Down
6 changes: 4 additions & 2 deletions shpc/main/modules/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,14 @@ 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?"
% "\n ".join(views_with_module)
)
if not utils.confirm_action(msg, force):
return
return False # If the user does not want to uninstall

# Podman needs image deletion
self.container.delete(module.name)
Expand Down Expand Up @@ -149,6 +149,8 @@ def uninstall(self, name, force=False):
if os.path.exists(module_dir):
self.versionfile.write(module_dir)

return True # Denoting successful uninstallation

def _uninstall(self, path, base_path, name):
"""
Sub function, so we can pass more than one folder from uninstall
Expand Down