diff --git a/pkg/state/state.go b/pkg/state/state.go index 431864ea..7eea6234 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -190,6 +190,8 @@ type HelmSpec struct { Atomic bool `yaml:"atomic"` // CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command CleanupOnFail bool `yaml:"cleanupOnFail,omitempty"` + // ForceConflicts, when set to true, force server-side apply changes against conflicts (Helm 4 only) + ForceConflicts bool `yaml:"forceConflicts"` // HistoryMax, limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) HistoryMax *int `yaml:"historyMax,omitempty"` // CreateNamespace, when set to true (default), --create-namespace is passed to helm on install/upgrade @@ -306,6 +308,8 @@ type ReleaseSpec struct { Atomic *bool `yaml:"atomic,omitempty"` // CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command CleanupOnFail *bool `yaml:"cleanupOnFail,omitempty"` + // ForceConflicts, when set to true, force server-side apply changes against conflicts (Helm 4 only) + ForceConflicts *bool `yaml:"forceConflicts,omitempty"` // HistoryMax, limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) HistoryMax *int `yaml:"historyMax,omitempty"` // Condition, when set, evaluate the mapping specified in this string to a boolean which decides whether or not to process the release @@ -3451,7 +3455,18 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp flags = append(flags, st.timeoutFlags(release, opt)...) - if (release.Force != nil && *release.Force) || (release.Force == nil && st.HelmDefaults.Force) { + forceEnabled := (release.Force != nil && *release.Force) || (release.Force == nil && st.HelmDefaults.Force) + forceConflictsEnabled := (release.ForceConflicts != nil && *release.ForceConflicts) || (release.ForceConflicts == nil && st.HelmDefaults.ForceConflicts) + + if forceConflictsEnabled && !helm.IsHelm4() { + return nil, nil, fmt.Errorf("releases[].forceConflicts requires Helm 4 or greater") + } + + if forceEnabled && forceConflictsEnabled { + return nil, nil, fmt.Errorf("force and forceConflicts are mutually exclusive") + } + + if forceEnabled { if helm.IsHelm4() { flags = append(flags, "--force-replace") } else { @@ -3459,6 +3474,10 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp } } + if forceConflictsEnabled { + flags = append(flags, "--force-conflicts") + } + if release.RecreatePods != nil && *release.RecreatePods || release.RecreatePods == nil && st.HelmDefaults.RecreatePods { flags = append(flags, "--recreate-pods") } diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 9fc5d893..8ec1cb5e 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -376,6 +376,76 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { "--namespace", "test-namespace", }, }, + { + name: "force-conflicts-helm4", + defaults: HelmSpec{ + ForceConflicts: false, + CreateNamespace: &disable, + }, + version: semver.MustParse("4.0.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + ForceConflicts: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--force-conflicts", + "--namespace", "test-namespace", + }, + }, + { + name: "force-conflicts-from-default-helm4", + defaults: HelmSpec{ + ForceConflicts: true, + CreateNamespace: &disable, + }, + version: semver.MustParse("4.0.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--force-conflicts", + "--namespace", "test-namespace", + }, + }, + { + name: "force-conflicts-helm3-error", + defaults: HelmSpec{ + CreateNamespace: &disable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + ForceConflicts: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + wantErr: "releases[].forceConflicts requires Helm 4 or greater", + }, + { + name: "force-and-force-conflicts-mutually-exclusive-helm4", + defaults: HelmSpec{ + CreateNamespace: &disable, + }, + version: semver.MustParse("4.0.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Force: &enable, + ForceConflicts: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + wantErr: "force and forceConflicts are mutually exclusive", + }, { name: "recreate-pods", defaults: HelmSpec{