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 8, 2019
1 parent 5c87ab7 commit e1a36cf
Show file tree
Hide file tree
Showing 18 changed files with 722 additions and 50 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
9 changes: 8 additions & 1 deletion src/chocolatey/infrastructure.app/ApplicationParameters.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright © 2017 - 2018 Chocolatey Software, Inc
// Copyright © 2011 - 2017 RealDimensions Software, LLC
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
//
Expand Down Expand Up @@ -127,6 +127,12 @@ public static class Environment
/// </summary>
public static readonly bool AllowPrompts = true;

public static class ExitCodes
{
public static readonly int ErrorFailNoActionReboot = 350;
public static readonly int ErrorInstallSuspend = 1604;
}

public static class Tools
{
//public static readonly string WebPiCmdExe = _fileSystem.combine_paths(InstallLocation, "nuget.exe");
Expand Down Expand Up @@ -170,6 +176,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 {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));
}

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 {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, 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 {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, 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 {1} or {2}. Overrides the default feature '{3}' set to '{4}'. Available in 0.10.12+.".format_with
(ApplicationParameters.Features.UsePackageExitCodes, ApplicationParameters.ExitCodes.ErrorFailNoActionReboot, ApplicationParameters.ExitCodes.ErrorInstallSuspend, 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
63 changes: 62 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,67 @@ public void run(string[] args, ChocolateyConfiguration config, Container contain
}
}
},
() => command.handle_validation(config),
() => {
command.handle_validation(config);

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

foreach (var validationCheck in validationChecks)
{
validationResults.AddRange(validationCheck.validate(config));
}

var validationErrors = report_validation_summary(validationResults, config);

if (validationErrors != 0)
{
// NOTE: This is intentionally left blank, as the reason for throwing is
// documented in the report_validation_summary above, and a duplication
// is not required in the exception.
throw new ApplicationException("");
}
},
() => command.help_message(config));
});
}

private int report_validation_summary(IList<ValidationResult> validationResults, ChocolateyConfiguration config)
{
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);

if (config.RegularOutput)
{
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(
validationResults.Count,
successes,
warnings,
errors));

if (warnings != 0)
{
this.Log().Info("");
this.Log().Warn("Validation 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("Validation 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));
}
}

return errors;
}
}
}
Loading

0 comments on commit e1a36cf

Please sign in to comment.