Skip to content

Commit 1a86916

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 1a86916

18 files changed

+722
-50
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

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright © 2017 - 2018 Chocolatey Software, Inc
22
// Copyright © 2011 - 2017 RealDimensions Software, LLC
3-
//
3+
//
44
// Licensed under the Apache License, Version 2.0 (the "License");
55
// you may not use this file except in compliance with the License.
66
//
@@ -127,6 +127,12 @@ public static class Environment
127127
/// </summary>
128128
public static readonly bool AllowPrompts = true;
129129

130+
public static class ExitCodes
131+
{
132+
public static readonly int ErrorFailNoActionReboot = 350;
133+
public static readonly int ErrorInstallSuspend = 1604;
134+
}
135+
130136
public static class Tools
131137
{
132138
//public static readonly string WebPiCmdExe = _fileSystem.combine_paths(InstallLocation, "nuget.exe");
@@ -170,6 +176,7 @@ public static class Features
170176
public static readonly string IgnoreUnfoundPackagesOnUpgradeOutdated = "ignoreUnfoundPackagesOnUpgradeOutdated";
171177
public static readonly string RemovePackageInformationOnUninstall = "removePackageInformationOnUninstall";
172178
public static readonly string LogWithoutColor = "logWithoutColor";
179+
public static readonly string ExitOnRebootDetected = "exitOnRebootDetected";
173180
}
174181

175182
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 {1} or {2}. When it exits with {1}, it means pending reboot discovered prior to running operation. When it exits with {2}, 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, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend));
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 {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with
160+
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, 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 {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with
123+
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, 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 {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with
187+
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, 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

+62-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,67 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain
8588
}
8689
}
8790
},
88-
() => command.handle_validation(config),
91+
() => {
92+
command.handle_validation(config);
93+
94+
var validationResults = new List<ValidationResult>();
95+
var validationChecks = container.GetAllInstances<IValidation>();
96+
97+
foreach (var validationCheck in validationChecks)
98+
{
99+
validationResults.AddRange(validationCheck.validate(config));
100+
}
101+
102+
var validationErrors = report_validation_summary(validationResults, config);
103+
104+
if (validationErrors != 0)
105+
{
106+
// NOTE: This is intentionally left blank, as the reason for throwing is
107+
// documented in the report_validation_summary above, and a duplication
108+
// is not required in the exception.
109+
throw new ApplicationException("");
110+
}
111+
},
89112
() => command.help_message(config));
90113
});
91114
}
115+
116+
private int report_validation_summary(IList<ValidationResult> validationResults, ChocolateyConfiguration config)
117+
{
118+
var successes = validationResults.Count(v => v.Status == ValidationStatus.Success);
119+
var warnings = validationResults.Count(v => v.Status == ValidationStatus.Warning);
120+
var errors = validationResults.Count(v => v.Status == ValidationStatus.Error);
121+
122+
if (config.RegularOutput)
123+
{
124+
this.Log().Info(errors + warnings == 0 ? ChocolateyLoggers.LogFileOnly : ChocolateyLoggers.Important, () => "{0} validations performed. {1} success(es), {2} warning(s), and {3} error(s).".format_with(
125+
validationResults.Count,
126+
successes,
127+
warnings,
128+
errors));
129+
130+
if (warnings != 0)
131+
{
132+
this.Log().Info("");
133+
this.Log().Warn("Validation Warnings:");
134+
foreach (var warning in validationResults.Where(p => p.Status == ValidationStatus.Warning).or_empty_list_if_null())
135+
{
136+
this.Log().Warn(" - {0}".format_with(warning.Message));
137+
}
138+
}
139+
140+
if (errors != 0)
141+
{
142+
this.Log().Info("");
143+
this.Log().Error("Validation Errors:");
144+
foreach (var error in validationResults.Where(p => p.Status == ValidationStatus.Error).or_empty_list_if_null())
145+
{
146+
this.Log().Error(" - {0}".format_with(error.Message));
147+
}
148+
}
149+
}
150+
151+
return errors;
152+
}
92153
}
93154
}

0 commit comments

Comments
 (0)