feat: Add support for --wait-retries flag.

This change wires up waitRetries option to set the helm --wait-retries flag.

--wait-retries was added in helm 3.15.0 and makes waiting more robust to registry errors.
https://github.com/helm/helm/commit/fc74964
https://github.com/helm/helm/releases/tag/v3.15.0

Resolves #1522

Signed-off-by: Connor Hindley <connor.hindley@tanium.com>
This commit is contained in:
Connor Hindley 2025-02-11 14:31:21 -06:00
parent 2a04e5deef
commit 5a68e553c5
No known key found for this signature in database
GPG Key ID: 3293949A7BFEEB6C
8 changed files with 100 additions and 3 deletions

View File

@ -385,6 +385,7 @@ func (a *App) Sync(c SyncConfigProvider) error {
SkipRefresh: c.SkipRefresh(), SkipRefresh: c.SkipRefresh(),
SkipDeps: c.SkipDeps(), SkipDeps: c.SkipDeps(),
Wait: c.Wait(), Wait: c.Wait(),
WaitRetries: c.WaitRetries(),
WaitForJobs: c.WaitForJobs(), WaitForJobs: c.WaitForJobs(),
IncludeCRDs: &includeCRDs, IncludeCRDs: &includeCRDs,
IncludeTransitiveNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeNeeds(),
@ -419,6 +420,7 @@ func (a *App) Apply(c ApplyConfigProvider) error {
SkipRefresh: c.SkipRefresh(), SkipRefresh: c.SkipRefresh(),
SkipDeps: c.SkipDeps(), SkipDeps: c.SkipDeps(),
Wait: c.Wait(), Wait: c.Wait(),
WaitRetries: c.WaitRetries(),
WaitForJobs: c.WaitForJobs(), WaitForJobs: c.WaitForJobs(),
IncludeCRDs: &includeCRDs, IncludeCRDs: &includeCRDs,
SkipCleanup: c.RetainValuesFiles() || c.SkipCleanup(), SkipCleanup: c.RetainValuesFiles() || c.SkipCleanup(),
@ -1546,6 +1548,7 @@ Do you really want to apply?
SkipCleanup: c.RetainValuesFiles() || c.SkipCleanup(), SkipCleanup: c.RetainValuesFiles() || c.SkipCleanup(),
SkipCRDs: c.SkipCRDs(), SkipCRDs: c.SkipCRDs(),
Wait: c.Wait(), Wait: c.Wait(),
WaitRetries: c.WaitRetries(),
WaitForJobs: c.WaitForJobs(), WaitForJobs: c.WaitForJobs(),
ReuseValues: c.ReuseValues(), ReuseValues: c.ReuseValues(),
ResetValues: c.ResetValues(), ResetValues: c.ResetValues(),
@ -1943,6 +1946,7 @@ Do you really want to sync?
Set: c.Set(), Set: c.Set(),
SkipCRDs: c.SkipCRDs(), SkipCRDs: c.SkipCRDs(),
Wait: c.Wait(), Wait: c.Wait(),
WaitRetries: c.WaitRetries(),
WaitForJobs: c.WaitForJobs(), WaitForJobs: c.WaitForJobs(),
ReuseValues: c.ReuseValues(), ReuseValues: c.ReuseValues(),
ResetValues: c.ResetValues(), ResetValues: c.ResetValues(),

View File

@ -2244,6 +2244,7 @@ type applyConfig struct {
diffArgs string diffArgs string
logger *zap.SugaredLogger logger *zap.SugaredLogger
wait bool wait bool
waitRetries int
waitForJobs bool waitForJobs bool
reuseValues bool reuseValues bool
postRenderer string postRenderer string
@ -2272,6 +2273,10 @@ func (a applyConfig) Wait() bool {
return a.wait return a.wait
} }
func (a applyConfig) WaitRetries() int {
return a.waitRetries
}
func (a applyConfig) WaitForJobs() bool { func (a applyConfig) WaitForJobs() bool {
return a.waitForJobs return a.waitForJobs
} }

View File

@ -63,6 +63,7 @@ type ApplyConfigProvider interface {
SkipDeps() bool SkipDeps() bool
SkipRefresh() bool SkipRefresh() bool
Wait() bool Wait() bool
WaitRetries() int
WaitForJobs() bool WaitForJobs() bool
IncludeTests() bool IncludeTests() bool
@ -113,6 +114,7 @@ type SyncConfigProvider interface {
SkipDeps() bool SkipDeps() bool
SkipRefresh() bool SkipRefresh() bool
Wait() bool Wait() bool
WaitRetries() int
WaitForJobs() bool WaitForJobs() bool
SyncArgs() string SyncArgs() string

View File

@ -50,6 +50,8 @@ type ApplyOptions struct {
SuppressDiff bool SuppressDiff bool
// Wait is true if the helm command should wait for the release to be deployed // Wait is true if the helm command should wait for the release to be deployed
Wait bool Wait bool
// WaitRetries is the number of times to retry waiting for the release to be deployed
WaitRetries int
// WaitForJobs is true if the helm command should wait for the jobs to be completed // WaitForJobs is true if the helm command should wait for the jobs to be completed
WaitForJobs bool WaitForJobs bool
// Propagate '--skipSchemaValidation' to helmv3 template and helm install // Propagate '--skipSchemaValidation' to helmv3 template and helm install
@ -213,6 +215,11 @@ func (a *ApplyImpl) Wait() bool {
return a.ApplyOptions.Wait return a.ApplyOptions.Wait
} }
// WaitRetries returns the wait retries.
func (a *ApplyImpl) WaitRetries() int {
return a.ApplyOptions.WaitRetries
}
// WaitForJobs returns the wait for jobs. // WaitForJobs returns the wait for jobs.
func (a *ApplyImpl) WaitForJobs() bool { func (a *ApplyImpl) WaitForJobs() bool {
return a.ApplyOptions.WaitForJobs return a.ApplyOptions.WaitForJobs

View File

@ -20,6 +20,8 @@ type SyncOptions struct {
SkipCRDs bool SkipCRDs bool
// Wait is the wait flag // Wait is the wait flag
Wait bool Wait bool
// WaitRetries is the wait retries flag
WaitRetries int
// WaitForJobs is the wait for jobs flag // WaitForJobs is the wait for jobs flag
WaitForJobs bool WaitForJobs bool
// ReuseValues is true if the helm command should reuse the values // ReuseValues is true if the helm command should reuse the values
@ -110,6 +112,11 @@ func (t *SyncImpl) Wait() bool {
return t.SyncOptions.Wait return t.SyncOptions.Wait
} }
// WaitRetries returns the wait retries
func (t *SyncImpl) WaitRetries() int {
return t.SyncOptions.WaitRetries
}
// WaitForJobs returns the wait for jobs // WaitForJobs returns the wait for jobs
func (t *SyncImpl) WaitForJobs() bool { func (t *SyncImpl) WaitForJobs() bool {
return t.SyncOptions.WaitForJobs return t.SyncOptions.WaitForJobs

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"github.com/helmfile/chartify" "github.com/helmfile/chartify"
@ -105,15 +106,31 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec
return flags return flags
} }
func (st *HelmState) appendWaitFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string { func (st *HelmState) appendWaitFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, ops *SyncOpts) []string {
var hasWait bool
switch { switch {
case release.Wait != nil && *release.Wait: case release.Wait != nil && *release.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
case ops != nil && ops.Wait: case ops != nil && ops.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
case release.Wait == nil && st.HelmDefaults.Wait: case release.Wait == nil && st.HelmDefaults.Wait:
hasWait = true
flags = append(flags, "--wait") flags = append(flags, "--wait")
} }
// see https://github.com/helm/helm/releases/tag/v3.15.0
// https://github.com/helm/helm/commit/fc74964
if hasWait && helm.IsVersionAtLeast("3.15.0") {
switch {
case release.WaitRetries != nil && *release.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(*release.WaitRetries))
case ops != nil && ops.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(ops.WaitRetries))
case release.WaitRetries == nil && st.HelmDefaults.WaitRetries > 0:
flags = append(flags, "--wait-retries", strconv.Itoa(st.HelmDefaults.WaitRetries))
}
}
return flags return flags
} }

View File

@ -76,13 +76,16 @@ func TestAppendWaitFlags(t *testing.T) {
name string name string
release *ReleaseSpec release *ReleaseSpec
syncOpts *SyncOpts syncOpts *SyncOpts
helm helmexec.Interface
helmSpec HelmSpec helmSpec HelmSpec
expected []string expected []string
}{ }{
// --wait
{ {
name: "release wait", name: "release wait",
release: &ReleaseSpec{Wait: &[]bool{true}[0]}, release: &ReleaseSpec{Wait: &[]bool{true}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -90,6 +93,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "cli flags wait", name: "cli flags wait",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{Wait: true}, syncOpts: &SyncOpts{Wait: true},
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{}, helmSpec: HelmSpec{},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -97,6 +101,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "helm defaults wait", name: "helm defaults wait",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -104,6 +109,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "release wait false", name: "release wait false",
release: &ReleaseSpec{Wait: &[]bool{false}[0]}, release: &ReleaseSpec{Wait: &[]bool{false}[0]},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{}, expected: []string{},
}, },
@ -111,6 +117,7 @@ func TestAppendWaitFlags(t *testing.T) {
name: "cli flags wait false", name: "cli flags wait false",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: &SyncOpts{}, syncOpts: &SyncOpts{},
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: true}, helmSpec: HelmSpec{Wait: true},
expected: []string{"--wait"}, expected: []string{"--wait"},
}, },
@ -118,16 +125,58 @@ func TestAppendWaitFlags(t *testing.T) {
name: "helm defaults wait false", name: "helm defaults wait false",
release: &ReleaseSpec{}, release: &ReleaseSpec{},
syncOpts: nil, syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{Wait: false}, helmSpec: HelmSpec{Wait: false},
expected: []string{}, expected: []string{},
}, },
// --wait-retries
{
name: "release wait and retry unsupported",
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.11.0"),
helmSpec: HelmSpec{},
expected: []string{"--wait"},
},
{
name: "release wait and retry supported",
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{},
expected: []string{"--wait", "--wait-retries", "1"},
},
{
name: "no wait retry",
release: &ReleaseSpec{WaitRetries: &[]int{1}[0]},
syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{},
expected: []string{},
},
{
name: "cli flags wait and retry",
release: &ReleaseSpec{},
syncOpts: &SyncOpts{Wait: true, WaitRetries: 2},
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{},
expected: []string{"--wait", "--wait-retries", "2"},
},
{
name: "helm defaults wait retry",
release: &ReleaseSpec{},
syncOpts: nil,
helm: testutil.NewVersionHelmExec("3.15.0"),
helmSpec: HelmSpec{Wait: true, WaitRetries: 3},
expected: []string{"--wait", "--wait-retries", "3"},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
st := &HelmState{} st := &HelmState{}
st.HelmDefaults = tt.helmSpec st.HelmDefaults = tt.helmSpec
got := st.appendWaitFlags([]string{}, tt.release, tt.syncOpts) got := st.appendWaitFlags([]string{}, tt.helm, tt.release, tt.syncOpts)
require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected) require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected)
}) })
} }

View File

@ -167,6 +167,8 @@ type HelmSpec struct {
Devel bool `yaml:"devel"` Devel bool `yaml:"devel"`
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful // Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
Wait bool `yaml:"wait"` Wait bool `yaml:"wait"`
// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
WaitRetries int `yaml:"waitRetries"`
// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout // WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
WaitForJobs bool `yaml:"waitForJobs"` WaitForJobs bool `yaml:"waitForJobs"`
// Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) // Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
@ -262,6 +264,8 @@ type ReleaseSpec struct {
Devel *bool `yaml:"devel,omitempty"` Devel *bool `yaml:"devel,omitempty"`
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful // Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
Wait *bool `yaml:"wait,omitempty"` Wait *bool `yaml:"wait,omitempty"`
// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
WaitRetries *int `yaml:"waitRetries,omitempty"`
// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout // WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
WaitForJobs *bool `yaml:"waitForJobs,omitempty"` WaitForJobs *bool `yaml:"waitForJobs,omitempty"`
// Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) // Timeout is the time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
@ -781,6 +785,7 @@ type SyncOpts struct {
SkipCleanup bool SkipCleanup bool
SkipCRDs bool SkipCRDs bool
Wait bool Wait bool
WaitRetries int
WaitForJobs bool WaitForJobs bool
ReuseValues bool ReuseValues bool
ResetValues bool ResetValues bool
@ -1122,6 +1127,7 @@ type ChartPrepareOptions struct {
Validate bool Validate bool
IncludeCRDs *bool IncludeCRDs *bool
Wait bool Wait bool
WaitRetries int
WaitForJobs bool WaitForJobs bool
OutputDir string OutputDir string
OutputDirTemplate string OutputDirTemplate string
@ -2730,7 +2736,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
flags = append(flags, "--enable-dns") flags = append(flags, "--enable-dns")
} }
flags = st.appendWaitFlags(flags, release, opt) flags = st.appendWaitFlags(flags, helm, release, opt)
flags = st.appendWaitForJobsFlags(flags, release, opt) flags = st.appendWaitForJobsFlags(flags, release, opt)
// non-OCI chart should be verified here // non-OCI chart should be verified here