diff --git a/cmd/apply.go b/cmd/apply.go index c56dc81728..55679cdc63 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -48,6 +48,7 @@ var ( e.Options.Pipeline.Target.DryRun = false e.Options.Pipeline.Target.CleanGitBranches = applyCleanGitBranches e.Options.Pipeline.Target.ExistingOnly = applyExistingOnly + e.Options.Pipeline.DisableChangelog = disableChangelog logrus.Warningln("Deprecated command, please instead use `updatecli pipeline apply`") @@ -74,4 +75,6 @@ func init() { applyCmd.Flags().BoolVar(&applyCleanGitBranches, "clean-git-branches", false, "Remove updatecli working git branches like '--clean-git-branches=true'") applyCmd.Flags().StringArrayVar(&pipelineIds, "pipeline-ids", []string{}, "Filter pipelines to apply by their pipeline IDs, accepted as comma separated list") applyCmd.Flags().StringArrayVar(&labels, "labels", []string{}, "Filter pipelines by their labels, accepted as a comma separated list (key:value)") + + addDisableChangelogFlag(applyCmd, &disableChangelog) } diff --git a/cmd/compose_apply.go b/cmd/compose_apply.go index e5de00c8ad..04444febef 100644 --- a/cmd/compose_apply.go +++ b/cmd/compose_apply.go @@ -53,6 +53,7 @@ var ( e.Options.Pipeline.Target.DryRun = false e.Options.Pipeline.Target.CleanGitBranches = composeApplyCleanGitBranches e.Options.Pipeline.Target.ExistingOnly = composeApplyExistingOnly + e.Options.Pipeline.DisableChangelog = disableChangelog err = run("compose/apply") if err != nil { @@ -77,5 +78,7 @@ func init() { composeApplyCmd.Flags().StringArrayVar(&composeApplyOnlyPolicyIDs, "only-policy-ids", []string{}, "Filter policies to apply by their policy IDs, accepted as a comma separated list") composeApplyCmd.Flags().StringArrayVar(&composeApplyIgnoredPolicyIDs, "ignored-policy-ids", []string{}, "Filter policies to ignore by their policy IDs, accepted as a comma separated list") + addDisableChangelogFlag(composeApplyCmd, &disableChangelog) + composeCmd.AddCommand(composeApplyCmd) } diff --git a/cmd/compose_diff.go b/cmd/compose_diff.go index 66945b60b1..bc7cae3534 100644 --- a/cmd/compose_diff.go +++ b/cmd/compose_diff.go @@ -46,6 +46,7 @@ var ( e.Options.Pipeline.Target.Push = false e.Options.Pipeline.Target.Clean = composeCmdClean e.Options.Pipeline.Target.DryRun = true + e.Options.Pipeline.DisableChangelog = disableChangelog err = run("compose/diff") if err != nil { @@ -65,5 +66,8 @@ func init() { composeDiffCmd.Flags().StringArrayVar(&labels, "labels", []string{}, "Filter pipelines to apply by their labels, accepted as a comma separated list (key:value)") composeDiffCmd.Flags().StringArrayVar(&composeDiffOnlyPolicyIDs, "only-policy-ids", []string{}, "Filter policies to apply by their policy IDs, accepted as a comma separated list") composeDiffCmd.Flags().StringArrayVar(&composeDiffIgnoredPolicyIDs, "ignored-policy-ids", []string{}, "Filter policies to ignore by their policy IDs, accepted as a comma separated list") + + addDisableChangelogFlag(composeDiffCmd, &disableChangelog) + composeCmd.AddCommand(composeDiffCmd) } diff --git a/cmd/diff.go b/cmd/diff.go index 496050e2d2..a50d83e77f 100644 --- a/cmd/diff.go +++ b/cmd/diff.go @@ -35,6 +35,7 @@ var ( e.Options.Pipeline.Target.Push = false e.Options.Pipeline.Target.Clean = diffClean e.Options.Pipeline.Target.DryRun = true + e.Options.Pipeline.DisableChangelog = disableChangelog logrus.Warningln("Deprecated command, please instead use `updatecli pipeline diff`") @@ -57,4 +58,6 @@ func init() { diffCmd.Flags().BoolVar(&disableTLS, "disable-tls", false, "Disable TLS verification like '--disable-tls=true'") diffCmd.Flags().StringArrayVar(&pipelineIds, "pipeline-ids", []string{}, "Filter pipelines to apply by their pipeline IDs, accepted a comma separated list") diffCmd.Flags().StringArrayVar(&labels, "labels", []string{}, "Filter pipelines to apply by their labels, accepted as a comma separated list (key:value)") + + addDisableChangelogFlag(diffCmd, &disableChangelog) } diff --git a/cmd/env.go b/cmd/env.go new file mode 100644 index 0000000000..eed66571c1 --- /dev/null +++ b/cmd/env.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "os" + "strconv" + "strings" + + "github.com/sirupsen/logrus" +) + +const DisableChangelogEnvVar = "UPDATECLI_DISABLE_CHANGELOG" + +// getEnvBoolOrDefault reads a boolean environment variable. +// It returns defaultValue when the variable is unset or invalid. +func getEnvBoolOrDefault(envVar string, defaultValue bool) bool { + value, ok := os.LookupEnv(envVar) + if !ok { + return defaultValue + } + + parsed, err := strconv.ParseBool(strings.TrimSpace(value)) + if err != nil { + logrus.Debugf( + "invalid boolean value for environment variable %q: %q, defaulting to %t", + envVar, + value, + defaultValue, + ) + return defaultValue + } + + return parsed +} diff --git a/cmd/env_test.go b/cmd/env_test.go new file mode 100644 index 0000000000..f10edbf326 --- /dev/null +++ b/cmd/env_test.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "testing" +) + +func TestGetEnvBoolOrDefault(t *testing.T) { + tests := []struct { + name string + envVar string + envValue string + setEnv bool + defaultValue bool + expected bool + }{ + { + name: "not_set_returns_default_true", + envVar: "UNDEFINED_VAR_1", + defaultValue: true, + expected: true, + }, + { + name: "not_set_returns_default_false", + envVar: "UNDEFINED_VAR_2", + defaultValue: false, + expected: false, + }, + { + name: "set_to_true", + envVar: "TEST_VAR_TRUE", + envValue: "true", + setEnv: true, + defaultValue: false, + expected: true, + }, + { + name: "set_to_false", + envVar: "TEST_VAR_FALSE", + envValue: "false", + setEnv: true, + defaultValue: true, + expected: false, + }, + { + name: "set_to_1", + envVar: "TEST_VAR_1", + envValue: "1", + setEnv: true, + defaultValue: false, + expected: true, + }, + { + name: "set_to_0", + envVar: "TEST_VAR_0", + envValue: "0", + setEnv: true, + defaultValue: true, + expected: false, + }, + { + name: "whitespace_trimmed", + envVar: "TEST_VAR_SPACE", + envValue: " true ", + setEnv: true, + defaultValue: false, + expected: true, + }, + { + name: "invalid_returns_default_true", + envVar: "TEST_VAR_INVALID_1", + envValue: "invalid", + setEnv: true, + defaultValue: true, + expected: true, + }, + { + name: "invalid_returns_default_false", + envVar: "TEST_VAR_INVALID_2", + envValue: "maybe", + setEnv: true, + defaultValue: false, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setEnv { + t.Setenv(tt.envVar, tt.envValue) + } + + result := getEnvBoolOrDefault(tt.envVar, tt.defaultValue) + + if result != tt.expected { + t.Errorf("got %v, expected %v", result, tt.expected) + } + }) + } +} diff --git a/cmd/flags.go b/cmd/flags.go new file mode 100644 index 0000000000..c6725062c8 --- /dev/null +++ b/cmd/flags.go @@ -0,0 +1,15 @@ +package cmd + +import "github.com/spf13/cobra" + +// addDisableChangelogFlag registers the shared --disable-changelog flag on the +// provided command, using the value from UPDATECLI_DISABLE_CHANGELOG as the +// default when the flag is not explicitly passed. +func addDisableChangelogFlag(cmd *cobra.Command, dest *bool) { + cmd.Flags().BoolVar( + dest, + "disable-changelog", + getEnvBoolOrDefault(DisableChangelogEnvVar, false), + "Disable changelog retrieval to avoid unnecessary requests (env: "+DisableChangelogEnvVar+")", + ) +} diff --git a/cmd/flags_test.go b/cmd/flags_test.go new file mode 100644 index 0000000000..827bda2f80 --- /dev/null +++ b/cmd/flags_test.go @@ -0,0 +1,128 @@ +package cmd + +import ( + "strings" + "testing" + + "github.com/spf13/cobra" +) + +func TestAddDisableChangelogFlagRegistration(t *testing.T) { + t.Setenv(DisableChangelogEnvVar, "false") + + cmd := &cobra.Command{ + Use: "test", + } + + var disableChangelog bool + addDisableChangelogFlag(cmd, &disableChangelog) + + // Check that flag exists + flag := cmd.Flags().Lookup("disable-changelog") + if flag == nil { + t.Fatal("flag not registered") + } + + // Check flag has help text + if flag.Usage == "" { + t.Error("flag help text is empty") + } + + // Check that env var name appears in help text + if !strings.Contains(flag.Usage, DisableChangelogEnvVar) { + t.Errorf( + "flag help text does not mention env var %q: help text is %q", + DisableChangelogEnvVar, + flag.Usage, + ) + } + + // Check default value is "false" when env var not set + if flag.DefValue != "false" { + t.Errorf( + "flag default value when no env var: got %q, expected %q", + flag.DefValue, + "false", + ) + } +} + +func TestAddDisableChangelogFlagUsesEnvDefault(t *testing.T) { + tests := []struct { + name string + envValue string + expectedDef string + }{ + { + name: "env_var_true", + envValue: "true", + expectedDef: "true", + }, + { + name: "env_var_false", + envValue: "false", + expectedDef: "false", + }, + { + name: "env_var_1", + envValue: "1", + expectedDef: "true", + }, + { + name: "env_var_0", + envValue: "0", + expectedDef: "false", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Setenv(DisableChangelogEnvVar, tt.envValue) + + cmd := &cobra.Command{ + Use: "test", + } + + var disableChangelog bool + addDisableChangelogFlag(cmd, &disableChangelog) + + flag := cmd.Flags().Lookup("disable-changelog") + if flag == nil { + t.Fatal("flag not registered") + } + + if flag.DefValue != tt.expectedDef { + t.Errorf( + "flag default value: got %q, expected %q", + flag.DefValue, + tt.expectedDef, + ) + } + }) + } +} + +func TestDisableChangelogFlagOverridesEnv(t *testing.T) { + t.Setenv(DisableChangelogEnvVar, "true") + + cmd := &cobra.Command{ + Use: "test", + RunE: func(cmd *cobra.Command, args []string) error { + return nil + }, + } + + var disableChangelog bool + addDisableChangelogFlag(cmd, &disableChangelog) + + cmd.SetArgs([]string{"--disable-changelog=false"}) + + err := cmd.Execute() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if disableChangelog { + t.Error("expected flag to override env var") + } +} diff --git a/cmd/pipeline_apply.go b/cmd/pipeline_apply.go index f934e80100..9d1fca39a8 100644 --- a/cmd/pipeline_apply.go +++ b/cmd/pipeline_apply.go @@ -42,6 +42,7 @@ var ( e.Options.Pipeline.Target.DryRun = false e.Options.Pipeline.Target.CleanGitBranches = applyCleanGitBranches e.Options.Pipeline.Target.ExistingOnly = applyExistingOnly + e.Options.Pipeline.DisableChangelog = disableChangelog err = run("pipeline/apply") if err != nil { @@ -67,5 +68,7 @@ func init() { pipelineApplyCmd.Flags().StringArrayVar(&pipelineIds, "pipeline-ids", []string{}, "Filter pipelines to apply by their IDs, accepted as a comma separated list") pipelineApplyCmd.Flags().StringArrayVar(&labels, "labels", []string{}, "Filter pipelines to apply by their labels, accepted as a comma separated list (key:value)") + addDisableChangelogFlag(pipelineApplyCmd, &disableChangelog) + pipelineCmd.AddCommand(pipelineApplyCmd) } diff --git a/cmd/pipeline_diff.go b/cmd/pipeline_diff.go index 9915c5af19..0e6743386e 100644 --- a/cmd/pipeline_diff.go +++ b/cmd/pipeline_diff.go @@ -33,6 +33,7 @@ var ( e.Options.Pipeline.Target.Push = false e.Options.Pipeline.Target.Clean = diffClean e.Options.Pipeline.Target.DryRun = true + e.Options.Pipeline.DisableChangelog = disableChangelog err = run("pipeline/diff") if err != nil { @@ -54,5 +55,7 @@ func init() { pipelineDiffCmd.Flags().StringArrayVar(&pipelineIds, "pipeline-ids", []string{}, "Filter pipelines to apply by their pipeline IDs, accepted a comma separated list") pipelineDiffCmd.Flags().StringArrayVar(&labels, "labels", []string{}, "Filter pipelines to apply by their labels, accepted as a comma separated list (key:value)") + addDisableChangelogFlag(pipelineDiffCmd, &disableChangelog) + pipelineCmd.AddCommand(pipelineDiffCmd) } diff --git a/cmd/root.go b/cmd/root.go index 9cf03ccd40..7e0024b7d1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -38,6 +38,7 @@ var ( verbose bool experimental bool disableTLS bool + disableChangelog bool uniqueTmpDir bool rootCmd = &cobra.Command{ diff --git a/pkg/core/pipeline/options.go b/pkg/core/pipeline/options.go index 1154ea54cd..aef229bfbe 100644 --- a/pkg/core/pipeline/options.go +++ b/pkg/core/pipeline/options.go @@ -8,4 +8,6 @@ import ( type Options struct { Target target.Options + // DisableChangelog disables changelog retrieval for targets. + DisableChangelog bool } diff --git a/pkg/core/pipeline/targets.go b/pkg/core/pipeline/targets.go index 8c85b0d556..351436931f 100644 --- a/pkg/core/pipeline/targets.go +++ b/pkg/core/pipeline/targets.go @@ -36,36 +36,38 @@ func (p *Pipeline) RunTarget(ctx context.Context, id string, sourceIds []string) err = fmt.Errorf("%s", err) } - changelogSourceID := target.Config.SourceID - if changelogSourceID == "" { - switch len(sourceIds) { - case 1: - changelogSourceID = sourceIds[0] - case 0: - // If we have more than one sourceID then we can't define in a reliable way which one to use - // as the order of the sourceIDs is not guaranteed. - default: - logrus.Debugf("Target depends on a too many sources that we can't determine which one to use for the changelog") + if !p.Options.DisableChangelog { + changelogSourceID := target.Config.SourceID + if changelogSourceID == "" { + switch len(sourceIds) { + case 1: + changelogSourceID = sourceIds[0] + case 0: + // If we have more than one sourceID then we can't define in a reliable way which one to use + // as the order of the sourceIDs is not guaranteed. + default: + logrus.Debugf("Target depends on a too many sources that we can't determine which one to use for the changelog") + } } - } - if changelogSourceID != "" { - // Once the source is executed, then it can retrieve its changelog - // Any error means an empty changelog - if source, found := p.Sources[changelogSourceID]; found { - c, err := resource.New(source.Config.ResourceConfig) + if changelogSourceID != "" { + // Once the source is executed, then it can retrieve its changelog + // Any error means an empty changelog + if source, found := p.Sources[changelogSourceID]; found { + c, err := resource.New(source.Config.ResourceConfig) - if err == nil { + if err == nil { - changelogs := c.Changelog(target.Result.Information, source.OriginalOutput) + changelogs := c.Changelog(target.Result.Information, source.OriginalOutput) - if changelogs != nil { - target.Result.Changelogs = *changelogs + if changelogs != nil { + target.Result.Changelogs = *changelogs - logrus.Debugf("%s", changelogs.String()) + logrus.Debugf("%s", changelogs.String()) - } else { - logrus.Debugln("no changelog detected") + } else { + logrus.Debugln("no changelog detected") + } } } } diff --git a/pkg/core/pipeline/targets_test.go b/pkg/core/pipeline/targets_test.go index eabf2e476b..3a91b2b18f 100644 --- a/pkg/core/pipeline/targets_test.go +++ b/pkg/core/pipeline/targets_test.go @@ -8,9 +8,12 @@ import ( "github.com/updatecli/updatecli/pkg/core/config" "github.com/updatecli/updatecli/pkg/core/pipeline/condition" "github.com/updatecli/updatecli/pkg/core/pipeline/resource" + "github.com/updatecli/updatecli/pkg/core/pipeline/source" "github.com/updatecli/updatecli/pkg/core/pipeline/target" + "github.com/updatecli/updatecli/pkg/core/result" "github.com/updatecli/updatecli/pkg/plugins/resources/shell" "github.com/updatecli/updatecli/pkg/plugins/resources/shell/success/exitcode" + updateclihttp "github.com/updatecli/updatecli/pkg/plugins/resources/updateclihttp" ) func TestRunTarget(t *testing.T) { @@ -473,3 +476,53 @@ func TestRunTarget(t *testing.T) { } } + +func TestRunTarget_DisableChangelogSkipsChangelog(t *testing.T) { + p := Pipeline{ + Config: &config.Config{ + Spec: config.Spec{ + Targets: map[string]target.Config{ + "test": { + ResourceConfig: resource.ResourceConfig{ + Kind: "shell", + Name: "test", + Spec: shell.Spec{ + Command: "true", + ChangedIf: shell.SpecChangedIf{ + Kind: "exitcode", + Spec: exitcode.Spec{Warning: 1, Success: 0, Failure: 2}, + }, + }, + }, + SourceID: "source1", + }, + }, + }, + }, + Sources: map[string]source.Source{ + "source1": { + Config: source.Config{ + ResourceConfig: resource.ResourceConfig{ + Kind: "http", + Name: "source1", + Spec: updateclihttp.Spec{Url: "https://example.com"}, + }, + }, + Output: "old-value", + OriginalOutput: "old-value", + Result: &result.Source{Information: "old-value", Result: result.SUCCESS}, + }, + }, + Targets: map[string]target.Target{ + "test": { + Result: &result.Target{}, + }, + }, + } + + p.Options.DisableChangelog = true + + _, _, err := p.RunTarget(context.Background(), "test", []string{"source1"}) + require.NoError(t, err) + require.Empty(t, p.Targets["test"].Result.Changelogs) +}