diff --git a/cmd/apply.go b/cmd/apply.go index dc9982b8..c79e97cb 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -72,6 +72,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.StringVar(&applyOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default), 'helm-legacy' (Helm v4 only), or 'kubedog'") f.IntVar(&applyOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`) f.BoolVar(&applyOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking") + f.StringVar(&applyOptions.Description, "description", "", `Set description for all releases. If set, overridesdescriptions in helmfile.yaml. Will be passed to "helm upgrade --description"`) return cmd } diff --git a/cmd/sync.go b/cmd/sync.go index fdedbd72..5d180103 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -57,6 +57,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.StringVar(&syncOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default), 'helm-legacy' (Helm v4 only), or 'kubedog'") f.IntVar(&syncOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`) f.BoolVar(&syncOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking") + f.StringVar(&syncOptions.Description, "description", "", `Set description for all releases. If set, overrides descriptions in helmfile.yaml. Will be passed to "helm upgrade --description"`) return cmd } diff --git a/pkg/app/app.go b/pkg/app/app.go index 66748a44..a4b40b88 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1798,6 +1798,7 @@ Do you really want to apply? TrackMode: c.TrackMode(), TrackTimeout: c.TrackTimeout(), TrackLogs: c.TrackLogs(), + Description: c.Description(), } return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts) })) @@ -2267,6 +2268,7 @@ Do you really want to sync? TrackMode: c.TrackMode(), TrackTimeout: c.TrackTimeout(), TrackLogs: c.TrackLogs(), + Description: c.Description(), } return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), opts) })) diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index da895b46..21ec2d4e 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2610,6 +2610,10 @@ func (a applyConfig) TrackLogs() bool { return a.trackLogs } +func (a applyConfig) Description() string { + return "" +} + type depsConfig struct { skipRepos bool includeTransitiveNeeds bool diff --git a/pkg/app/config.go b/pkg/app/config.go index 024e7cbe..9b92e498 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -92,6 +92,8 @@ type ApplyConfigProvider interface { TrackTimeout() int TrackLogs() bool + Description() string + concurrencyConfig interactive loggingConfig @@ -129,6 +131,8 @@ type SyncConfigProvider interface { TrackTimeout() int TrackLogs() bool + Description() string + DAGConfig concurrencyConfig diff --git a/pkg/config/apply.go b/pkg/config/apply.go index 872844f7..8580bba2 100644 --- a/pkg/config/apply.go +++ b/pkg/config/apply.go @@ -88,6 +88,8 @@ type ApplyOptions struct { TrackTimeout int // TrackLogs enables log streaming with kubedog TrackLogs bool + // Description is the description that will be passed to helm upgrade --description + Description string } // NewApply creates a new Apply @@ -314,6 +316,11 @@ func (a *ApplyImpl) TrackLogs() bool { return a.ApplyOptions.TrackLogs } +// Description returns the description. +func (a *ApplyImpl) Description() string { + return a.ApplyOptions.Description +} + func (a *ApplyImpl) ValidateConfig() error { validTrackModes := []string{"helm", "helm-legacy", "kubedog"} if a.ApplyOptions.TrackMode != "" && !slices.Contains(validTrackModes, a.ApplyOptions.TrackMode) { diff --git a/pkg/config/sync.go b/pkg/config/sync.go index 79a90eb6..8124108b 100644 --- a/pkg/config/sync.go +++ b/pkg/config/sync.go @@ -59,6 +59,8 @@ type SyncOptions struct { TrackTimeout int // TrackLogs enables log streaming with kubedog TrackLogs bool + // Description is the description that will be passed to helm upgrade --description + Description string } // NewSyncOptions creates a new Apply @@ -214,6 +216,11 @@ func (t *SyncImpl) TrackLogs() bool { return t.SyncOptions.TrackLogs } +// Description returns the description. +func (t *SyncImpl) Description() string { + return t.SyncOptions.Description +} + func (t *SyncImpl) ValidateConfig() error { validTrackModes := []string{"helm", "helm-legacy", "kubedog"} if t.SyncOptions.TrackMode != "" && !slices.Contains(validTrackModes, t.SyncOptions.TrackMode) { diff --git a/pkg/state/state.go b/pkg/state/state.go index 56e4a361..cec6396d 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -353,7 +353,10 @@ type ReleaseSpec struct { UnitTests []string `yaml:"unitTests,omitempty"` // Name is the name of this release - Name string `yaml:"name,omitempty"` + Name string `yaml:"name,omitempty"` + + // Description is the description for this release that will be passed to helm upgrade with --description flag + Description string `yaml:"description,omitempty"` Namespace string `yaml:"namespace,omitempty"` Labels map[string]string `yaml:"labels,omitempty"` Values []any `yaml:"values,omitempty"` @@ -909,6 +912,7 @@ type SyncOpts struct { TrackMode string TrackTimeout int TrackLogs bool + Description string } type SyncOpt interface{ Apply(*SyncOpts) } @@ -3394,6 +3398,28 @@ func (st *HelmState) appendChartDownloadFlags(flags []string, release *ReleaseSp return flags } +// appendDescriptionFlags appends the helm command-line flag for release description +// Command line takes precedence over config file +func (st *HelmState) appendDescriptionFlags(flags []string, release *ReleaseSpec, opt *SyncOpts, helm helmexec.Interface) ([]string, error) { + description := release.Description + if opt != nil && opt.Description != "" { + description = opt.Description + } + + if description != "" { + if !helm.IsVersionAtLeast("3.3.0") { + // Determine error message based on source + if opt != nil && opt.Description != "" { + return nil, fmt.Errorf("--description flag requires Helm 3.3.0 or greater") + } + return nil, fmt.Errorf("releases[].description requires Helm 3.3.0 or greater") + } + flags = append(flags, "--description", description) + } + + return flags, nil +} + func (st *HelmState) needsPlainHttp(release *ReleaseSpec, repo *RepositorySpec) bool { var repoPlainHttp, relPlainHttp bool if repo != nil { @@ -3523,6 +3549,11 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp flags = st.appendPostRenderFlags(flags, release, postRenderer, helm) + flags, err := st.appendDescriptionFlags(flags, release, opt, helm) + if err != nil { + return nil, nil, err + } + var postRendererArgs []string if opt != nil { postRendererArgs = opt.PostRendererArgs diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index b8e62573..98c1f258 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -908,6 +908,188 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { "--namespace", "test-namespace", }, }, + { + name: "description-from-release", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + Description: "Release description from config", + }, + syncOpts: &SyncOpts{}, + want: []string{ + "--version", "0.1", + "--description", "Release description from config", + "--namespace", "test-namespace", + }, + }, + { + name: "description-from-cli", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + }, + syncOpts: &SyncOpts{ + Description: "CLI description from --description flag", + }, + want: []string{ + "--version", "0.1", + "--description", "CLI description from --description flag", + "--namespace", "test-namespace", + }, + }, + { + name: "description-cli-overrides-release", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + Description: "Release description from config", + }, + syncOpts: &SyncOpts{ + Description: "CLI description overrides config", + }, + want: []string{ + "--version", "0.1", + "--description", "CLI description overrides config", + "--namespace", "test-namespace", + }, + }, + { + name: "description-empty-string-not-passed", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + Description: "", + }, + syncOpts: &SyncOpts{ + Description: "", + }, + want: []string{ + "--version", "0.1", + "--namespace", "test-namespace", + }, + }, + { + name: "description-from-config-unsupported-version-3.1.0", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.1.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + Description: "Release description from config", + }, + syncOpts: &SyncOpts{}, + wantErr: "releases[].description requires Helm 3.3.0 or greater", + }, + { + name: "description-from-config-unsupported-version-3.2.4", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.2.4"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + Description: "Release description from config", + }, + syncOpts: &SyncOpts{}, + wantErr: "releases[].description requires Helm 3.3.0 or greater", + }, + { + name: "description-from-cli-unsupported-version", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.2.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + }, + syncOpts: &SyncOpts{ + Description: "CLI description from --description flag", + }, + wantErr: "--description flag requires Helm 3.3.0 or greater", + }, + { + name: "description-empty-on-old-version", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.1.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + // No description set + }, + syncOpts: &SyncOpts{}, + want: []string{ + "--version", "0.1", + "--namespace", "test-namespace", + // No --description flag should appear + }, + }, + { + name: "description-from-config-supported-version-3.3.0", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("3.3.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + Description: "Release description from config", + }, + syncOpts: &SyncOpts{}, + want: []string{ + "--version", "0.1", + "--description", "Release description from config", + "--namespace", "test-namespace", + }, + }, } for i := range tests { tt := tests[i] diff --git a/pkg/state/temp_test.go b/pkg/state/temp_test.go index 0dcc6153..7ea16939 100644 --- a/pkg/state/temp_test.go +++ b/pkg/state/temp_test.go @@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) { run(testcase{ subject: "baseline", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, - want: "foo-values-5bc9c89c6b", + want: "foo-values-6ccb848dcd", }) run(testcase{ subject: "different bytes content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: []byte(`{"k":"v"}`), - want: "foo-values-7bf9c8bcdf", + want: "foo-values-5bcbbc4c85", }) run(testcase{ subject: "different map content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: map[string]any{"k": "v"}, - want: "foo-values-65694d8947", + want: "foo-values-7c6468f955", }) run(testcase{ subject: "different chart", release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, - want: "foo-values-856c5f7dd5", + want: "foo-values-8645f5847f", }) run(testcase{ subject: "different name", release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, - want: "bar-values-fff55fbf5", + want: "bar-values-54bd8c865", }) run(testcase{ subject: "specific ns", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, - want: "myns-foo-values-6bfbb74765", + want: "myns-foo-values-b4849b445", }) for id, n := range ids {