Skip to content

Commit 519fd25

Browse files
committed
(chocolateyGH-1038) Add ability to stop operation on reboot
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.
1 parent 5c87ab7 commit 519fd25

18 files changed

+622
-17
lines changed

src/chocolatey.console/Program.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ private static void Main(string[] args)
158158
"chocolatey".Log().Error(ChocolateyLoggers.Important, () => "{0}".format_with(ex.Message));
159159
}
160160

161-
Environment.ExitCode = 1;
161+
if (Environment.ExitCode == 0) Environment.ExitCode = 1;
162162
}
163163
finally
164164
{

src/chocolatey/chocolatey.csproj

+7
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
</Compile>
103103
<Compile Include="AssemblyExtensions.cs" />
104104
<Compile Include="infrastructure.app\commands\ChocolateyInfoCommand.cs" />
105+
<Compile Include="infrastructure.app\validations\GlobalConfigurationValidation.cs" />
105106
<Compile Include="infrastructure.app\configuration\EnvironmentSettings.cs" />
106107
<Compile Include="infrastructure.app\domain\GenericRegistryKey.cs" />
107108
<Compile Include="infrastructure.app\domain\GenericRegistryValue.cs" />
@@ -120,10 +121,13 @@
120121
<Compile Include="infrastructure.app\domain\RegistryValueExtensions.cs" />
121122
<Compile Include="infrastructure.app\domain\RegistryValueKindType.cs" />
122123
<Compile Include="infrastructure.app\events\HandlePackageResultCompletedMessage.cs" />
124+
<Compile Include="infrastructure.app\services\IPendingRebootService.cs" />
125+
<Compile Include="infrastructure.app\services\PendingRebootService.cs" />
123126
<Compile Include="infrastructure.app\templates\ChocolateyTodoTemplate.cs" />
124127
<Compile Include="infrastructure.app\utility\ArgumentsUtility.cs" />
125128
<Compile Include="infrastructure.app\utility\HashCode.cs" />
126129
<Compile Include="infrastructure.app\utility\PackageUtility.cs" />
130+
<Compile Include="infrastructure.app\validations\SystemStateValidation.cs" />
127131
<Compile Include="infrastructure\filesystem\FileSystem.cs" />
128132
<Compile Include="infrastructure\logging\AggregateLog.cs" />
129133
<Compile Include="infrastructure\logging\LogLevelType.cs" />
@@ -317,6 +321,9 @@
317321
<Compile Include="infrastructure\tokens\TokenReplacer.cs" />
318322
<Compile Include="ILogExtensions.cs" />
319323
<Compile Include="infrastructure\tolerance\FaultTolerance.cs" />
324+
<Compile Include="infrastructure\validations\IValidation.cs" />
325+
<Compile Include="infrastructure\validations\ValidationResult.cs" />
326+
<Compile Include="infrastructure\validations\ValidationStatus.cs" />
320327
<Compile Include="infrastructure\xml\XmlCData.cs" />
321328
<Compile Include="LogExtensions.cs" />
322329
<Compile Include="ObjectExtensions.cs" />

src/chocolatey/infrastructure.app/ApplicationParameters.cs

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ public static class Features
170170
public static readonly string IgnoreUnfoundPackagesOnUpgradeOutdated = "ignoreUnfoundPackagesOnUpgradeOutdated";
171171
public static readonly string RemovePackageInformationOnUninstall = "removePackageInformationOnUninstall";
172172
public static readonly string LogWithoutColor = "logWithoutColor";
173+
public static readonly string ExitOnRebootDetected = "exitOnRebootDetected";
173174
}
174175

175176
public static class Messages

src/chocolatey/infrastructure.app/builders/ConfigurationBuilder.cs

+1
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ private static void set_feature_flags(ChocolateyConfiguration config, ConfigFile
303303

304304
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.");
305305
config.PromptForConfirmation = !set_feature_flag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass.");
306+
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));
306307
}
307308

308309
private static bool set_feature_flag(string featureName, ConfigFileSettings configFileSettings, bool defaultEnabled, string description)

src/chocolatey/infrastructure.app/commands/ChocolateyInstallCommand.cs

+15
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,21 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon
155155
"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()),
156156
option => configuration.Features.StopOnFirstPackageFailure = option != null
157157
)
158+
.Add("exitwhenrebootdetected|exit-when-reboot-detected",
159+
"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
160+
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
161+
option => configuration.Features.ExitOnRebootDetected = option != null
162+
)
163+
.Add("ignoredetectedreboot|ignore-detected-reboot",
164+
"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
165+
(ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
166+
option =>
167+
{
168+
if (option != null)
169+
{
170+
configuration.Features.ExitOnRebootDetected = false;
171+
}
172+
})
158173
;
159174

160175
//todo: package name can be a url / installertype

src/chocolatey/infrastructure.app/commands/ChocolateyUninstallCommand.cs

+15
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,21 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon
118118
"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()),
119119
option => configuration.Features.StopOnFirstPackageFailure = option != null
120120
)
121+
.Add("exitwhenrebootdetected|exit-when-reboot-detected",
122+
"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
123+
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
124+
option => configuration.Features.ExitOnRebootDetected = option != null
125+
)
126+
.Add("ignoredetectedreboot|ignore-detected-reboot",
127+
"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
128+
(ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
129+
option =>
130+
{
131+
if (option != null)
132+
{
133+
configuration.Features.ExitOnRebootDetected = false;
134+
}
135+
})
121136
;
122137
}
123138

src/chocolatey/infrastructure.app/commands/ChocolateyUpgradeCommand.cs

+15
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,21 @@ public virtual void configure_argument_parser(OptionSet optionSet, ChocolateyCon
182182
{
183183
if (option != null) configuration.Features.UseRememberedArgumentsForUpgrades = false;
184184
})
185+
.Add("exitwhenrebootdetected|exit-when-reboot-detected",
186+
"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
187+
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
188+
option => configuration.Features.ExitOnRebootDetected = option != null
189+
)
190+
.Add("ignoredetectedreboot|ignore-detected-reboot",
191+
"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
192+
(ApplicationParameters.Features.ExitOnRebootDetected, configuration.Features.ExitOnRebootDetected.to_string()),
193+
option =>
194+
{
195+
if (option != null)
196+
{
197+
configuration.Features.ExitOnRebootDetected = false;
198+
}
199+
})
185200
;
186201
}
187202

src/chocolatey/infrastructure.app/configuration/ChocolateyConfiguration.cs

+1
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ public sealed class FeaturesConfiguration
390390
public bool UseRememberedArgumentsForUpgrades { get; set; }
391391
public bool IgnoreUnfoundPackagesOnUpgradeOutdated { get; set; }
392392
public bool RemovePackageInformationOnUninstall { get; set; }
393+
public bool ExitOnRebootDetected { get; set; }
393394

394395
//todo remove in 0.11.0
395396
public bool ScriptsCheckLastExitCode { get; set; }

src/chocolatey/infrastructure.app/registration/ContainerBinding.cs

+16
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ namespace chocolatey.infrastructure.app.registration
2727
using infrastructure.commands;
2828
using infrastructure.configuration;
2929
using infrastructure.services;
30+
using infrastructure.validations;
3031
using nuget;
3132
using services;
3233
using tasks;
34+
using validations;
3335
using CryptoHashProvider = cryptography.CryptoHashProvider;
3436
using IFileSystem = filesystem.IFileSystem;
3537
using IHashProvider = cryptography.IHashProvider;
@@ -61,6 +63,7 @@ public void RegisterComponents(Container container)
6163
container.Register<IChocolateyPackageInformationService, ChocolateyPackageInformationService>(Lifestyle.Singleton);
6264
container.Register<IShimGenerationService, ShimGenerationService>(Lifestyle.Singleton);
6365
container.Register<IRegistryService, RegistryService>(Lifestyle.Singleton);
66+
container.Register<IPendingRebootService, PendingRebootService>(Lifestyle.Singleton);
6467
container.Register<IFilesService, FilesService>(Lifestyle.Singleton);
6568
container.Register<IConfigTransformService, ConfigTransformService>(Lifestyle.Singleton);
6669
container.Register<IHashProvider>(() => new CryptoHashProvider(container.GetInstance<IFileSystem>()), Lifestyle.Singleton);
@@ -126,6 +129,19 @@ public void RegisterComponents(Container container)
126129
return list.AsReadOnly();
127130
},
128131
Lifestyle.Singleton);
132+
133+
container.Register<IEnumerable<IValidation>>(
134+
() =>
135+
{
136+
var list = new List<IValidation>
137+
{
138+
new GlobalConfigurationValidation(),
139+
new SystemStateValidation(container.GetInstance<IPendingRebootService>())
140+
};
141+
142+
return list.AsReadOnly();
143+
},
144+
Lifestyle.Singleton);
129145
}
130146
}
131147

src/chocolatey/infrastructure.app/runners/ConsoleApplication.cs

+58-1
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@ namespace chocolatey.infrastructure.app.runners
1818
{
1919
using System;
2020
using System.Collections.Generic;
21+
using System.Linq;
2122
using SimpleInjector;
2223
using configuration;
24+
using infrastructure.validations;
2325
using logging;
2426
using utility;
27+
using validations;
2528

2629
/// <summary>
2730
/// Console application responsible for running chocolatey
@@ -85,9 +88,63 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain
8588
}
8689
}
8790
},
88-
() => command.handle_validation(config),
91+
() =>
92+
{
93+
command.handle_validation(config);
94+
95+
var validationResults = new List<ValidationResult>();
96+
var validations = container.GetAllInstances<IValidation>();
97+
98+
foreach (var validation in validations)
99+
{
100+
validationResults.AddRange(validation.validate(config));
101+
}
102+
103+
var validationErrors = report_validation_summary(validationResults);
104+
105+
if (validationErrors != 0)
106+
{
107+
// NOTE: This is intentionally left blank
108+
throw new ApplicationException("");
109+
}
110+
},
89111
() => command.help_message(config));
90112
});
91113
}
114+
115+
private int report_validation_summary(IList<ValidationResult> validationResults)
116+
{
117+
var successes = validationResults.Count(v => v.Status == ValidationStatus.Success);
118+
var warnings = validationResults.Count(v => v.Status == ValidationStatus.Warning);
119+
var errors = validationResults.Count(v => v.Status == ValidationStatus.Error);
120+
121+
this.Log().Info("{0} validations performed. {1} success(es), {2} warning(s), and {3} error(s).".format_with(
122+
validationResults.Count,
123+
successes,
124+
warnings,
125+
errors));
126+
127+
if (warnings != 0)
128+
{
129+
this.Log().Info("");
130+
this.Log().Warn("Warnings:");
131+
foreach (var warning in validationResults.Where(p => p.Status == ValidationStatus.Warning).or_empty_list_if_null())
132+
{
133+
this.Log().Warn(" - {0}".format_with(warning.Message));
134+
}
135+
}
136+
137+
if (errors != 0)
138+
{
139+
this.Log().Info("");
140+
this.Log().Error("Errors:");
141+
foreach (var error in validationResults.Where(p => p.Status == ValidationStatus.Error).or_empty_list_if_null())
142+
{
143+
this.Log().Error(" - {0}".format_with(error.Message, error.ExitCode));
144+
}
145+
}
146+
147+
return errors;
148+
}
92149
}
93150
}

src/chocolatey/infrastructure.app/services/ChocolateyPackageService.cs

+38-15
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,24 @@ public void handle_package_result(PackageResult packageResult, ChocolateyConfigu
420420

421421
remove_pending(packageResult, config);
422422

423+
// Test to see if exit code is either:
424+
// 1641 - restart initiated
425+
// 3010 - restart required
426+
var rebootExitCodes = new List<int> { 1641, 3010 };
427+
428+
if(rebootExitCodes.Contains(packageResult.ExitCode))
429+
{
430+
if(config.Features.ExitOnRebootDetected)
431+
{
432+
Environment.ExitCode = 1604;
433+
this.Log().Warn(ChocolateyLoggers.Important, @"Chocolatey has detected a pending reboot after installing/upgrading
434+
package '{0}' - stopping further execution".format_with(packageResult.Name));
435+
436+
// NOTE: This is intentionally left blank
437+
throw new ApplicationException("");
438+
}
439+
}
440+
423441
if (!packageResult.Success)
424442
{
425443
this.Log().Error(ChocolateyLoggers.Important, "The {0} of {1} was NOT successful.".format_with(commandName.to_string(), packageResult.Name));
@@ -560,29 +578,34 @@ public ConcurrentDictionary<string, PackageResult> install_run(ChocolateyConfigu
560578

561579
get_environment_before(config, allowLogging: true);
562580

563-
foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null())
581+
try
564582
{
565-
Action<PackageResult> action = null;
566-
if (packageConfig.SourceType == SourceType.normal)
583+
foreach (var packageConfig in set_config_from_package_names_and_packages_config(config, packageInstalls).or_empty_list_if_null())
567584
{
568-
action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install);
569-
}
585+
Action<PackageResult> action = null;
586+
if (packageConfig.SourceType == SourceType.normal)
587+
{
588+
action = (packageResult) => handle_package_result(packageResult, packageConfig, CommandNameType.install);
589+
}
570590

571-
var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action));
591+
var results = perform_source_runner_function(packageConfig, r => r.install_run(packageConfig, action));
572592

573-
foreach (var result in results)
574-
{
575-
packageInstalls.GetOrAdd(result.Key, result.Value);
593+
foreach (var result in results)
594+
{
595+
packageInstalls.GetOrAdd(result.Key, result.Value);
596+
}
576597
}
577598
}
578-
579-
var installFailures = report_action_summary(packageInstalls, "installed");
580-
if (installFailures != 0 && Environment.ExitCode == 0)
599+
finally
581600
{
582-
Environment.ExitCode = 1;
583-
}
601+
var installFailures = report_action_summary(packageInstalls, "installed");
602+
if (installFailures != 0 && Environment.ExitCode == 0)
603+
{
604+
Environment.ExitCode = 1;
605+
}
584606

585-
randomly_notify_about_pro_business(config);
607+
randomly_notify_about_pro_business(config);
608+
}
586609

587610
return packageInstalls;
588611
}

0 commit comments

Comments
 (0)