diff --git a/api/v2alpha1/helmrelease_types.go b/api/v2alpha1/helmrelease_types.go index c566208..e7b79cd 100644 --- a/api/v2alpha1/helmrelease_types.go +++ b/api/v2alpha1/helmrelease_types.go @@ -18,6 +18,8 @@ package v2alpha1 import ( "encoding/json" + "strings" + "time" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -34,15 +36,20 @@ type HelmReleaseSpec struct { // +required Interval metav1.Duration `json:"interval"` - // Timeout + // +optional + ReleaseName string `json:"releaseName,omitempty"` + + // +optional + TargetNamespace string `json:"targetNamespace,omitempty"` + // +optional Timeout *metav1.Duration `json:"timeout,omitempty"` - // Wait tells the reconciler to wait with marking a Helm action as - // successful until all resources are in a ready state. When set, it will - // wait for as long as 'Timeout'. // +optional - Wait bool `json:"wait,omitempty"` + MaxHistory *int `json:"maxHistory,omitempty"` + + // +optional + Install Install `json:"install,omitempty"` // +optional Upgrade Upgrade `json:"upgrade,omitempty"` @@ -61,12 +68,68 @@ type HelmReleaseSpec struct { Values apiextensionsv1.JSON `json:"values,omitempty"` } +type Install struct { + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + + // +optional + DisableWait bool `json:"disableWait,omitempty"` + + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` + + // +optional + DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` + + // +optional + Replace bool `json:"replace,omitempty"` + + // +optional + SkipCRDs bool `json:"skipCRDs,omitempty"` +} + +func (in Install) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + switch in.Timeout { + case nil: + return defaultTimeout + default: + return *in.Timeout + } +} + type Upgrade struct { // +optional Timeout *metav1.Duration `json:"timeout,omitempty"` // +optional MaxRetries int `json:"maxRetries,omitempty"` + + // +optional + DisableWait bool `json:"disableWait,omitempty"` + + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` + + // +optional + DisableOpenAPIValidation bool `json:"disableOpenAPIValidation,omitempty"` + + // +optional + Force bool `json:"force,omitempty"` + + // +optional + PreserveValues bool `json:"preserveValues,omitempty"` + + // +optional + CleanupOnFail bool `json:"cleanupOnFail,omitempty"` +} + +func (in Upgrade) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + switch in.Timeout { + case nil: + return defaultTimeout + default: + return *in.Timeout + } } type Test struct { @@ -116,6 +179,21 @@ type Rollback struct { // +optional Timeout *metav1.Duration `json:"timeout,omitempty"` + + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` + + // +optional + DisableWait bool `json:"disableWait,omitempty"` + + // +optional + Recreate bool `json:"recreate,omitempty"` + + // +optional + Force bool `json:"force,omitempty"` + + // +optional + CleanupOnFail bool `json:"cleanupOnFail,omitempty"` } func (in Rollback) On() []Condition { @@ -142,8 +220,23 @@ func (in Rollback) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { } type Uninstall struct { + // +optional + Timeout *metav1.Duration `json:"timeout,omitempty"` + // +optional OnCondition *[]Condition `json:"onCondition,omitempty"` + + // +optional + DisableHooks bool `json:"disableHooks,omitempty"` +} + +func (in Uninstall) GetTimeout(defaultTimeout metav1.Duration) metav1.Duration { + switch in.Timeout { + case nil: + return defaultTimeout + default: + return *in.Timeout + } } func (in Uninstall) On() []Condition { @@ -176,7 +269,7 @@ type HelmReleaseStatus struct { // +optional LastAttemptedRevision string `json:"lastAttemptedRevision,omitempty"` - // LastReleaseRevision is the revision of the last successfully Helm release. + // LastReleaseRevision is the revision of the last successful Helm release. // +optional LastReleaseRevision int `json:"lastReleaseRevision,omitempty"` @@ -274,21 +367,49 @@ type HelmRelease struct { // GetValues unmarshals the raw values to a map[string]interface{} // and returns the result. -func (in *HelmRelease) GetValues() map[string]interface{} { +func (in HelmRelease) GetValues() map[string]interface{} { var values map[string]interface{} _ = json.Unmarshal(in.Spec.Values.Raw, &values) return values } -func (in *HelmRelease) GetTimeout() metav1.Duration { +func (in HelmRelease) GetReleaseName() string { + if in.Spec.ReleaseName != "" { + return in.Spec.ReleaseName + } + if in.Spec.TargetNamespace != "" { + return strings.Join([]string{in.Spec.TargetNamespace, in.Name}, "-") + } + return in.Name +} + +func (in HelmRelease) GetReleaseNamespace() string { + switch { + case in.Spec.TargetNamespace != "": + return in.Spec.TargetNamespace + default: + return in.Namespace + } +} + +func (in HelmRelease) GetTimeout() metav1.Duration { switch in.Spec.Timeout { case nil: - return in.Spec.Interval + return metav1.Duration{Duration: 300 * time.Second} default: return *in.Spec.Timeout } } +func (in HelmRelease) GetMaxHistory() int { + switch in.Spec.MaxHistory { + case nil: + return 10 + default: + return *in.Spec.MaxHistory + } +} + // +kubebuilder:object:root=true // HelmReleaseList contains a list of HelmRelease diff --git a/api/v2alpha1/zz_generated.deepcopy.go b/api/v2alpha1/zz_generated.deepcopy.go index 7278a4d..b56e278 100644 --- a/api/v2alpha1/zz_generated.deepcopy.go +++ b/api/v2alpha1/zz_generated.deepcopy.go @@ -110,6 +110,12 @@ func (in *HelmReleaseSpec) DeepCopyInto(out *HelmReleaseSpec) { *out = new(v1.Duration) **out = **in } + if in.MaxHistory != nil { + in, out := &in.MaxHistory, &out.MaxHistory + *out = new(int) + **out = **in + } + in.Install.DeepCopyInto(&out.Install) in.Upgrade.DeepCopyInto(&out.Upgrade) in.Test.DeepCopyInto(&out.Test) in.Rollback.DeepCopyInto(&out.Rollback) @@ -149,6 +155,26 @@ func (in *HelmReleaseStatus) DeepCopy() *HelmReleaseStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Install) DeepCopyInto(out *Install) { + *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Install. +func (in *Install) DeepCopy() *Install { + if in == nil { + return nil + } + out := new(Install) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Rollback) DeepCopyInto(out *Rollback) { *out = *in @@ -214,6 +240,11 @@ func (in *Test) DeepCopy() *Test { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Uninstall) DeepCopyInto(out *Uninstall) { *out = *in + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout + *out = new(v1.Duration) + **out = **in + } if in.OnCondition != nil { in, out := &in.OnCondition, &out.OnCondition *out = new([]Condition) diff --git a/config/crd/bases/helm.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.fluxcd.io_helmreleases.yaml index 9382f6f..d94ba7b 100644 --- a/config/crd/bases/helm.fluxcd.io_helmreleases.yaml +++ b/config/crd/bases/helm.fluxcd.io_helmreleases.yaml @@ -36,13 +36,40 @@ spec: spec: description: HelmReleaseSpec defines the desired state of HelmRelease properties: + install: + properties: + disableHooks: + type: boolean + disableOpenAPIValidation: + type: boolean + disableWait: + type: boolean + replace: + type: boolean + skipCRDs: + type: boolean + timeout: + type: string + type: object interval: description: Interval at which to reconcile the Helm release. type: string + maxHistory: + type: integer + releaseName: + type: string rollback: properties: + cleanupOnFail: + type: boolean + disableHooks: + type: boolean + disableWait: + type: boolean enable: type: boolean + force: + type: boolean onCondition: items: description: Condition contains condition information for a HelmRelease. @@ -72,6 +99,8 @@ spec: - type type: object type: array + recreate: + type: boolean timeout: type: string type: object @@ -93,6 +122,8 @@ spec: - kind - name type: object + targetNamespace: + type: string test: properties: enable: @@ -130,10 +161,11 @@ spec: type: string type: object timeout: - description: Timeout type: string uninstall: properties: + disableHooks: + type: boolean onCondition: items: description: Condition contains condition information for a HelmRelease. @@ -163,22 +195,31 @@ spec: - type type: object type: array + timeout: + type: string type: object upgrade: properties: + cleanupOnFail: + type: boolean + disableHooks: + type: boolean + disableOpenAPIValidation: + type: boolean + disableWait: + type: boolean + force: + type: boolean maxRetries: type: integer + preserveValues: + type: boolean timeout: type: string type: object values: description: Values holds the values for this Helm release. x-kubernetes-preserve-unknown-fields: true - wait: - description: Wait tells the reconciler to wait with marking a Helm action - as successful until all resources are in a ready state. When set, - it will wait for as long as 'Timeout'. - type: boolean required: - interval - sourceRef @@ -227,7 +268,7 @@ spec: attempt. type: string lastReleaseRevision: - description: LastReleaseRevision is the revision of the last successfully + description: LastReleaseRevision is the revision of the last successful Helm release. type: integer observedGeneration: diff --git a/controllers/helmrelease_controller.go b/controllers/helmrelease_controller.go index bd06190..9c5fe18 100644 --- a/controllers/helmrelease_controller.go +++ b/controllers/helmrelease_controller.go @@ -241,39 +241,59 @@ func (r *HelmReleaseReconciler) release(log logr.Logger, hr v2.HelmRelease, sour func (r *HelmReleaseReconciler) install(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease) (*release.Release, error) { install := action.NewInstall(cfg) - install.ReleaseName = hr.Name - install.Namespace = hr.Namespace + install.ReleaseName = hr.GetReleaseName() + install.Namespace = hr.GetReleaseNamespace() + install.Timeout = hr.Spec.Install.GetTimeout(hr.GetTimeout()).Duration + install.Wait = !hr.Spec.Install.DisableWait + install.DisableHooks = hr.Spec.Install.DisableHooks + install.DisableOpenAPIValidation = hr.Spec.Install.DisableOpenAPIValidation + install.Replace = hr.Spec.Install.Replace + install.SkipCRDs = hr.Spec.Install.SkipCRDs return install.Run(chart, hr.GetValues()) } func (r *HelmReleaseReconciler) upgrade(cfg *action.Configuration, chart *chart.Chart, hr v2.HelmRelease) (*release.Release, error) { upgrade := action.NewUpgrade(cfg) - upgrade.Namespace = hr.Namespace - // TODO(hidde): make this configurable - upgrade.ResetValues = true + upgrade.Namespace = hr.GetReleaseNamespace() + upgrade.ResetValues = !hr.Spec.Upgrade.PreserveValues + upgrade.ReuseValues = hr.Spec.Upgrade.PreserveValues + upgrade.MaxHistory = hr.GetMaxHistory() + upgrade.Timeout = hr.Spec.Upgrade.GetTimeout(hr.GetTimeout()).Duration + upgrade.Wait = !hr.Spec.Upgrade.DisableWait + upgrade.DisableHooks = hr.Spec.Upgrade.DisableHooks + upgrade.Force = hr.Spec.Upgrade.Force + upgrade.CleanupOnFail = hr.Spec.Upgrade.CleanupOnFail return upgrade.Run(hr.Name, chart, hr.GetValues()) } func (r *HelmReleaseReconciler) test(cfg *action.Configuration, hr v2.HelmRelease) (*release.Release, error) { test := action.NewReleaseTesting(cfg) - test.Namespace = hr.Namespace + test.Namespace = hr.GetReleaseNamespace() test.Timeout = hr.Spec.Test.GetTimeout(hr.GetTimeout()).Duration - return test.Run(hr.Name) + return test.Run(hr.GetReleaseName()) } func (r *HelmReleaseReconciler) rollback(cfg *action.Configuration, hr v2.HelmRelease) error { rollback := action.NewRollback(cfg) rollback.Timeout = hr.Spec.Rollback.GetTimeout(hr.GetTimeout()).Duration + rollback.Wait = !hr.Spec.Rollback.DisableWait + rollback.DisableHooks = hr.Spec.Rollback.DisableHooks + rollback.Force = hr.Spec.Rollback.Force + rollback.Recreate = hr.Spec.Rollback.Recreate + rollback.CleanupOnFail = hr.Spec.Rollback.CleanupOnFail - return rollback.Run(hr.Name) + return rollback.Run(hr.GetReleaseName()) } func (r *HelmReleaseReconciler) uninstall(cfg *action.Configuration, hr v2.HelmRelease) error { uninstall := action.NewUninstall(cfg) - _, err := uninstall.Run(hr.Name) + uninstall.Timeout = hr.Spec.Uninstall.GetTimeout(hr.GetTimeout()).Duration + uninstall.DisableHooks = hr.Spec.Uninstall.DisableHooks + + _, err := uninstall.Run(hr.GetReleaseName()) return err } @@ -365,9 +385,9 @@ func (r *HelmReleaseReconciler) download(url, tmpDir string) (string, error) { func (r *HelmReleaseReconciler) newActionCfg(log logr.Logger, hr v2.HelmRelease) (*action.Configuration, error) { cfg := new(action.Configuration) - // TODO(hidde): write our own init + ns := hr.GetReleaseNamespace() err := cfg.Init(&genericclioptions.ConfigFlags{ - Namespace: &hr.Namespace, + Namespace: &ns, APIServer: &r.Config.Host, CAFile: &r.Config.CAFile, BearerToken: &r.Config.BearerToken,