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