mirror of
https://github.com/helmfile/helmfile.git
synced 2026-10-17 21:37:14 +02:00
feat: add support for helm 4 --server-side upgrade flag (#2641)
* feat: add support for helm 4 --server-side upgrade flag Add support for the helm 4 upgrade flag --server-side which accepts "true", "false", or "auto" (default "auto"). This allows users to explicitly control server-side apply behavior, which is needed for releases originally installed with Helm 3 and being managed with Helm 4. The flag can be configured via: - CLI: --server-side flag on sync, apply, and diff commands - helmDefaults.serverSide in helmfile.yaml - releases[].serverSide per-release override Precedence: release-level > CLI flag > helmDefaults. Errors are returned when serverSide is set but running Helm 3, or when an invalid value is provided. Closes #2640 Signed-off-by: yxxhero <aiopsclub@163.com> * test: update TestGenerateID expected hashes for new ServerSide field Adding ServerSide *string to ReleaseSpec changes spew's %#v output and shifts the FNV hash used by generateValuesID. Update the hard-coded want values to the new deterministic hashes. Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
@@ -354,6 +354,44 @@ func (st *HelmState) appendTakeOwnershipFlagsForUpgrade(flags []string, helm hel
|
||||
return flags
|
||||
}
|
||||
|
||||
// validServerSideValues are the allowed values for the helm 4 --server-side flag.
|
||||
var validServerSideValues = map[string]struct{}{"true": {}, "false": {}, "auto": {}}
|
||||
|
||||
// appendServerSideFlagsForUpgrade appends the helm 4 --server-side flag when appropriate.
|
||||
// Precedence: release-level > CLI flag > helmDefaults.
|
||||
func (st *HelmState) appendServerSideFlagsForUpgrade(flags []string, helm helmexec.Interface, release *ReleaseSpec, serverSide string) ([]string, error) {
|
||||
if !helm.IsHelm4() {
|
||||
// --server-side with a string value is helm 4 only. Guard against misconfiguration
|
||||
// when the user is running helm 3.
|
||||
if release.ServerSide != nil && *release.ServerSide != "" {
|
||||
return nil, fmt.Errorf("serverSide requires Helm 4 or greater (set via releases[].serverSide)")
|
||||
}
|
||||
if st.HelmDefaults.ServerSide != nil && *st.HelmDefaults.ServerSide != "" {
|
||||
return nil, fmt.Errorf("serverSide requires Helm 4 or greater (set via helmDefaults.serverSide)")
|
||||
}
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
var value string
|
||||
switch {
|
||||
case release.ServerSide != nil && *release.ServerSide != "":
|
||||
value = *release.ServerSide
|
||||
case serverSide != "":
|
||||
value = serverSide
|
||||
case st.HelmDefaults.ServerSide != nil && *st.HelmDefaults.ServerSide != "":
|
||||
value = *st.HelmDefaults.ServerSide
|
||||
default:
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
if _, ok := validServerSideValues[value]; !ok {
|
||||
return nil, fmt.Errorf("invalid serverSide value %q: must be \"true\", \"false\", or \"auto\"", value)
|
||||
}
|
||||
|
||||
flags = append(flags, "--server-side", value)
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
// append show-only flags to helm flags
|
||||
func (st *HelmState) appendShowOnlyFlags(flags []string, showOnly []string) []string {
|
||||
showOnlyFlags := []string{}
|
||||
|
||||
@@ -572,3 +572,157 @@ func TestFormatLabels(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func TestAppendServerSideFlagsForUpgrade(t *testing.T) {
|
||||
type args struct {
|
||||
flags []string
|
||||
helm helmexec.Interface
|
||||
helmSpec HelmSpec
|
||||
opt *SyncOpts
|
||||
release *ReleaseSpec
|
||||
expected []string
|
||||
wantErr bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
}{
|
||||
{
|
||||
name: "no server-side when not configured",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
release: &ReleaseSpec{},
|
||||
opt: &SyncOpts{},
|
||||
expected: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "server-side from cmd flag",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
release: &ReleaseSpec{},
|
||||
opt: &SyncOpts{ServerSide: "true"},
|
||||
expected: []string{"--server-side", "true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "server-side from release",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
release: &ReleaseSpec{ServerSide: strPtr("false")},
|
||||
opt: &SyncOpts{},
|
||||
expected: []string{"--server-side", "false"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "server-side from helmDefaults",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
helmSpec: HelmSpec{ServerSide: strPtr("auto")},
|
||||
release: &ReleaseSpec{},
|
||||
opt: &SyncOpts{},
|
||||
expected: []string{"--server-side", "auto"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "release-level overrides cmd flag",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
release: &ReleaseSpec{ServerSide: strPtr("true")},
|
||||
opt: &SyncOpts{ServerSide: "false"},
|
||||
expected: []string{"--server-side", "true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "cmd flag overrides helmDefaults",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
helmSpec: HelmSpec{ServerSide: strPtr("false")},
|
||||
release: &ReleaseSpec{},
|
||||
opt: &SyncOpts{ServerSide: "true"},
|
||||
expected: []string{"--server-side", "true"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "helm 3 with no config is ignored",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewVersionHelmExec("3.17.0"),
|
||||
release: &ReleaseSpec{},
|
||||
opt: &SyncOpts{},
|
||||
expected: []string{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "helm 3 with release-level config errors",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewVersionHelmExec("3.17.0"),
|
||||
release: &ReleaseSpec{ServerSide: strPtr("true")},
|
||||
opt: &SyncOpts{},
|
||||
expected: []string{},
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "helm 3 with helmDefaults config errors",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewVersionHelmExec("3.17.0"),
|
||||
helmSpec: HelmSpec{ServerSide: strPtr("true")},
|
||||
release: &ReleaseSpec{},
|
||||
opt: &SyncOpts{},
|
||||
expected: []string{},
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid value errors",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
release: &ReleaseSpec{},
|
||||
opt: &SyncOpts{ServerSide: "yes"},
|
||||
expected: []string{},
|
||||
wantErr: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty release-level value falls through to cmd flag",
|
||||
args: args{
|
||||
flags: []string{},
|
||||
helm: testutil.NewHelmExec(true),
|
||||
release: &ReleaseSpec{ServerSide: strPtr("")},
|
||||
opt: &SyncOpts{ServerSide: "auto"},
|
||||
expected: []string{"--server-side", "auto"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
st := &HelmState{}
|
||||
st.HelmDefaults = tt.args.helmSpec
|
||||
optServerSide := ""
|
||||
if tt.args.opt != nil {
|
||||
optServerSide = tt.args.opt.ServerSide
|
||||
}
|
||||
got, err := st.appendServerSideFlagsForUpgrade(tt.args.flags, tt.args.helm, tt.args.release, optServerSide)
|
||||
if tt.args.wantErr {
|
||||
require.Error(t, err, "appendServerSideFlagsForUpgrade() should return error")
|
||||
} else {
|
||||
require.NoError(t, err, "appendServerSideFlagsForUpgrade() should not return error")
|
||||
require.Equalf(t, tt.args.expected, got, "appendServerSideFlagsForUpgrade() = %v, want %v", got, tt.args.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +250,8 @@ type HelmSpec struct {
|
||||
SyncReleaseLabels *bool `yaml:"syncReleaseLabels,omitempty"`
|
||||
// TakeOwnership is true if the helmfile should take ownership of the release
|
||||
TakeOwnership *bool `yaml:"takeOwnership,omitempty"`
|
||||
// ServerSide controls the helm 4 --server-side flag for upgrade. Must be "true", "false", or "auto".
|
||||
ServerSide *string `yaml:"serverSide,omitempty"`
|
||||
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
|
||||
TrackMode string `yaml:"trackMode,omitempty"`
|
||||
}
|
||||
@@ -477,6 +479,8 @@ type ReleaseSpec struct {
|
||||
SyncReleaseLabels *bool `yaml:"syncReleaseLabels,omitempty"`
|
||||
// TakeOwnership is true if release should take ownership of resources
|
||||
TakeOwnership *bool `yaml:"takeOwnership,omitempty"`
|
||||
// ServerSide controls the helm 4 --server-side flag for this release. Must be "true", "false", or "auto".
|
||||
ServerSide *string `yaml:"serverSide,omitempty"`
|
||||
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
|
||||
TrackMode string `yaml:"trackMode,omitempty"`
|
||||
// TrackTimeout specifies timeout for kubedog tracking (in seconds)
|
||||
@@ -965,6 +969,7 @@ type SyncOpts struct {
|
||||
SyncArgs string
|
||||
HideNotes bool
|
||||
TakeOwnership bool
|
||||
ServerSide string
|
||||
TrackMode string
|
||||
TrackTimeout int
|
||||
TrackLogs bool
|
||||
@@ -2908,6 +2913,7 @@ type DiffOpts struct {
|
||||
SuppressOutputLineRegex []string
|
||||
SkipSchemaValidation bool
|
||||
TakeOwnership bool
|
||||
ServerSide string
|
||||
// DetectedKubeVersion is the Kubernetes version detected from the cluster.
|
||||
// This is used when kubeVersion is not specified in helmfile.yaml
|
||||
DetectedKubeVersion string
|
||||
@@ -3766,10 +3772,12 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
||||
postRenderer := ""
|
||||
syncReleaseLabels := false
|
||||
takeOwnership := false
|
||||
serverSide := ""
|
||||
if opt != nil {
|
||||
postRenderer = opt.PostRenderer
|
||||
syncReleaseLabels = opt.SyncReleaseLabels
|
||||
takeOwnership = opt.TakeOwnership
|
||||
serverSide = opt.ServerSide
|
||||
}
|
||||
|
||||
flags = st.appendConnectionFlags(flags, release)
|
||||
@@ -3808,6 +3816,13 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
||||
// append take-ownership flag
|
||||
flags = st.appendTakeOwnershipFlagsForUpgrade(flags, helm, release, takeOwnership)
|
||||
|
||||
// append server-side flag
|
||||
var ssErr error
|
||||
flags, ssErr = st.appendServerSideFlagsForUpgrade(flags, helm, release, serverSide)
|
||||
if ssErr != nil {
|
||||
return nil, nil, ssErr
|
||||
}
|
||||
|
||||
flags = st.appendExtraSyncFlags(flags, opt)
|
||||
|
||||
common, clean, err := st.namespaceAndValuesFlags(helm, release, workerIndex)
|
||||
@@ -3992,8 +4007,10 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec,
|
||||
}
|
||||
|
||||
takeOwnership := false
|
||||
serverSide := ""
|
||||
if opt != nil {
|
||||
takeOwnership = opt.TakeOwnership
|
||||
serverSide = opt.ServerSide
|
||||
}
|
||||
|
||||
flags, err = st.appendTakeOwnershipFlagsForDiff(flags, release, takeOwnership, pluginsDir)
|
||||
@@ -4001,6 +4018,12 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec,
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// append server-side flag
|
||||
flags, err = st.appendServerSideFlagsForUpgrade(flags, helm, release, serverSide)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
common, files, err := st.namespaceAndValuesFlags(helm, release, workerIndex)
|
||||
if err != nil {
|
||||
return nil, files, err
|
||||
|
||||
@@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
|
||||
run(testcase{
|
||||
subject: "baseline",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
want: "foo-values-7f6f8d74dd",
|
||||
want: "foo-values-56c9b878cf",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different bytes content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: []byte(`{"k":"v"}`),
|
||||
want: "foo-values-5fc74c864c",
|
||||
want: "foo-values-55d46b7dd4",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different map content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: map[string]any{"k": "v"},
|
||||
want: "foo-values-77df88dd65",
|
||||
want: "foo-values-8c574f646",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different chart",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
|
||||
want: "foo-values-77c96457f7",
|
||||
want: "foo-values-664cdfb57d",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different name",
|
||||
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
|
||||
want: "bar-values-6695f7ff4c",
|
||||
want: "bar-values-fbb5fd45b",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "specific ns",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
|
||||
want: "myns-foo-values-9b9484d4c",
|
||||
want: "myns-foo-values-7db84f99b",
|
||||
})
|
||||
|
||||
for id, n := range ids {
|
||||
|
||||
Reference in New Issue
Block a user