From 5c67cbcd6a4e8b9d0747d0667747baa39005d861 Mon Sep 17 00:00:00 2001 From: Hristiyan Ivanov <62302646+hristiy4n@users.noreply.github.com> Date: Sun, 22 Mar 2026 00:34:33 +0100 Subject: [PATCH] fix: pass --timeout flag through to helm for sync and apply (#2495) * fix: propagate timeout flag Signed-off-by: Hristiyan Ivanov * test: add test for propagating timeout flag Signed-off-by: Hristiyan Ivanov * feat: add timeout flag to apply command Signed-off-by: Hristiyan Ivanov * test: add test for timeout flag for helmfile apply Signed-off-by: Hristiyan Ivanov * fix: improve description of timeout flag Signed-off-by: Hristiyan Ivanov --------- Signed-off-by: Hristiyan Ivanov --- cmd/apply.go | 1 + cmd/sync.go | 2 +- pkg/app/app.go | 2 ++ pkg/app/app_apply_test.go | 28 +++++++++++++++++++ pkg/app/app_sync_test.go | 25 +++++++++++++++++ pkg/app/app_test.go | 5 ++++ pkg/app/config.go | 2 ++ .../timeout_flag_is_passed_to_helm/log | 21 ++++++++++++++ pkg/config/apply.go | 7 +++++ 9 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 pkg/app/testdata/testapply_2/timeout_flag_is_passed_to_helm/log diff --git a/cmd/apply.go b/cmd/apply.go index aa399737..dc9982b8 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -61,6 +61,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&applyOptions.SuppressDiff, "suppress-diff", false, "suppress diff in the output. Usable in new installs") f.BoolVar(&applyOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`) f.BoolVar(&applyOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`) + f.IntVar(&applyOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout in seconds for "helm upgrade --install --timeout" (default 0, which uses helmDefaults.timeout or helm's default if not set)`) f.BoolVar(&applyOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`) f.BoolVar(&applyOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`) f.StringVar(&applyOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`) diff --git a/cmd/sync.go b/cmd/sync.go index 376b1938..fdedbd72 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -47,7 +47,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release") f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`) f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`) - f.IntVar(&syncOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout setting "helm upgrade --install --timeout" (default 0, which means no timeout)`) + f.IntVar(&syncOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout in seconds for "helm upgrade --install --timeout" (default 0, which uses helmDefaults.timeout or helm's default if not set)`) f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`) f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`) f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`) diff --git a/pkg/app/app.go b/pkg/app/app.go index 7fb6da5e..66748a44 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1785,6 +1785,7 @@ Do you really want to apply? Wait: c.Wait(), WaitRetries: c.WaitRetries(), WaitForJobs: c.WaitForJobs(), + Timeout: c.Timeout(), ReuseValues: c.ReuseValues(), ResetValues: c.ResetValues(), PostRenderer: c.PostRenderer(), @@ -2253,6 +2254,7 @@ Do you really want to sync? Wait: c.Wait(), WaitRetries: c.WaitRetries(), WaitForJobs: c.WaitForJobs(), + Timeout: c.Timeout(), ReuseValues: c.ReuseValues(), ResetValues: c.ResetValues(), PostRenderer: c.PostRenderer(), diff --git a/pkg/app/app_apply_test.go b/pkg/app/app_apply_test.go index 2f235f1a..7dd663cf 100644 --- a/pkg/app/app_apply_test.go +++ b/pkg/app/app_apply_test.go @@ -25,6 +25,7 @@ func TestApply_2(t *testing.T) { fields fields ns string concurrency int + timeout int skipDiffOnInstall bool error string files map[string]string @@ -84,6 +85,7 @@ func TestApply_2(t *testing.T) { syncErr := app.Apply(applyConfig{ // if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic. concurrency: tc.concurrency, + timeout: tc.timeout, logger: logger, skipDiffOnInstall: tc.skipDiffOnInstall, skipNeeds: tc.fields.skipNeeds, @@ -653,4 +655,30 @@ foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default concurrency: 1, }) }) + + t.Run("timeout flag is passed to helm", func(t *testing.T) { + check(t, testcase{ + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: my-release + chart: incubator/raw + namespace: default +`, + }, + timeout: 300, + concurrency: 1, + upgraded: []exectest.Release{ + {Name: "my-release", Flags: []string{"--timeout", "300s", "--kube-context", "default", "--namespace", "default"}}, + }, + diffs: map[exectest.DiffKey]error{ + {Name: "my-release", Chart: "incubator/raw", Flags: "--kube-context default --namespace default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2}, + }, + lists: map[exectest.ListKey]string{ + {Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default +`, + }, + }) + }) } diff --git a/pkg/app/app_sync_test.go b/pkg/app/app_sync_test.go index 200e8f5c..ce38e77e 100644 --- a/pkg/app/app_sync_test.go +++ b/pkg/app/app_sync_test.go @@ -25,6 +25,7 @@ func TestSync(t *testing.T) { fields fields ns string concurrency int + timeout int skipDiffOnInstall bool error string files map[string]string @@ -81,6 +82,7 @@ func TestSync(t *testing.T) { syncErr := app.Sync(applyConfig{ concurrency: tc.concurrency, + timeout: tc.timeout, logger: logger, skipDiffOnInstall: tc.skipDiffOnInstall, skipNeeds: tc.fields.skipNeeds, @@ -478,4 +480,27 @@ releases: concurrency: 1, }) }) + + t.Run("timeout flag is passed to helm", func(t *testing.T) { + check(t, testcase{ + files: map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: my-release + chart: incubator/raw + namespace: default +`, + }, + timeout: 600, + concurrency: 1, + upgraded: []exectest.Release{ + {Name: "my-release", Flags: []string{"--timeout", "600s", "--kube-context", "default", "--namespace", "default"}}, + }, + lists: map[exectest.ListKey]string{ + {Filter: "^my-release$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE +my-release 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default +`, + }, + }) + }) } diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index ffc500fb..da895b46 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2369,6 +2369,7 @@ type applyConfig struct { wait bool waitRetries int waitForJobs bool + timeout int reuseValues bool postRenderer string postRendererArgs []string @@ -2409,6 +2410,10 @@ func (a applyConfig) WaitForJobs() bool { return a.waitForJobs } +func (a applyConfig) Timeout() int { + return a.timeout +} + func (a applyConfig) Values() []string { return a.values } diff --git a/pkg/app/config.go b/pkg/app/config.go index 03bdc627..024e7cbe 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -59,6 +59,7 @@ type ApplyConfigProvider interface { Wait() bool WaitRetries() int WaitForJobs() bool + Timeout() int IncludeTests() bool @@ -114,6 +115,7 @@ type SyncConfigProvider interface { Wait() bool WaitRetries() int WaitForJobs() bool + Timeout() int SyncArgs() string Validate() bool diff --git a/pkg/app/testdata/testapply_2/timeout_flag_is_passed_to_helm/log b/pkg/app/testdata/testapply_2/timeout_flag_is_passed_to_helm/log new file mode 100644 index 00000000..5e22123e --- /dev/null +++ b/pkg/app/testdata/testapply_2/timeout_flag_is_passed_to_helm/log @@ -0,0 +1,21 @@ +merged environment: &{default map[] map[] map[]} +1 release(s) found in helmfile.yaml + +Affected releases are: + my-release (incubator/raw) UPDATED + +invoking preapply hooks for 1 groups of releases in this order: +GROUP RELEASES +1 default/default/my-release + +invoking preapply hooks for releases in group 1/1: default/default/my-release +processing 1 groups of releases in this order: +GROUP RELEASES +1 default/default/my-release + +processing releases in group 1/1: default/default/my-release + +UPDATED RELEASES: +NAME NAMESPACE CHART VERSION DURATION +my-release default incubator/raw 3.1.0 0s + diff --git a/pkg/config/apply.go b/pkg/config/apply.go index f97d712c..872844f7 100644 --- a/pkg/config/apply.go +++ b/pkg/config/apply.go @@ -57,6 +57,8 @@ type ApplyOptions struct { WaitRetries int // WaitForJobs is true if the helm command should wait for the jobs to be completed WaitForJobs bool + // Timeout is the timeout for helm operations in seconds + Timeout int // Propagate '--skip-schema-validation' to helmv3 template and helm install SkipSchemaValidation bool // ReuseValues is true if the helm command should reuse the values @@ -235,6 +237,11 @@ func (a *ApplyImpl) WaitForJobs() bool { return a.ApplyOptions.WaitForJobs } +// Timeout returns the timeout. +func (a *ApplyImpl) Timeout() int { + return a.ApplyOptions.Timeout +} + // ReuseValues returns the ReuseValues. func (a *ApplyImpl) ReuseValues() bool { if !a.ResetValues() {