Skip to content

Commit

Permalink
(chocolateyGH-1038) Add ability to stop operation on reboot
Browse files Browse the repository at this point in the history
There are now parameters and a feature flag which will allow
halting/overriding the install/upgrade/uninstall of a package, when
a reboot request is returned from one of it's dependencies.

When this occurs, an ApplicationException will be thrown, along with
a specific exit code, either 350 (pending reboot discovered prior to
running), or 1604 (some work completed prior to restart request) will be
returned.  This could then be inspected to decide when a reboot should
actually be performed, before continuing with the remainder of the
installation.

The initial implementation of this ability followed closely to how the
stoponfirstfailure parameter and feature flag are implemented, and then
subsequent changes added to the implementation.
  • Loading branch information
gep13 committed Mar 6, 2019
1 parent 5c87ab7 commit 519fd25
Show file tree
Hide file tree
Showing 18 changed files with 622 additions and 17 deletions.
2 changes: 1 addition & 1 deletion src/chocolatey.console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ private static void Main(string[] args)
"chocolatey".Log().Error(ChocolateyLoggers.Important, () => "{0}".format_with(ex.Message));
}

Environment.ExitCode = 1;
if (Environment.ExitCode == 0) Environment.ExitCode = 1;
}
finally
{
Expand Down
7 changes: 7 additions & 0 deletions src/chocolatey/chocolatey.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
</Compile>
<Compile Include="AssemblyExtensions.cs" />
<Compile Include="infrastructure.app\commands\ChocolateyInfoCommand.cs" />
<Compile Include="infrastructure.app\validations\GlobalConfigurationValidation.cs" />
<Compile Include="infrastructure.app\configuration\EnvironmentSettings.cs" />
<Compile Include="infrastructure.app\domain\GenericRegistryKey.cs" />
<Compile Include="infrastructure.app\domain\GenericRegistryValue.cs" />
Expand All @@ -120,10 +121,13 @@
<Compile Include="infrastructure.app\domain\RegistryValueExtensions.cs" />
<Compile Include="infrastructure.app\domain\RegistryValueKindType.cs" />
<Compile Include="infrastructure.app\events\HandlePackageResultCompletedMessage.cs" />
<Compile Include="infrastructure.app\services\IPendingRebootService.cs" />
<Compile Include="infrastructure.app\services\PendingRebootService.cs" />
<Compile Include="infrastructure.app\templates\ChocolateyTodoTemplate.cs" />
<Compile Include="infrastructure.app\utility\ArgumentsUtility.cs" />
<Compile Include="infrastructure.app\utility\HashCode.cs" />
<Compile Include="infrastructure.app\utility\PackageUtility.cs" />
<Compile Include="infrastructure.app\validations\SystemStateValidation.cs" />
<Compile Include="infrastructure\filesystem\FileSystem.cs" />
<Compile Include="infrastructure\logging\AggregateLog.cs" />
<Compile Include="infrastructure\logging\LogLevelType.cs" />
Expand Down Expand Up @@ -317,6 +321,9 @@
<Compile Include="infrastructure\tokens\TokenReplacer.cs" />
<Compile Include="ILogExtensions.cs" />
<Compile Include="infrastructure\tolerance\FaultTolerance.cs" />
<Compile Include="infrastructure\validations\IValidation.cs" />
<Compile Include="infrastructure\validations\ValidationResult.cs" />
<Compile Include="infrastructure\validations\ValidationStatus.cs" />
<Compile Include="infrastructure\xml\XmlCData.cs" />
<Compile Include="LogExtensions.cs" />
<Compile Include="ObjectExtensions.cs" />
Expand Down
1 change: 1 addition & 0 deletions src/chocolatey/infrastructure.app/ApplicationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ public static class Features
public static readonly string IgnoreUnfoundPackagesOnUpgradeOutdated = "ignoreUnfoundPackagesOnUpgradeOutdated";
public static readonly string RemovePackageInformationOnUninstall = "removePackageInformationOnUninstall";
public static readonly string LogWithoutColor = "logWithoutColor";
public static readonly string ExitOnRebootDetected = "exitOnRebootDetected";
}

public static class Messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ private static void set_feature_flags(ChocolateyConfiguration config, ConfigFile

config.Features.ScriptsCheckLastExitCode = set_feature_flag(ApplicationParameters.Features.ScriptsCheckLastExitCode, configFileSettings, defaultEnabled: false, description: "Scripts Check $LastExitCode (external commands) - Leave this off unless you absolutely need it while you fix your package scripts to use `throw 'error message'` or `Set-PowerShellExitCode #` instead of `exit #`. This behavior started in 0.9.10 and produced hard to find bugs. If the last external process exits successfully but with an exit code of not zero, this could cause hard to detect package failures. Available in 0.10.3+. Will be removed in 0.11.0.");
config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass.");
config.Features.ExitOnRebootDetected = set_feature_flag(ApplicationParameters.Features.ExitOnRebootDetected, configFileSettings, defaultEnabled: false, description: "Exit On Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either 350 or 1604. When it exits with 350, it means pending reboot discovered prior to running operation. When it exits with 1604, it means some work completed prior to reboot request being detected. As this will affect upgrade all, it is normally recommended to leave this off. Available in 0.10.12+.".format_with(ApplicationParameters.Features.ExitOnRebootDetected));
}

private static bool set_feature_flag(string featureName, ConfigFileSettings configFileSettings, bool defaultEnabled, string description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,21 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon
"Stop On First Package Failure - stop running install, upgrade or uninstall on first package failure instead of continuing with others. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.StopOnFirstPackageFailure, configuration.Features.StopOnFirstPackageFailure.to_string()),
option => configuration.Features.StopOnFirstPackageFailure = option != null
)
.Add("exitwhenrebootdetected|exit-when-reboot-detected",
"Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either 350 or 1604. Overrides the default feature '{1}' set to '{2}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
option => configuration.Features.ExitOnRebootDetected = option != null
)
.Add("ignoredetectedreboot|ignore-detected-reboot",
"Ignore Detected Reboot - If a reboot request is detected during a Chocolatey operation, then ignore it. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
option =>
{
if (option != null)
{
configuration.Features.ExitOnRebootDetected = false;
}
})
;

//todo: package name can be a url / installertype
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,21 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon
"Stop On First Package Failure - stop running install, upgrade or uninstall on first package failure instead of continuing with others. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.4+.".format_with(ApplicationParameters.Features.StopOnFirstPackageFailure, configuration.Features.StopOnFirstPackageFailure.to_string()),
option => configuration.Features.StopOnFirstPackageFailure = option != null
)
.Add("exitwhenrebootdetected|exit-when-reboot-detected",
"Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either 350 or 1604. Overrides the default feature '{1}' set to '{2}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
option => configuration.Features.ExitOnRebootDetected = option != null
)
.Add("ignoredetectedreboot|ignore-detected-reboot",
"Ignore Detected Reboot - If a reboot request is detected during a Chocolatey operation, then ignore it. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
option =>
{
if (option != null)
{
configuration.Features.ExitOnRebootDetected = false;
}
})
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon
{
if (option != null) configuration.Features.UseRememberedArgumentsForUpgrades = false;
})
.Add("exitwhenrebootdetected|exit-when-reboot-detected",
"Exit When Reboot Detected - Stop running install, upgrade, or uninstall when a reboot request is detected. Requires '{0}' feature to be turned on. Will exit with either 350 or 1604. Overrides the default feature '{1}' set to '{2}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
option => configuration.Features.ExitOnRebootDetected = option != null
)
.Add("ignoredetectedreboot|ignore-detected-reboot",
"Ignore Detected Reboot - If a reboot request is detected during a Chocolatey operation, then ignore it. Overrides the default feature '{0}' set to '{1}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
option =>
{
if (option != null)
{
configuration.Features.ExitOnRebootDetected = false;
}
})
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ public sealed class FeaturesConfiguration
public bool UseRememberedArgumentsForUpgrades { get; set; }
public bool IgnoreUnfoundPackagesOnUpgradeOutdated { get; set; }
public bool RemovePackageInformationOnUninstall { get; set; }
public bool ExitOnRebootDetected { get; set; }

//todo remove in 0.11.0
public bool ScriptsCheckLastExitCode { get; set; }
Expand Down
16 changes: 16 additions & 0 deletions src/chocolatey/infrastructure.app/registration/ContainerBinding.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ namespace chocolatey.infrastructure.app.registration
using infrastructure.commands;
using infrastructure.configuration;
using infrastructure.services;
using infrastructure.validations;
using nuget;
using services;
using tasks;
using validations;
using CryptoHashProvider = cryptography.CryptoHashProvider;
using IFileSystem = filesystem.IFileSystem;
using IHashProvider = cryptography.IHashProvider;
Expand Down Expand Up @@ -61,6 +63,7 @@ public void RegisterComponents(Container container)
container.Register<IChocolateyPackageInformationService, ChocolateyPackageInformationService>(Lifestyle.Singleton);
container.Register<IShimGenerationService, ShimGenerationService>(Lifestyle.Singleton);
container.Register<IRegistryService, RegistryService>(Lifestyle.Singleton);
container.Register<IPendingRebootService, PendingRebootService>(Lifestyle.Singleton);
container.Register<IFilesService, FilesService>(Lifestyle.Singleton);
container.Register<IConfigTransformService, ConfigTransformService>(Lifestyle.Singleton);
container.Register<IHashProvider>(() => new CryptoHashProvider(container.GetInstance<IFileSystem>()), Lifestyle.Singleton);
Expand Down Expand Up @@ -126,6 +129,19 @@ public void RegisterComponents(Container container)
return list.AsReadOnly();
},
Lifestyle.Singleton);

container.Register<IEnumerable<IValidation>>(
() =>
{
var list = new List<IValidation>
{
new GlobalConfigurationValidation(),
new SystemStateValidation(container.GetInstance<IPendingRebootService>())
};

return list.AsReadOnly();
},
Lifestyle.Singleton);
}
}

Expand Down
59 changes: 58 additions & 1 deletion src/chocolatey/infrastructure.app/runners/ConsoleApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,13 @@ namespace chocolatey.infrastructure.app.runners
{
using System;
using System.Collections.Generic;
using System.Linq;
using SimpleInjector;
using configuration;
using infrastructure.validations;
using logging;
using utility;
using validations;

/// <summary>
/// Console application responsible for running chocolatey
Expand Down Expand Up @@ -85,9 +88,63 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain
}
}
},
() => command.handle_validation(config),
() =>
{
command.handle_validation(config);

var validationResults = new List<ValidationResult>();
var validations = container.GetAllInstances<IValidation>();

foreach (var validation in validations)
{
validationResults.AddRange(validation.validate(config));
}

var validationErrors = report_validation_summary(validationResults);

if (validationErrors != 0)
{
// NOTE: This is intentionally left blank
throw new ApplicationException("");
}
},
() => command.help_message(config));
});
}

private int report_validation_summary(IList<ValidationResult> validationResults)
{
var successes = validationResults.Count(v => v.Status == ValidationStatus.Success);
var warnings = validationResults.Count(v => v.Status == ValidationStatus.Warning);
var errors = validationResults.Count(v => v.Status == ValidationStatus.Error);

this.Log().Info("{0} validations performed. {1} success(es), {2} warning(s), and {3} error(s).".format_with(
validationResults.Count,
successes,
warnings,
errors));

if (warnings != 0)
{
this.Log().Info("");
this.Log().Warn("Warnings:");
foreach (var warning in validationResults.Where(p => p.Status == ValidationStatus.Warning).or_empty_list_if_null())
{
this.Log().Warn(" - {0}".format_with(warning.Message));
}
}

if (errors != 0)
{
this.Log().Info("");
this.Log().Error("Errors:");
foreach (var error in validationResults.Where(p => p.Status == ValidationStatus.Error).or_empty_list_if_null())
{
this.Log().Error(" - {0}".format_with(error.Message, error.ExitCode));
}
}

return errors;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,24 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu

remove_pending(packageResult, config);

// Test to see if exit code is either:
// 1641 - restart initiated
// 3010 - restart required
var rebootExitCodes = new List<int> { 1641, 3010 };

if(rebootExitCodes.Contains(packageResult.ExitCode))
{
if(config.Features.ExitOnRebootDetected)
{
Environment.ExitCode = 1604;
this.Log().Warn(ChocolateyLoggers.Important, @"Chocolatey has detected a pending reboot after installing/upgrading
package '{0}' - stopping further execution".format_with(packageResult.Name));

// NOTE: This is intentionally left blank
throw new ApplicationException("");
}
}

if (!packageResult.Success)
{
this.Log().Error(ChocolateyLoggers.Important, "The {0} of {1} was NOT successful.".format_with(commandName.to_string(), packageResult.Name));
Expand Down Expand Up @@ -560,29 +578,34 @@ public ConcurrentDictionary<string, PackageResult> install_run(ChocolateyConfigu

get_environment_before(config, allowLogging: true);

foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null())
try
{
Action<PackageResult> action = null;
if (packageConfig.SourceType == SourceType.normal)
foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null())
{
action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install);
}
Action<PackageResult> action = null;
if (packageConfig.SourceType == SourceType.normal)
{
action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install);
}

var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action));
var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action));

foreach (var result in results)
{
packageInstalls.GetOrAdd(result.Key, result.Value);
foreach (var result in results)
{
packageInstalls.GetOrAdd(result.Key, result.Value);
}
}
}

var installFailures = report_action_summary(packageInstalls, "installed");
if (installFailures != 0 && Environment.ExitCode == 0)
finally
{
Environment.ExitCode = 1;
}
var installFailures = report_action_summary(packageInstalls, "installed");
if (installFailures != 0 && Environment.ExitCode == 0)
{
Environment.ExitCode = 1;
}

randomly_notify_about_pro_business(config);
randomly_notify_about_pro_business(config);
}

return packageInstalls;
}
Expand Down
Loading

0 comments on commit 519fd25

Please sign in to comment.