Skip to content

Commit

Permalink
(chocolatey#1054) Ensure backups & beforeModify for dependencies
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
vexx32 committed Feb 28, 2023
1 parent c692c04 commit d0f2070
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,8 @@ public virtual ConcurrentDictionary<string, PackageResult> 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>(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)
{
Expand Down
130 changes: 91 additions & 39 deletions src/chocolatey/infrastructure.app/services/NugetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -446,6 +447,17 @@ public virtual ConcurrentDictionary<string, PackageResult> install_run(Chocolate
uninstallSuccessAction: null,
addUninstallHandler: true);

EventHandler<PackageOperationEventArgs> beforeModifyHandler = (sender, eventArgs) =>
{
// Soundness: any code called here must avoid calling packageManager methods for
// InstallPackage() and UninstallPackage(), or in some other way handle the
// unbounded recursion doing so would create.
backup_and_before_modify(eventArgs.Package, config, beforeModifyAction);
};

packageManager.PackageUninstalling += beforeModifyHandler;
packageManager.PackageInstalling += beforeModifyHandler;

config.start_backup();

foreach (string packageName in packageNames.or_empty_list_if_null())
Expand Down Expand Up @@ -632,6 +644,17 @@ public virtual ConcurrentDictionary<string, PackageResult> upgrade_run(Chocolate
uninstallSuccessAction: null,
addUninstallHandler: false);

EventHandler<PackageOperationEventArgs> beforeModifyHandler = (sender, eventArgs) =>
{
// Soundness: any code called here must avoid calling packageManager methods for
// InstallPackage() and UninstallPackage(), or in some other way handle the
// unbounded recursion doing so would create.
backup_and_before_modify(eventArgs.Package, config, beforeUpgradeAction);
};

packageManager.PackageUninstalling += beforeModifyHandler;
packageManager.PackageInstalling += beforeModifyHandler;

var configIgnoreDependencies = config.IgnoreDependencies;
set_package_names_if_all_is_specified(config, () => { config.IgnoreDependencies = true; });
config.IgnoreDependencies = configIgnoreDependencies;
Expand Down Expand Up @@ -680,7 +703,7 @@ public virtual ConcurrentDictionary<string, PackageResult> 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);
Expand Down Expand Up @@ -845,22 +868,12 @@ public virtual ConcurrentDictionary<string, PackageResult> 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(
() =>
{
backup_existing_package_files(config, installedPackage);
_fileSystem.delete_directory_if_exists(_fileSystem.combine_paths(ApplicationParameters.PackagesLocation, installedPackage.Id), recursive: true);
remove_cache_for_package(config, installedPackage);
},
Expand All @@ -871,6 +884,7 @@ public virtual ConcurrentDictionary<string, PackageResult> upgrade_run(Chocolate
{
packageManager.UpdatePackage(availablePackage, updateDependencies: !config.IgnoreDependencies, allowPrereleaseVersions: config.Prerelease);
}

remove_nuget_cache_for_package(availablePackage);
}
}
Expand Down Expand Up @@ -1104,7 +1118,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)
{
Expand All @@ -1115,8 +1129,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)
Expand Down Expand Up @@ -1287,20 +1299,29 @@ public virtual ConcurrentDictionary<string, PackageResult> uninstall_run(Chocola
var packageUninstalls = new ConcurrentDictionary<string, PackageResult>(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));
},
addUninstallHandler: true);

var loopCount = 0;

packageManager.PackageUninstalling += (s, e) =>
{
// Soundness: any code called here must avoid calling packageManager methods for
// InstallPackage() and UninstallPackage(), or in some other way handle the
// unbounded recursion doing so would create.
var pkg = e.Package;

backup_and_before_modify(pkg, config, beforeUninstallAction);

// TODO: Removal special handling for SxS packages once we hit v2.0.0

// this section fires twice sometimes, like for older packages in a sxs install...
Expand All @@ -1326,8 +1347,8 @@ public virtual ConcurrentDictionary<string, PackageResult> 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));
Expand All @@ -1337,6 +1358,10 @@ public virtual ConcurrentDictionary<string, PackageResult> 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,
Expand Down Expand Up @@ -1495,21 +1520,7 @@ public virtual ConcurrentDictionary<string, PackageResult> 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)
Expand Down Expand Up @@ -1543,6 +1554,47 @@ public virtual ConcurrentDictionary<string, PackageResult> uninstall_run(Chocola
return packageUninstalls;
}

/// <summary>
/// This method should be called before any modifications are made to a package.
/// Typically this should be injected into the IPackageManager's Installing and Uninstalling events
/// in order to handle both installation and uninstallation of dependencies as well as the primary
/// packages.
/// </summary>
/// <param name="package">The package currently being modified. For IPackageManager event handlers, this will be the eventArgs.Package property.</param>
/// <param name="config">The current configuration.</param>
/// <param name="beforeModifyAction">Any action to run before performing backup operations. Typically this is an invocation of the chocolateyBeforeModify script.</param>
private void backup_and_before_modify(IPackage package, ChocolateyConfiguration config, Action<PackageResult> beforeModifyAction)
{
var installDirectory = get_install_directory(config, 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}'", package.Id);

var packageToModify = new PackageResult(package, installDirectory);
beforeModifyAction(packageToModify);
}

"chocolatey".Log().Debug("Backing up package files for '{0}'", package.Id);

backup_existing_package_files(config, package);
}
}

private void backup_existing_package_files(ChocolateyConfiguration config, IPackage package)
{
var packageInformation = _packageInfoService.get_package_information(package);

ensure_package_files_have_compatible_attributes(config, package, packageInformation);
rename_legacy_package_version(config, package, packageInformation);
remove_rollback_directory_if_exists(package.Id);
backup_existing_version(config, package, packageInformation);
remove_shim_directors(config, package, packageInformation);
}

/// <summary>
/// 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.
Expand Down

0 comments on commit d0f2070

Please sign in to comment.