diff --git a/doc/helmchart.md b/doc/helmchart.md index abdb0316..69fb4465 100644 --- a/doc/helmchart.md +++ b/doc/helmchart.md @@ -18,7 +18,7 @@ _Underlying type:_ _string_ _Validation:_ -- Enum: [abort reinstall] +- Enum: [abort reinstall retry] _Appears in:_ - [HelmChartConfigSpec](#helmchartconfigspec) @@ -118,7 +118,8 @@ _Appears in:_ | `values` _[JSON](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#json-v1-apiextensions-k8s-io)_ | Override complex Chart values via structured YAML. Takes precedence over options set via valuesContent.
Helm CLI positional argument/flag: `--values` | | | | `valuesContent` _string_ | Override complex Chart values via inline YAML content.
Helm CLI positional argument/flag: `--values` | | | | `valuesSecrets` _[SecretSpec](#secretspec) array_ | Override complex Chart values via references to external Secrets.
Helm CLI positional argument/flag: `--values` | | | -| `failurePolicy` _[FailurePolicy](#failurepolicy)_ | Configures handling of failed chart installation or upgrades.
- `reinstall` will perform a clean uninstall and reinstall of the chart.
- `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error. | reinstall | Enum: [abort reinstall]
| +| `failurePolicy` _[FailurePolicy](#failurepolicy)_ | Configures handling of failed chart installation or upgrades.
- `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error.
- `reinstall` will perform a clean uninstall and reinstall of the chart; this is the default behavior.
- `retry` will attempt to retry the install or upgrade whenever chart configuration changes. | reinstall | Enum: [abort reinstall retry]
| +| `forceConflicts` _boolean_ | Set to True if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields.
Helm CLI positional argument/flag: `--force-conflicts` | | | @@ -150,11 +151,12 @@ _Appears in:_ | `helmVersion` _string_ | DEPRECATED. Helm version to use. Only v3 is currently supported. | | | | `bootstrap` _boolean_ | Set to True if this chart is needed to bootstrap the cluster (Cloud Controller Manager, CNI, etc). | | | | `takeOwnership` _boolean_ | Set to True if helm should take ownership of existing resources when installing/upgrading the chart.
Helm CLI positional argument/flag: `--take-ownership` | | | +| `forceConflicts` _boolean_ | Set to True if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields.
Helm CLI positional argument/flag: `--force-conflicts` | | | | `chartContent` _string_ | Base64-encoded chart archive .tgz; overides `.spec.chart` and `.spec.version`.
Helm CLI positional argument/flag: `CHART` | | | | `jobImage` _string_ | Specify the image to use for tht helm job pod when installing or upgrading the helm chart. | | | | `backOffLimit` _integer_ | Specify the number of retries before considering the helm job failed. | | | | `timeout` _[Duration](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#duration-v1-meta)_ | Timeout for Helm operations.
Helm CLI positional argument/flag: `--timeout` | | | -| `failurePolicy` _[FailurePolicy](#failurepolicy)_ | Configures handling of failed chart installation or upgrades.
- `reinstall` will perform a clean uninstall and reinstall of the chart.
- `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error. | reinstall | Enum: [abort reinstall]
| +| `failurePolicy` _[FailurePolicy](#failurepolicy)_ | Configures handling of failed chart installation or upgrades.
- `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error.
- `reinstall` will perform a clean uninstall and reinstall of the chart; this is the default behavior.
- `retry` will attempt to retry the install or upgrade whenever chart configuration changes. | reinstall | Enum: [abort reinstall retry]
| | `authSecret` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#localobjectreference-v1-core)_ | Reference to Secret of type kubernetes.io/basic-auth holding Basic auth credentials for the Chart repo. | | | | `authPassCredentials` _boolean_ | Pass Basic auth credentials to all domains.
Helm CLI positional argument/flag: `--pass-credentials` | | | | `insecureSkipTLSVerify` _boolean_ | Skip TLS certificate checks for the chart download.
Helm CLI positional argument/flag: `--insecure-skip-tls-verify` | | | diff --git a/pkg/apis/helm.cattle.io/v1/types.go b/pkg/apis/helm.cattle.io/v1/types.go index 1df72243..ca078f6b 100644 --- a/pkg/apis/helm.cattle.io/v1/types.go +++ b/pkg/apis/helm.cattle.io/v1/types.go @@ -10,7 +10,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) -// +kubebuilder:validation:Enum={"abort","reinstall"} +// +kubebuilder:validation:Enum={"abort","reinstall","retry"} type FailurePolicy string // +kubebuilder:validation:Enum={"secret","configmap"} @@ -79,6 +79,9 @@ type HelmChartSpec struct { // Set to True if helm should take ownership of existing resources when installing/upgrading the chart. // Helm CLI positional argument/flag: `--take-ownership` TakeOwnership bool `json:"takeOwnership,omitempty"` + // Set to True if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields. + // Helm CLI positional argument/flag: `--force-conflicts` + ForceConflicts bool `json:"forceConflicts,omitempty"` // Base64-encoded chart archive .tgz; overides `.spec.chart` and `.spec.version`. // Helm CLI positional argument/flag: `CHART` ChartContent string `json:"chartContent,omitempty"` @@ -90,8 +93,9 @@ type HelmChartSpec struct { // Helm CLI positional argument/flag: `--timeout` Timeout *metav1.Duration `json:"timeout,omitempty"` // Configures handling of failed chart installation or upgrades. - // - `reinstall` will perform a clean uninstall and reinstall of the chart. // - `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error. + // - `reinstall` will perform a clean uninstall and reinstall of the chart; this is the default behavior. + // - `retry` will attempt to retry the install or upgrade whenever chart configuration changes. // +kubebuilder:default=reinstall FailurePolicy FailurePolicy `json:"failurePolicy,omitempty"` // Reference to Secret of type kubernetes.io/basic-auth holding Basic auth credentials for the Chart repo. @@ -162,10 +166,14 @@ type HelmChartConfigSpec struct { // Helm CLI positional argument/flag: `--values` ValuesSecrets []SecretSpec `json:"valuesSecrets,omitempty"` // Configures handling of failed chart installation or upgrades. - // - `reinstall` will perform a clean uninstall and reinstall of the chart. // - `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error. + // - `reinstall` will perform a clean uninstall and reinstall of the chart; this is the default behavior. + // - `retry` will attempt to retry the install or upgrade whenever chart configuration changes. // +kubebuilder:default=reinstall FailurePolicy FailurePolicy `json:"failurePolicy,omitempty"` + // Set to True if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields. + // Helm CLI positional argument/flag: `--force-conflicts` + ForceConflicts *bool `json:"forceConflicts,omitempty"` } type HelmChartConditionType string diff --git a/pkg/apis/helm.cattle.io/v1/zz_generated_deepcopy.go b/pkg/apis/helm.cattle.io/v1/zz_generated_deepcopy.go index c8889623..b54516ed 100644 --- a/pkg/apis/helm.cattle.io/v1/zz_generated_deepcopy.go +++ b/pkg/apis/helm.cattle.io/v1/zz_generated_deepcopy.go @@ -23,6 +23,7 @@ package v1 import ( corev1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" intstr "k8s.io/apimachinery/pkg/util/intstr" @@ -135,6 +136,11 @@ func (in *HelmChartConfigList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HelmChartConfigSpec) DeepCopyInto(out *HelmChartConfigSpec) { *out = *in + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } if in.ValuesSecrets != nil { in, out := &in.ValuesSecrets, &out.ValuesSecrets *out = make([]SecretSpec, len(*in)) @@ -142,6 +148,11 @@ func (in *HelmChartConfigSpec) DeepCopyInto(out *HelmChartConfigSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ForceConflicts != nil { + in, out := &in.ForceConflicts, &out.ForceConflicts + *out = new(bool) + **out = **in + } return } @@ -203,6 +214,11 @@ func (in *HelmChartSpec) DeepCopyInto(out *HelmChartSpec) { (*out)[key] = val } } + if in.Values != nil { + in, out := &in.Values, &out.Values + *out = new(apiextensionsv1.JSON) + (*in).DeepCopyInto(*out) + } if in.ValuesSecrets != nil { in, out := &in.ValuesSecrets, &out.ValuesSecrets *out = make([]SecretSpec, len(*in)) diff --git a/pkg/controllers/chart/chart.go b/pkg/controllers/chart/chart.go index 55d6a203..a81c4f24 100644 --- a/pkg/controllers/chart/chart.go +++ b/pkg/controllers/chart/chart.go @@ -63,8 +63,9 @@ const ( LabelControlPlaneSuffix = "control-plane" LabelEtcdSuffix = "etcd" - FailurePolicyReinstall = "reinstall" FailurePolicyAbort = "abort" + FailurePolicyReinstall = "reinstall" + FailurePolicyRetry = "retry" chartBySecretIndex = "helmcharts.helm.cattle.io/chart-by-secret" chartConfigBySecretIndex = "helmcharts.helm.cattle.io/chartconfig-by-secret" @@ -339,6 +340,28 @@ func (c *Controller) OnChange(chart *v1.HelmChart, chartStatus v1.HelmChartStatu return nil, chartStatus, nil } + if c.jobFailed(chart) { + c.recorder.Eventf(chart, corev1.EventTypeWarning, "JobFailed", "Job has reached configured number of retries without succeeding") + chartCopy := chart.DeepCopy() + chartCopy.Status.Conditions = []v1.HelmChartCondition{ + { + Type: v1.HelmChartJobCreated, + Status: corev1.ConditionTrue, + Reason: "Job created", + Message: fmt.Sprintf("Applying HelmChart using Job %s/%s", chart.Namespace, jobName(chart)), + }, + { + Type: v1.HelmChartFailed, + Status: corev1.ConditionTrue, + Reason: "Job failed", + Message: "Job has reached configured number of retries without succeeding", + }, + } + if _, err := c.helms.UpdateStatus(chartCopy); err != nil { + return nil, chartStatus, fmt.Errorf("unable to update status of helm chart to set failed condition: %w", err) + } + } + // Jobs are created as suspended. Once the job controller syncs it and adds the // Suspended condition, we know that is has been observed by the job controller, // and we can safely manage it without triggering race conditions. If the job is @@ -378,7 +401,7 @@ func (c *Controller) OnChange(chart *v1.HelmChart, chartStatus v1.HelmChartStatu Type: v1.HelmChartJobCreated, Status: corev1.ConditionTrue, Reason: "Job created", - Message: fmt.Sprintf("Applying HelmChart using Job %s/%s", job.Namespace, job.Name), + Message: fmt.Sprintf("Applying HelmChart using Job %s/%s", chart.Namespace, jobName(chart)), }, { Type: v1.HelmChartFailed, @@ -458,7 +481,7 @@ func (c *Controller) OnRemove(key string, chart *v1.HelmChart) (*v1.HelmChart, e chartCopy.Status.JobName = job.Name chart, err = c.helms.UpdateStatus(chartCopy) if err != nil { - return chart, fmt.Errorf("unable to update status of helm chart to add uninstall job name %s", chartCopy.Status.JobName) + return chart, fmt.Errorf("unable to update status of helm chart to add uninstall job name %s: %w", chartCopy.Status.JobName, err) } } @@ -531,6 +554,9 @@ func (c *Controller) getJobAndRelatedResources(chart *v1.HelmChart) (*batch.Job, failurePolicy = fp } + // set default for SSA force-conflicts + forceConflicts := chart.Spec.ForceConflicts + // override default backOffLimit if specified backOffLimit := defaultBackOffLimit if chart.Spec.BackOffLimit != nil { @@ -568,6 +594,11 @@ func (c *Controller) getJobAndRelatedResources(chart *v1.HelmChart) (*batch.Job, failurePolicy = fp } + // Override the force-conflict setting to what is provided in the HelmChartConfig + if config.Spec.ForceConflicts != nil { + forceConflicts = *config.Spec.ForceConflicts + } + // make sure that changes to HelmChart ValuesSecrets triger change to hash for _, secret := range config.Spec.ValuesSecrets { if !secret.IgnoreUpdates && secret.Name != "chart-values-"+config.Name { @@ -583,6 +614,7 @@ func (c *Controller) getJobAndRelatedResources(chart *v1.HelmChart) (*batch.Job, // note: the purpose of the additional annotation is to cause the job to be destroyed // and recreated if the hash of the HelmChartConfig changes while it is being processed setFailurePolicy(job, failurePolicy) + setForceConflicts(job, forceConflicts) setBackOffLimit(job, backOffLimit) hashObjects(job, objects...) @@ -698,6 +730,19 @@ func (c *Controller) jobComplete(chart *v1.HelmChart) bool { return false } +// jobFailed returns true if the job controller has added a True Failed +// condition to the job for the given chart. +func (c *Controller) jobFailed(chart *v1.HelmChart) bool { + if job, _ := c.jobs.Cache().Get(chart.Namespace, jobName(chart)); job != nil { + for _, condition := range job.Status.Conditions { + if condition.Type == batch.JobFailed { + return condition.Status == corev1.ConditionTrue + } + } + } + return false +} + // jobReady returns true if the job is suspended, has never been started, and // the Job controller has added a True Suspended condition to the job for the // given chart. The addition of this condition indicates that the controller @@ -1404,8 +1449,17 @@ func setFailurePolicy(job *batch.Job, failurePolicy string) { }) } +func setForceConflicts(job *batch.Job, forceConflicts bool) { + if forceConflicts { + job.Spec.Template.Spec.Containers[0].Args = slices.Insert(job.Spec.Template.Spec.Containers[0].Args, 1, "--force-conflicts") + } +} + func hashObjects(job *batch.Job, objs ...metav1.Object) { hash := sha256.New() + if backoffLimit := job.Spec.BackoffLimit; backoffLimit != nil { + hash.Write(fmt.Append(nil, *backoffLimit)) + } if obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&job.Spec.Template); err == nil { ust := unstructured.Unstructured{Object: obj} if b, err := ust.MarshalJSON(); err == nil { diff --git a/pkg/crds/yaml/generated/helm.cattle.io_helmchartconfigs.yaml b/pkg/crds/yaml/generated/helm.cattle.io_helmchartconfigs.yaml index 741adf38..52082b5a 100644 --- a/pkg/crds/yaml/generated/helm.cattle.io_helmchartconfigs.yaml +++ b/pkg/crds/yaml/generated/helm.cattle.io_helmchartconfigs.yaml @@ -50,12 +50,19 @@ spec: default: reinstall description: |- Configures handling of failed chart installation or upgrades. - - `reinstall` will perform a clean uninstall and reinstall of the chart. - `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error. + - `reinstall` will perform a clean uninstall and reinstall of the chart; this is the default behavior. + - `retry` will attempt to retry the install or upgrade whenever chart configuration changes. enum: - abort - reinstall + - retry type: string + forceConflicts: + description: |- + Set to True if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields. + Helm CLI positional argument/flag: `--force-conflicts` + type: boolean values: description: |- Override complex Chart values via structured YAML. Takes precedence over options set via valuesContent. diff --git a/pkg/crds/yaml/generated/helm.cattle.io_helmcharts.yaml b/pkg/crds/yaml/generated/helm.cattle.io_helmcharts.yaml index 6cdb0969..f2417f3a 100644 --- a/pkg/crds/yaml/generated/helm.cattle.io_helmcharts.yaml +++ b/pkg/crds/yaml/generated/helm.cattle.io_helmcharts.yaml @@ -146,12 +146,19 @@ spec: default: reinstall description: |- Configures handling of failed chart installation or upgrades. - - `reinstall` will perform a clean uninstall and reinstall of the chart. - `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error. + - `reinstall` will perform a clean uninstall and reinstall of the chart; this is the default behavior. + - `retry` will attempt to retry the install or upgrade whenever chart configuration changes. enum: - abort - reinstall + - retry type: string + forceConflicts: + description: |- + Set to True if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields. + Helm CLI positional argument/flag: `--force-conflicts` + type: boolean helmVersion: description: DEPRECATED. Helm version to use. Only v3 is currently supported. diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index fc518e82..fb207c97 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -22,6 +22,7 @@ import ( clientset "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned" helmv1 "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1" fakehelmv1 "github.com/k3s-io/helm-controller/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -34,7 +35,7 @@ import ( // without applying any field management, validations and/or defaults. It shouldn't be considered a replacement // for a real clientset and is mostly useful in simple unit tests. // -// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves +// Deprecated: NewClientset replaces this with support for field management, which significantly improves // server side apply testing. NewClientset is only available when apply configurations are generated (e.g. // via --with-applyconfig). func NewSimpleClientset(objects ...runtime.Object) *Clientset { @@ -49,9 +50,13 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset { cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(testing.WatchActionImpl); ok { + opts = watchAction.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) + watch, err := o.Watch(gvr, ns, opts) if err != nil { return false, nil, err } @@ -78,6 +83,17 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } +// IsWatchListSemanticsSupported informs the reflector that this client +// doesn't support WatchList semantics. +// +// This is a synthetic method whose sole purpose is to satisfy the optional +// interface check performed by the reflector. +// Returning true signals that WatchList can NOT be used. +// No additional logic is implemented here. +func (c *Clientset) IsWatchListSemanticsUnSupported() bool { + return true +} + var ( _ clientset.Interface = &Clientset{} _ testing.FakeClient = &Clientset{} diff --git a/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/helm.cattle.io_client.go b/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/helm.cattle.io_client.go index e8ae97de..34aedfda 100644 --- a/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/helm.cattle.io_client.go +++ b/pkg/generated/clientset/versioned/typed/helm.cattle.io/v1/helm.cattle.io_client.go @@ -50,9 +50,7 @@ func (c *HelmV1Client) HelmChartConfigs(namespace string) HelmChartConfigInterfa // where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*HelmV1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) httpClient, err := rest.HTTPClientFor(&config) if err != nil { return nil, err @@ -64,9 +62,7 @@ func NewForConfig(c *rest.Config) (*HelmV1Client, error) { // Note the http client provided takes precedence over the configured transport values. func NewForConfigAndClient(c *rest.Config, h *http.Client) (*HelmV1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) client, err := rest.RESTClientForConfigAndClient(&config, h) if err != nil { return nil, err @@ -89,7 +85,7 @@ func New(c rest.Interface) *HelmV1Client { return &HelmV1Client{c} } -func setConfigDefaults(config *rest.Config) error { +func setConfigDefaults(config *rest.Config) { gv := helmcattleiov1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" @@ -98,8 +94,6 @@ func setConfigDefaults(config *rest.Config) error { if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() } - - return nil } // RESTClient returns a RESTClient that is used to communicate diff --git a/test/suite/helm_test.go b/test/suite/helm_test.go index b18c8347..102b4085 100644 --- a/test/suite/helm_test.go +++ b/test/suite/helm_test.go @@ -15,6 +15,7 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/utils/ptr" ) @@ -166,6 +167,80 @@ var _ = Describe("HelmChart Controller Tests", Ordered, func() { }) }) + Context("When a HelmChart is updated with spec.forceConflicts=true", func() { + var ( + err error + chart *v1.HelmChart + ) + BeforeAll(func() { + chart = framework.NewHelmChart("traefik-force-conflicts", + "stable/traefik", + "1.86.1", + "v3", + "", + "metrics:\n prometheus:\n enabled: true\nkubernetes:\n ingressEndpoint:\n useDefaultPublishedService: true\nimage: docker.io/rancher/library-traefik\n", + map[string]intstr.IntOrString{ + "rbac.enabled": { + Type: intstr.String, + StrVal: "true", + }, + "ssl.enabled": { + Type: intstr.String, + StrVal: "true", + }, + }) + chart.Spec.FailurePolicy = "retry" + chart, err = framework.CreateHelmChart(chart, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + }) + It("Should create a release for the chart", func() { + Eventually(framework.ListSecretReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(And( + HaveLen(1), + ContainElement(HaveField("ObjectMeta.Labels", HaveKeyWithValue("status", "deployed"))), + )) + }) + Specify("A conflicting controller modifies fields", func() { + deployment, err := framework.ClientSet.AppsV1().Deployments(chart.Namespace).Patch( + context.TODO(), + chart.Name, + types.MergePatchType, + []byte(`{"spec": {"replicas": 2}}`), + metav1.PatchOptions{FieldManager: "example-other-controller"}, + ) + Expect(err).ToNot(HaveOccurred()) + Expect(deployment.Spec.Replicas).To(HaveValue(BeNumerically("==", 2))) + }) + It("Should create a new failed release when the values are changed", func() { + chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) + chart.Spec.Set["replicas"] = intstr.FromString("3") + chart, err = framework.UpdateHelmChart(chart, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + Expect(chart.Spec.Set["replicas"]).To(Equal(intstr.FromString("3"))) + Eventually(framework.ListSecretReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(And( + HaveLen(2), + ContainElement(HaveField("ObjectMeta.Labels", HaveKeyWithValue("status", "failed"))), + )) + }) + It("Should create a new deployed release", func() { + chart, err = framework.GetHelmChart(chart.Name, chart.Namespace) + chart.Spec.ForceConflicts = true + chart, err = framework.UpdateHelmChart(chart, framework.Namespace) + Expect(err).ToNot(HaveOccurred()) + Expect(chart.Spec.ForceConflicts).To(BeTrue()) + Eventually(framework.ListSecretReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(And( + HaveLen(3), + ContainElement(HaveField("ObjectMeta.Labels", HaveKeyWithValue("status", "deployed"))), + )) + }) + AfterAll(func() { + err = framework.DeleteHelmChart(chart.Name, chart.Namespace) + Expect(err).ToNot(HaveOccurred()) + + Eventually(getHelmChartIgnoreNotFound, 120*time.Second, 5*time.Second).WithArguments(chart.Name, chart.Namespace).Should(BeNil()) + Eventually(framework.ListSecretReleases, 120*time.Second, 5*time.Second).WithArguments(chart).Should(HaveLen(0)) + }) + }) + Context("When a HelmChart is created with spec.takeOwnership=true", func() { var ( err error