diff --git a/README.md b/README.md index 089ee074..64a2c7eb 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,15 @@ context: kube-context # kube-context (--kube-context) helmDefaults: tillerNamespace: tiller-namespace #dedicated default key for tiller-namespace kubeContext: kube-context #dedicated default key for kube-context + # additional and global args passed to helm args: - - "--recreate-pods" - - "--timeout=600" - - "--force" + - "--set k=v" + # defaults for verify, wait, force, timeout and recreatePods under releases[] + verify: true + wait: true + timeout: 600 + recreatePods: true + force: true releases: # Published chart example diff --git a/state/state.go b/state/state.go index efbcbc13..9b5c0117 100644 --- a/state/state.go +++ b/state/state.go @@ -42,6 +42,15 @@ type HelmSpec struct { KubeContext string `yaml:"kubeContext"` TillerNamespace string `yaml:"tillerNamespace"` Args []string `yaml:"args"` + Verify bool `yaml:"verify"` + // 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"` + // 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 int `yaml:"timeout"` + // RecreatePods, when set to true, instruct helmfile to perform pods restart for the resource if applicable + RecreatePods bool `yaml:"recreatePods"` + // Force, when set to true, forces resource update through delete/recreate if needed + Force bool `yaml:"force"` } // RepositorySpec that defines values for a helm repo @@ -59,15 +68,15 @@ type ReleaseSpec struct { // Chart is the name of the chart being installed to create this release Chart string `yaml:"chart"` Version string `yaml:"version"` - Verify bool `yaml:"verify"` + Verify *bool `yaml:"verify"` // 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"` // 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 int `yaml:"timeout"` + Timeout *int `yaml:"timeout"` // RecreatePods, when set to true, instruct helmfile to perform pods restart for the resource if applicable - RecreatePods bool `yaml:"recreatePods"` + RecreatePods *bool `yaml:"recreatePods"` // Force, when set to true, forces resource update through delete/recreate if needed - Force bool `yaml:"force"` + Force *bool `yaml:"force"` // Name is the name of this release Name string `yaml:"name"` @@ -224,7 +233,7 @@ func (state *HelmState) SyncReleases(helm helmexec.Interface, additionalValues [ go func() { for release := range jobQueue { state.applyDefaultsTo(release) - flags, flagsErr := state.flagsForUpgrade(helm, state.BaseChartPath, release) + flags, flagsErr := state.flagsForUpgrade(helm, release) if flagsErr != nil { errQueue <- &ReleaseError{release, flagsErr} doneQueue <- true @@ -659,27 +668,37 @@ func chartNameWithoutRepository(chart string) string { return chartSplit[len(chartSplit)-1] } -func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, basePath string, release *ReleaseSpec) ([]string, error) { +func (state *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec) ([]string, error) { flags := []string{} if release.Version != "" { flags = append(flags, "--version", release.Version) } - if release.Verify { + + if release.Verify != nil && *release.Verify || state.HelmDefaults.Verify { flags = append(flags, "--verify") } - if release.Wait { + + if release.Wait != nil && *release.Wait || state.HelmDefaults.Wait { flags = append(flags, "--wait") } - if release.Timeout != 0 { - flags = append(flags, "--timeout", fmt.Sprintf("%d", release.Timeout)) + + timeout := state.HelmDefaults.Timeout + if release.Timeout != nil { + timeout = *release.Timeout } - if release.Force { + if timeout != 0 { + flags = append(flags, "--timeout", fmt.Sprintf("%d", timeout)) + } + + if release.Force != nil && *release.Force || state.HelmDefaults.Force { flags = append(flags, "--force") } - if release.RecreatePods { + + if release.RecreatePods != nil && *release.RecreatePods || state.HelmDefaults.RecreatePods { flags = append(flags, "--recreate-pods") } - common, err := state.namespaceAndValuesFlags(helm, basePath, release) + + common, err := state.namespaceAndValuesFlags(helm, state.BaseChartPath, release) if err != nil { return nil, err } diff --git a/state/state_test.go b/state/state_test.go index 555e45f5..bef5c3d4 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -188,6 +188,7 @@ func TestLabelParsing(t *testing.T) { } } } + func TestHelmState_applyDefaultsTo(t *testing.T) { type fields struct { BaseChartPath string @@ -200,10 +201,11 @@ func TestHelmState_applyDefaultsTo(t *testing.T) { type args struct { spec ReleaseSpec } + verify := false specWithNamespace := ReleaseSpec{ Chart: "test/chart", Version: "0.1", - Verify: false, + Verify: &verify, Name: "test-charts", Namespace: "test-namespace", Values: nil, @@ -278,6 +280,238 @@ func TestHelmState_applyDefaultsTo(t *testing.T) { } } +func TestHelmState_flagsForUpgrade(t *testing.T) { + enable := true + disable := false + + some := func(v int) *int { + return &v + } + + tests := []struct { + name string + defaults HelmSpec + release *ReleaseSpec + want []string + }{ + { + name: "no-options", + defaults: HelmSpec{ + Verify: false, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Verify: &disable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--namespace", "test-namespace", + }, + }, + { + name: "verify", + defaults: HelmSpec{ + Verify: false, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Verify: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--verify", + "--namespace", "test-namespace", + }, + }, + { + name: "verify-from-default", + defaults: HelmSpec{ + Verify: true, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Verify: &disable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--verify", + "--namespace", "test-namespace", + }, + }, + { + name: "force", + defaults: HelmSpec{ + Force: false, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Force: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--force", + "--namespace", "test-namespace", + }, + }, + { + name: "force-from-default", + defaults: HelmSpec{ + Force: true, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Force: &disable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--force", + "--namespace", "test-namespace", + }, + }, + { + name: "recreate-pods", + defaults: HelmSpec{ + RecreatePods: false, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + RecreatePods: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--recreate-pods", + "--namespace", "test-namespace", + }, + }, + { + name: "recreate-pods-from-default", + defaults: HelmSpec{ + RecreatePods: true, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + RecreatePods: &disable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--recreate-pods", + "--namespace", "test-namespace", + }, + }, + { + name: "wait", + defaults: HelmSpec{ + Wait: false, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Wait: &enable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--wait", + "--namespace", "test-namespace", + }, + }, + { + name: "wait-from-default", + defaults: HelmSpec{ + Wait: true, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Wait: &disable, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--wait", + "--namespace", "test-namespace", + }, + }, + { + name: "timeout", + defaults: HelmSpec{ + Timeout: 0, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Timeout: some(123), + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--timeout", "123", + "--namespace", "test-namespace", + }, + }, + { + name: "timeout-from-default", + defaults: HelmSpec{ + Timeout: 123, + }, + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Timeout: nil, + Name: "test-charts", + Namespace: "test-namespace", + }, + want: []string{ + "--version", "0.1", + "--timeout", "123", + "--namespace", "test-namespace", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + state := &HelmState{ + BaseChartPath: "./", + Context: "default", + Releases: []ReleaseSpec{*tt.release}, + HelmDefaults: tt.defaults, + } + helm := helmexec.New(logger, "default") + args, err := state.flagsForUpgrade(helm, tt.release) + if err != nil { + t.Errorf("unexpected error flagsForUpgade: %v", err) + } + if !reflect.DeepEqual(args, tt.want) { + t.Errorf("flagsForUpgrade returned = %v, want %v", args, tt.want) + } + }) + } +} + func Test_renderTemplateString(t *testing.T) { type args struct { s string