From 5a3bb05dad51378271f0be9923fe8f831956ed0c Mon Sep 17 00:00:00 2001 From: Rain Sallow Date: Mon, 27 Feb 2023 17:06:47 -0500 Subject: [PATCH] (#1054) Ensure backups & beforeModify for dependencies Previously we only ran backups and beforeModify scripts on the target package(s) and not their dependencies. This change should ensure we are _always_ running backups on any existing packages from a NuGet source before we try to modify them in any way. Additionally, this should ensure we can run the beforeModify scripts for ALL dependencies of the target package(s). --- .../services/ChocolateyPackageService.cs | 3 +- .../services/NugetService.cs | 109 +++++++++++------- .../services/TemplateService.cs | 1 + 3 files changed, 73 insertions(+), 40 deletions(-) diff --git a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs index 09ae129488..642ddbcf2c 100644 --- a/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs +++ b/src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs @@ -606,7 +606,8 @@ public virtual ConcurrentDictionary install_run(Chocolate action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install); } - var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action)); + var beforeModifyAction = new Action(packageResult => before_package_modify(packageResult, config)); + var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action, beforeModifyAction)); foreach (var result in results) { diff --git a/src/chocolatey/infrastructure.app/services/NugetService.cs b/src/chocolatey/infrastructure.app/services/NugetService.cs index 01a7fd17d0..e13129c922 100644 --- a/src/chocolatey/infrastructure.app/services/NugetService.cs +++ b/src/chocolatey/infrastructure.app/services/NugetService.cs @@ -24,6 +24,7 @@ namespace chocolatey.infrastructure.app.services using System.Net; using adapters; using chocolatey.infrastructure.app.utility; + using chocolatey.infrastructure.configuration; using commandline; using configuration; using domain; @@ -444,6 +445,7 @@ public virtual ConcurrentDictionary install_run(Chocolate if (continueAction != null) continueAction.Invoke(packageResult); }, uninstallSuccessAction: null, + beforeModifyAction: get_before_modify_handler(config, beforeModifyAction), addUninstallHandler: true); config.start_backup(); @@ -630,6 +632,7 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate if (continueAction != null) continueAction.Invoke(packageResult); }, uninstallSuccessAction: null, + beforeModifyAction: get_before_modify_handler(config, beforeUpgradeAction), addUninstallHandler: false); var configIgnoreDependencies = config.IgnoreDependencies; @@ -680,7 +683,7 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate } else { - var installResults = install_run(config, continueAction); + var installResults = install_run(config, continueAction, beforeUpgradeAction); foreach (var result in installResults) { packageInstalls.GetOrAdd(result.Key, result.Value); @@ -845,17 +848,6 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate packageName, version == null ? null : version.ToString())) { - if (beforeUpgradeAction != null) - { - var currentPackageResult = new PackageResult(installedPackage, get_install_directory(config, installedPackage)); - beforeUpgradeAction(currentPackageResult); - } - - remove_rollback_directory_if_exists(packageName); - ensure_package_files_have_compatible_attributes(config, installedPackage, pkgInfo); - rename_legacy_package_version(config, installedPackage, pkgInfo); - backup_existing_version(config, installedPackage, pkgInfo); - remove_shim_directors(config, installedPackage, pkgInfo); if (config.Force && (installedPackage.Version == availablePackage.Version)) { FaultTolerance.try_catch_with_logging_exception( @@ -871,6 +863,7 @@ public virtual ConcurrentDictionary upgrade_run(Chocolate { packageManager.UpdatePackage(availablePackage, updateDependencies: !config.IgnoreDependencies, allowPrereleaseVersions: config.Prerelease); } + remove_nuget_cache_for_package(availablePackage); } } @@ -910,6 +903,7 @@ public virtual ConcurrentDictionary get_outdated(Chocolat _packageDownloader, installSuccessAction: null, uninstallSuccessAction: null, + beforeModifyAction: null, addUninstallHandler: false); var repository = packageManager.SourceRepository; @@ -1104,7 +1098,7 @@ public virtual void backup_existing_version(ChocolateyConfiguration config, IPac var errored = false; try { - _fileSystem.move_directory(pkgInstallPath, backupLocation); + _fileSystem.copy_directory(pkgInstallPath, backupLocation, overwriteExisting: true); } catch (Exception ex) { @@ -1115,8 +1109,6 @@ public virtual void backup_existing_version(ChocolateyConfiguration config, IPac { try { - _fileSystem.copy_directory(backupLocation, pkgInstallPath, overwriteExisting: true); - remove_packaging_files_prior_to_upgrade(pkgInstallPath, config.CommandName); } catch (Exception ex) @@ -1287,14 +1279,18 @@ public virtual ConcurrentDictionary uninstall_run(Chocola var packageUninstalls = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); SemanticVersion version = config.Version != null ? new SemanticVersion(config.Version) : null; - var packageManager = NugetCommon.GetPackageManager(config, _nugetLogger, _packageDownloader, - installSuccessAction: null, - uninstallSuccessAction: (e) => - { - var pkg = e.Package; - "chocolatey".Log().Info(ChocolateyLoggers.Important, " {0} has been successfully uninstalled.".format_with(pkg.Id)); - }, - addUninstallHandler: true); + var packageManager = NugetCommon.GetPackageManager( + config, + _nugetLogger, + _packageDownloader, + installSuccessAction: null, + uninstallSuccessAction: (e) => + { + var pkg = e.Package; + "chocolatey".Log().Info(ChocolateyLoggers.Important, " {0} has been successfully uninstalled.".format_with(pkg.Id)); + }, + beforeModifyAction: get_before_modify_handler(config, beforeUninstallAction), + addUninstallHandler: true); var loopCount = 0; packageManager.PackageUninstalling += (s, e) => @@ -1326,8 +1322,8 @@ public virtual ConcurrentDictionary uninstall_run(Chocola } // is this the latest version, have you passed --sxs, or is this a side-by-side install? This is the only way you get through to the continue action. - var latestVersion = packageManager.LocalRepository.FindPackage(e.Package.Id); - var pkgInfo = _packageInfoService.get_package_information(e.Package); + var latestVersion = packageManager.LocalRepository.FindPackage(pkg.Id); + var pkgInfo = _packageInfoService.get_package_information(pkg); if (latestVersion.Version == pkg.Version || config.AllowMultipleVersions || (pkgInfo != null && pkgInfo.IsSideBySide)) { packageResult.Messages.Add(new ResultMessage(ResultType.Debug, ApplicationParameters.Messages.ContinueChocolateyAction)); @@ -1337,6 +1333,10 @@ public virtual ConcurrentDictionary uninstall_run(Chocola { //todo: #2578 allow cleaning of pkgstore files } + + ensure_nupkg_is_removed(pkg, pkgInfo); + remove_installation_files(pkg, pkgInfo); + remove_cache_for_package(config, pkg); }; // if we are uninstalling a package and not forcing dependencies, @@ -1495,21 +1495,7 @@ public virtual ConcurrentDictionary uninstall_run(Chocola packageVersion.Id, packageVersion.Version.to_string()) ) { - if (beforeUninstallAction != null) - { - // guessing this is not added so that it doesn't fail the action if an error is recorded? - //var currentPackageResult = packageUninstalls.GetOrAdd(packageName, new PackageResult(packageVersion, get_install_directory(config, packageVersion))); - var currentPackageResult = new PackageResult(packageVersion, get_install_directory(config, packageVersion)); - beforeUninstallAction(currentPackageResult); - } - ensure_package_files_have_compatible_attributes(config, packageVersion, pkgInfo); - rename_legacy_package_version(config, packageVersion, pkgInfo); - remove_rollback_directory_if_exists(packageName); - backup_existing_version(config, packageVersion, pkgInfo); packageManager.UninstallPackage(packageVersion.Id.to_lower(), forceRemove: config.Force, removeDependencies: config.ForceDependencies, version: packageVersion.Version); - ensure_nupkg_is_removed(packageVersion, pkgInfo); - remove_installation_files(packageVersion, pkgInfo); - remove_cache_for_package(config, packageVersion); } } catch (Exception ex) @@ -1543,6 +1529,51 @@ public virtual ConcurrentDictionary uninstall_run(Chocola return packageUninstalls; } + /// + /// Gets a handler action which can be passed into the IPackageManager as the beforeModifyAction, + /// based on a given beforeModifyAction for a package (typically a beforeModify script run action). + /// + /// The current configuration. + /// + /// An action to take on a PackageResult object, typically an invocation of a beforeModify powershell script. + /// This action will NOT be invoked if the package is not already installed, so it is safe to pass in the action + /// even for install_run cases, where it may be needed in the case that dependencies need to be updated during + /// the installation of the new package. + /// + /// + Action get_before_modify_handler(ChocolateyConfiguration config, Action beforeModifyAction) + { + void handler(PackageOperationEventArgs args) + { + var installDirectory = get_install_directory(config, args.Package); + + if (installDirectory != null) + { + // if this is an already installed package we're modifying, ensure we run its beforeModify script and back it up properly. + if (beforeModifyAction != null) + { + "chocolatey".Log().Debug("Running beforeModify step for '{0}'", args.Package.Id); + + var packageToModify = new PackageResult(args.Package, installDirectory); + beforeModifyAction(packageToModify); + } + + var packageInformation = _packageInfoService.get_package_information(args.Package); + + "chocolatey".Log().Debug("Backing up package files for '{0}'", args.Package.Id); + + ensure_package_files_have_compatible_attributes(config, args.Package, packageInformation); + rename_legacy_package_version(config, args.Package, packageInformation); + remove_rollback_directory_if_exists(args.Package.Id); + backup_existing_version(config, args.Package, packageInformation); + remove_shim_directors(config, args.Package, packageInformation); + } + } + + return handler; + } + + /// /// NuGet will happily report a package has been uninstalled, even if it doesn't always remove the nupkg. /// Ensure that the package is deleted or throw an error. diff --git a/src/chocolatey/infrastructure.app/services/TemplateService.cs b/src/chocolatey/infrastructure.app/services/TemplateService.cs index 9e6d2355d2..d422824fc6 100644 --- a/src/chocolatey/infrastructure.app/services/TemplateService.cs +++ b/src/chocolatey/infrastructure.app/services/TemplateService.cs @@ -224,6 +224,7 @@ public void list(ChocolateyConfiguration configuration) new PackageDownloader(), installSuccessAction: null, uninstallSuccessAction: null, + beforeModifyAction: null, addUninstallHandler: false); var templateDirList = _fileSystem.get_directories(ApplicationParameters.TemplatesLocation).ToList();