Skip to content

Commit

Permalink
(#2591) Add headers to limit output commands
Browse files Browse the repository at this point in the history
When a user asks for limited output from Chocolatey, it is not uncommon
to pipe that output to `ConvertFrom-String` or `ConvertFrom-Csv` and
manually add headers to get back an object. This allows for getting a
header row back so that the end user doesn't need to add their own
headers and discern what they are.

This also adds a StringResources static class that allows us to store
constant strings in and use them across the code to reduce duplication.
  • Loading branch information
corbob authored and gep13 committed Jun 21, 2024
1 parent 88fcb13 commit ec14bec
Show file tree
Hide file tree
Showing 17 changed files with 227 additions and 6 deletions.
12 changes: 11 additions & 1 deletion src/chocolatey/StringResources.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,15 @@ public static class EnvironmentVariables
[Browsable(false)]
internal const string PackageNuspecVersion = "packageNuspecVersion";
}

public static class OptionDescriptions
{
public const string DISPLAY_HEADERS = "Display headers - Display headers when limit-output is used. Requires 2.3.0";
}

public static class Options
{
public const string DISPLAY_HEADERS = "headers"; // TODO: This option name needs to be decided and agreed upon.
}
}
}
}
1 change: 1 addition & 0 deletions src/chocolatey/infrastructure.app/ApplicationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ public static class Features
public static readonly string UsePackageRepositoryOptimizations = "usePackageRepositoryOptimizations";
public static readonly string DisableCompatibilityChecks = "disableCompatibilityChecks";
public static readonly string UsePackageHashValidation = "usePackageHashValidation";
public static readonly string AlwaysDisplayHeaders = "alwaysDisplayHeaders";
}

public static class Messages
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,8 @@ private static void SetAllFeatureFlags(ChocolateyConfiguration config, ConfigFil
config.Features.UsePackageRepositoryOptimizations = SetFeatureFlag(ApplicationParameters.Features.UsePackageRepositoryOptimizations, configFileSettings, defaultEnabled: true, description: "Use Package Repository Optimizations - Turn on optimizations for reducing bandwidth with repository queries during package install/upgrade/outdated operations. Should generally be left enabled, unless a repository needs to support older methods of query. When disabled, this makes queries similar to the way they were done in earlier versions of Chocolatey.");
config.Features.UsePackageHashValidation = SetFeatureFlag(ApplicationParameters.Features.UsePackageHashValidation, configFileSettings, defaultEnabled: false, description: "Use Package Hash Validation - Check the hash of the downloaded package file against the source provided hash. Only supports sources that provide SHA512 hashes. Disabled by default. Available in 2.3.0+");
config.PromptForConfirmation = !SetFeatureFlag(ApplicationParameters.Features.AllowGlobalConfirmation, configFileSettings, defaultEnabled: false, description: "Prompt for confirmation in scripts or bypass.");
config.DisableCompatibilityChecks = SetFeatureFlag(ApplicationParameters.Features.DisableCompatibilityChecks, configFileSettings, defaultEnabled: false, description: "Disable Compatibility Checks - Disable showing a warning when there is an incompatibility between Chocolatey CLI and Chocolatey Licensed Extension.");
config.DisableCompatibilityChecks = SetFeatureFlag(ApplicationParameters.Features.DisableCompatibilityChecks, configFileSettings, defaultEnabled: false, description: "Disable Compatibility Checks - Disable showing a warning when there is an incompatibility between Chocolatey CLI and Chocolatey Licensed Extension. Available in 1.1.0+");
config.DisplayHeaders = SetFeatureFlag(ApplicationParameters.Features.AlwaysDisplayHeaders, configFileSettings, defaultEnabled: false, description: StringResources.OptionDescriptions.DISPLAY_HEADERS);
}

private static bool SetFeatureFlag(string featureName, ConfigFileSettings configFileSettings, bool defaultEnabled, string description)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
.Add("k=|key=|apikey=|api-key=",
"ApiKey - The API key for the source. This is the authentication that identifies you and allows you to push to a source. With some sources this is either a key or it could be a user name and password specified as 'user:password'.",
option => configuration.ApiKeyCommand.Key = option.UnquoteSafe())
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true)
;
}

Expand Down Expand Up @@ -197,6 +200,11 @@ public virtual void Run(ChocolateyConfiguration configuration)
_configSettingsService.SetApiKey(configuration);
break;
default:
if (!configuration.RegularOutput && configuration.DisplayHeaders)
{
this.Log().Info("Source|Key");
}

_configSettingsService.GetApiKey(configuration, (key) =>
{
var authenticatedString = string.IsNullOrWhiteSpace(key.Key) ? string.Empty : "(Authenticated)";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
"value=",
"Value - the value of the config setting. Required with some actions. Defaults to empty.",
option => configuration.ConfigCommand.ConfigValue = option.UnquoteSafe())
.Add(
StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true)
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
.Add("n=|name=",
"Name - the name of the source. Required with actions other than list. Defaults to empty.",
option => configuration.FeatureCommand.Name = option.UnquoteSafe())
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true)
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ public override void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConf
.Add("include-configured-sources",
"Include Configured Sources - When using the '--source' option, this appends the sources that have been saved into the chocolatey.config file by 'source' command. Available in 2.3.0+",
option => configuration.IncludeConfiguredSources = option != null)
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true)
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
option => configuration.ListCommand.IdStartsWith = option != null)
.Add("detail|detailed",
"Detailed - Alias for verbose.",
option => configuration.Verbose = option != null);
option => configuration.Verbose = option != null)
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true);
}

public virtual int Count(ChocolateyConfiguration config)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
.Add("include-configured-sources",
"Include Configured Sources - When using the '--source' option, this appends the sources that have been saved into the chocolatey.config file by 'source' command. Available in 2.3.0+",
option => configuration.IncludeConfiguredSources = option != null)
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true)
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
.Add("version=",
"Version - Used when multiple versions of a package are installed. Defaults to empty.",
option => configuration.Version = option.UnquoteSafe())
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true)
;
}

Expand Down Expand Up @@ -178,6 +181,11 @@ public virtual void ListPins(ChocolateyConfiguration config)
config.QuietOutput = quiet;
config.Input = input;

if (!config.RegularOutput && config.DisplayHeaders)
{
this.Log().Info("PackageId|Version");
}

foreach (var pkg in packages.OrEmpty())
{
var pkgInfo = _packageInfoService.Get(pkg.PackageMetadata);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ public virtual void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfi
.Add("adminonly|admin-only",
"Visible to Administrators Only - Should this source be visible to non-administrators? Requires business edition. Defaults to false.",
option => configuration.SourceCommand.VisibleToAdminsOnly = option != null)
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true)
;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ public void ConfigureArgumentParser(OptionSet optionSet, ChocolateyConfiguration
optionSet
.Add("n=|name=",
"The name of the template to get information about.",
option => configuration.TemplateCommand.Name = option.UnquoteSafe().ToLower());
option => configuration.TemplateCommand.Name = option.UnquoteSafe().ToLower())
.Add(StringResources.Options.DISPLAY_HEADERS,
StringResources.OptionDescriptions.DISPLAY_HEADERS,
option => configuration.DisplayHeaders = true);
// todo: #2570 Allow for templates from an external path? Requires #1477
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ public ChocolateyConfiguration()
TemplateCommand = new TemplateCommandConfiguration();
CacheCommand = new CacheCommandConfiguration();
RuleCommand = new RuleCommandConfiguration();
DisplayHeaders = false;

#if DEBUG
AllowUnofficialBuild = true;
#endif
Expand Down Expand Up @@ -360,6 +362,7 @@ private void AppendOutput(StringBuilder propertyValues, string append)
public string DownloadChecksumType { get; set; }
public string DownloadChecksumType64 { get; set; }
public bool PinPackage { get; set; }
public bool DisplayHeaders { get; set; }

/// <summary>
/// Configuration values provided by choco.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ public virtual bool SkipSource(ConfigFileSourceSetting source, ChocolateyConfigu

public virtual IEnumerable<ChocolateySource> ListSources(ChocolateyConfiguration configuration)
{
if (!configuration.RegularOutput && configuration.DisplayHeaders)
{
this.Log().Info("SourceId|Location|Disabled|UserName|Certificate|Priority|BypassProxy|AllowSelfService|AdminOnly");
}

var list = new List<ChocolateySource>();
foreach (var source in ConfigFileSettings.Sources.OrEmpty().OrderBy(s => s.Id))
{
Expand Down Expand Up @@ -268,6 +273,11 @@ public void EnableSource(ChocolateyConfiguration configuration)

public void ListFeatures(ChocolateyConfiguration configuration)
{
if (!configuration.RegularOutput && configuration.DisplayHeaders)
{
this.Log().Info("FeatureName|Enabled|Description");
}

foreach (var feature in ConfigFileSettings.Features.OrEmpty().OrderBy(f => f.Name))
{
if (configuration.RegularOutput)
Expand Down Expand Up @@ -455,6 +465,11 @@ public void RemoveApiKey(ChocolateyConfiguration configuration)

public void ListConfig(ChocolateyConfiguration configuration)
{
if (!configuration.RegularOutput && configuration.DisplayHeaders)
{
this.Log().Info("Name|Value|Description");
}

foreach (var config in ConfigFileSettings.ConfigSettings.OrEmpty().OrderBy(c => c.Key))
{
if (configuration.RegularOutput)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,8 +257,16 @@ public virtual IEnumerable<PackageResult> List(ChocolateyConfiguration config)

if (config.RegularOutput)
{
// This doesn't make sense as a Debug message to me... Debug messages don't really show up when you're running normally...
this.Log().Debug(() => "Searching for package information");
}
else
{
if (config.DisplayHeaders)
{
this.Log().Info("PackageID|Version");
}
}

var packages = new List<PackageResult>();

Expand Down Expand Up @@ -846,6 +854,13 @@ public virtual void Outdated(ChocolateyConfiguration config)
Output is package name | current version | available version | pinned?
");
}
else
{
if (config.DisplayHeaders)
{
this.Log().Info("PackageName|CurrentVersion|AvailableVersion|Pinned");
}
}

config.PackageNames = ApplicationParameters.AllPackages;
config.UpgradeCommand.NotifyOnlyAvailableUpgrades = true;
Expand Down
9 changes: 7 additions & 2 deletions src/chocolatey/infrastructure.app/services/TemplateService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ public void List(ChocolateyConfiguration configuration)

if (string.IsNullOrWhiteSpace(configuration.TemplateCommand.Name))
{
if (!configuration.RegularOutput && configuration.DisplayHeaders)
{
this.Log().Info("TemplateName|Version");
}

if (templateDirList.Any())
{
foreach (var templateDir in templateDirList)
Expand All @@ -259,11 +264,11 @@ public void List(ChocolateyConfiguration configuration)
ListCustomTemplateInformation(configuration);
}

this.Log().Info(configuration.RegularOutput ? "{0} Custom templates found at {1}{2}".FormatWith(templateDirList.Count(), ApplicationParameters.TemplatesLocation, Environment.NewLine) : string.Empty);
this.Log().Info(configuration.RegularOutput ? ChocolateyLoggers.Normal : ChocolateyLoggers.LogFileOnly, "{0} Custom templates found at {1}{2}".FormatWith(templateDirList.Count(), ApplicationParameters.TemplatesLocation, Environment.NewLine));
}
else
{
this.Log().Info(configuration.RegularOutput ? "No custom templates installed in {0}{1}".FormatWith(ApplicationParameters.TemplatesLocation, Environment.NewLine) : string.Empty);
this.Log().Info(configuration.RegularOutput ? ChocolateyLoggers.Normal : ChocolateyLoggers.LogFileOnly, "No custom templates installed in {0}{1}".FormatWith(ApplicationParameters.TemplatesLocation, Environment.NewLine));
}

ListBuiltinTemplateInformation(configuration, isBuiltInTemplateOverridden, isBuiltInOrDefaultTemplateDefault);
Expand Down
133 changes: 133 additions & 0 deletions tests/pester-tests/features/Headers.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
Import-Module helpers/common-helpers

Describe "choco headers tests" -Tag Chocolatey, HeadersFeature {
BeforeDiscovery {
$Commands = @(
@{
Command = 'list'
CommandLine = '--local-only'
ExpectedHeaders = 'PackageID|Version'
}
@{
#
Command = 'info'
CommandLine = 'chocolatey --local-only'
ExpectedHeaders = 'PackageID|Version'
}
@{
# Needs to pin something...
Command = 'pin'
ExpectedHeaders = 'PackageID|Version'
}
@{
Command = 'outdated'
ExpectedHeaders = 'PackageName|CurrentVersion|AvailableVersion|Pinned'
}
@{
Command = 'source'
ExpectedHeaders = 'SourceId|Location|Disabled|UserName|Certificate|Priority|BypassProxy|AllowSelfService|AdminOnly'
}
@{
Command = 'config'
ExpectedHeaders = 'Name|Value|Description'
}
@{
Command = 'feature'
ExpectedHeaders = 'FeatureName|Enabled|Description'
}
@{
Command = 'apikey'
ExpectedHeaders = 'Source|Key'
}
@{
Command = 'template'
ExpectedHeaders = 'TemplateName|Version'
}
)
}
BeforeAll {
Initialize-ChocolateyTestInstall

New-ChocolateyInstallSnapshot
}

AfterAll {
Remove-ChocolateyTestInstall
}

Context "Outputs headers for <Command> when '--display-headers' provided configured" -ForEach $Commands {
BeforeAll {
$Output = Invoke-Choco $Command $CommandLine --limit-output --display-headers
}

It 'Exits success (0)' {
$Output.ExitCode | Should -Be 0 -Because $Output.String
}

It "Displays appropriate header" {
# Some commands won't output anything but the header. In that case we want just the lines instead of indexing into it.
$ActualOutput = if ($Output.Lines.Count -gt 1) {
$Output.Lines[0]
}
else {
$Output.Lines
}

$ActualOutput | Should -Be $ExpectedHeaders
}

}

Context "Does not output headers for <Command> by default" -ForEach $Commands {
BeforeAll {
$Output = Invoke-Choco $Command $CommandLine --limit-output
}

It 'Exits success (0)' {
$Output.ExitCode | Should -Be 0 -Because $Output.String
}

It "Does not display header" {
# Some commands won't output anything but the header. In that case we want just the lines instead of indexing into it.
$ActualOutput = if ($Output.Lines.Count -gt 1) {
$Output.Lines[0]
}
else {
$Output.Lines
}

$ActualOutput | Should -Not -Be $ExpectedHeaders
}

}

Context "Outputs headers when feature enabled" {
BeforeAll {
Restore-ChocolateyInstallSnapshot

Enable-ChocolateyFeature -Name AlwaysDisplayHeaders
}

Context "Outputs headers for <Command>" -ForEach $Commands {
BeforeAll {
$Output = Invoke-Choco $Command $CommandLine --limit-output
}

It 'Exits success (0)' {
$Output.ExitCode | Should -Be 0 -Because $Output.String
}

It "Displays appropriate header" {
# Some commands won't output anything but the header. In that case we want just the lines instead of indexing into it.
$ActualOutput = if ($Output.Lines.Count -gt 1) {
$Output.Lines[0]
}
else {
$Output.Lines
}

$ActualOutput | Should -Be $ExpectedHeaders
}
}
}
}

0 comments on commit ec14bec

Please sign in to comment.