Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions doc/helmchart.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ _Appears in:_
| `valuesContent` _string_ | Override complex Chart values via inline YAML content.<br />Helm CLI positional argument/flag: `--values` | | |
| `valuesSecrets` _[SecretSpec](#secretspec) array_ | Override complex Chart values via references to external Secrets.<br />Helm CLI positional argument/flag: `--values` | | |
| `failurePolicy` _[FailurePolicy](#failurepolicy)_ | Configures handling of failed chart installation or upgrades.<br />- `abort` will take no action and leave the chart in a failed state so that the administrator can manually resolve the error.<br />- `reinstall` will perform a clean uninstall and reinstall of the chart; this is the default behavior.<br />- `retry` will attempt to retry the install or upgrade whenever chart configuration changes. | reinstall | Enum: [abort reinstall retry] <br /> |
| `forceConflicts` _boolean_ | Set to True if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields.<br />Helm CLI positional argument/flag: `--force-conflicts` | | |
| `serverSide` _[ServerSide](#serverside)_ | Set to true if helm should enable server-side apply when updating objects. Defaults to `true` for install, and `auto` for upgrade.<br />- `true` enables server-side apply.<br />- `false` disables server-side apply.<br />- `auto` enables server-side apply if the chart was installed with server-side apply enabled.<br />Helm CLI positional argument/flag: `--server-side` | | Enum: [true false auto] <br /> |
| `forceConflicts` _boolean_ | Set to true if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields.<br />Helm CLI positional argument/flag: `--force-conflicts` | | |



Expand Down Expand Up @@ -151,7 +152,8 @@ _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.<br />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.<br />Helm CLI positional argument/flag: `--force-conflicts` | | |
| `serverSide` _[ServerSide](#serverside)_ | Set to true if helm should enable server-side apply when updating objects. Defaults to `true` for install, and `auto` for upgrade.<br />- `true` enables server-side apply.<br />- `false` disables server-side apply.<br />- `auto` enables server-side apply if the chart was installed with server-side apply enabled.<br />Helm CLI positional argument/flag: `--server-side` | | Enum: [true false auto] <br /> |
| `forceConflicts` _boolean_ | Set to true if helm should configure server-side apply to force changes when conflicts arise in ownership of managed fields.<br />Helm CLI positional argument/flag: `--force-conflicts` | | |
| `chartContent` _string_ | Base64-encoded chart archive .tgz; overides `.spec.chart` and `.spec.version`.<br />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. | | |
Expand Down Expand Up @@ -217,3 +219,18 @@ _Appears in:_
| `ignoreUpdates` _boolean_ | Ignore changes to the secret, and mark the secret as optional.<br />By default, the secret must exist, and changes to the secret will trigger an upgrade of the chart to apply the updated values.<br />If `ignoreUpdates` is true, the secret is optional, and changes to the secret will not trigger an upgrade of the chart. | | |


#### ServerSide

_Underlying type:_ _string_



_Validation:_
- Enum: [true false auto]

_Appears in:_
- [HelmChartConfigSpec](#helmchartconfigspec)
- [HelmChartSpec](#helmchartspec)



36 changes: 34 additions & 2 deletions pkg/apis/helm.cattle.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,29 @@ import (
// +kubebuilder:validation:Enum={"abort","reinstall","retry"}
type FailurePolicy string

var (
FailurePolicyAbort = FailurePolicy("abort")
FailurePolicyReinstall = FailurePolicy("reinstall")
FailurePolicyRetry = FailurePolicy("retry")
)

// +kubebuilder:validation:Enum={"secret","configmap"}
type HelmDriver string

var (
HelmDriverSecret = HelmDriver("secret")
HelmDriverConfigMap = HelmDriver("configmap")
)

// +kubebuilder:validation:Enum={"true","false","auto"}
type ServerSide string

var (
ServerSideTrue = ServerSide("true")
ServerSideFalse = ServerSide("false")
ServerSideAuto = ServerSide("auto")
)

// +genclient
// +kubebuilder:subresource:status
// +kubebuilder:resource:shortName=hc
Expand Down Expand Up @@ -79,7 +99,13 @@ 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.
// Set to true if helm should enable server-side apply when updating objects. Defaults to `true` for install, and `auto` for upgrade.
// - `true` enables server-side apply.
// - `false` disables server-side apply.
// - `auto` enables server-side apply if the chart was installed with server-side apply enabled.
// Helm CLI positional argument/flag: `--server-side`
ServerSide ServerSide `json:"serverSide,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`.
Expand Down Expand Up @@ -171,7 +197,13 @@ type HelmChartConfigSpec struct {
// - `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.
// Set to true if helm should enable server-side apply when updating objects. Defaults to `true` for install, and `auto` for upgrade.
// - `true` enables server-side apply.
// - `false` disables server-side apply.
// - `auto` enables server-side apply if the chart was installed with server-side apply enabled.
// Helm CLI positional argument/flag: `--server-side`
ServerSide ServerSide `json:"serverSide,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"`
}
Expand Down
57 changes: 36 additions & 21 deletions pkg/controllers/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,16 @@ const (
LabelControlPlaneSuffix = "control-plane"
LabelEtcdSuffix = "etcd"

FailurePolicyAbort = "abort"
FailurePolicyReinstall = "reinstall"
FailurePolicyRetry = "retry"

chartBySecretIndex = "helmcharts.helm.cattle.io/chart-by-secret"
chartConfigBySecretIndex = "helmcharts.helm.cattle.io/chartconfig-by-secret"
)

var (
commaRE = regexp.MustCompile(`\\*,`)
DefaultJobImage = "rancher/klipper-helm:latest"
JobTolerations []corev1.Toleration
JobResources *corev1.ResourceRequirements
DefaultFailurePolicy = FailurePolicyReinstall
defaultBackOffLimit = ptr.To(int32(1000))
commaRE = regexp.MustCompile(`\\*,`)
DefaultJobImage = "rancher/klipper-helm:latest"
JobTolerations []corev1.Toleration
JobResources *corev1.ResourceRequirements
defaultBackOffLimit = ptr.To(int32(1000))

defaultPodSecurityContext = &corev1.PodSecurityContext{
RunAsNonRoot: ptr.To(true),
Expand Down Expand Up @@ -548,10 +543,16 @@ func (c *Controller) shouldManage(chart *v1.HelmChart) (bool, error) {
}

func (c *Controller) getJobAndRelatedResources(chart *v1.HelmChart) (*batch.Job, []runtime.Object, error) {
// set a default failure policy
failurePolicy := DefaultFailurePolicy
if fp := string(chart.Spec.FailurePolicy); fp != "" {
failurePolicy = fp
// set default for failure policy
failurePolicy := v1.FailurePolicyReinstall
if chart.Spec.FailurePolicy != "" {
failurePolicy = chart.Spec.FailurePolicy
}

// set default for server-side apply (SSA)
serverSide := v1.ServerSideAuto
if chart.Spec.ServerSide != "" {
serverSide = chart.Spec.ServerSide
}

// set default for SSA force-conflicts
Expand Down Expand Up @@ -590,8 +591,13 @@ func (c *Controller) getJobAndRelatedResources(chart *v1.HelmChart) (*batch.Job,
valuesSecretAddConfig(job, valuesSecret, config)

// Override the failure policy to what is provided in the HelmChartConfig
if fp := string(config.Spec.FailurePolicy); fp != "" {
failurePolicy = fp
if config.Spec.FailurePolicy != "" {
failurePolicy = config.Spec.FailurePolicy
}

// Override the server-side apply setting to what is provided in the HelmChartConfig
if config.Spec.ServerSide != "" {
serverSide = config.Spec.ServerSide
}

// Override the force-conflict setting to what is provided in the HelmChartConfig
Expand All @@ -614,6 +620,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)
setServerSide(job, serverSide)
setForceConflicts(job, forceConflicts)
setBackOffLimit(job, backOffLimit)
hashObjects(job, objects...)
Expand Down Expand Up @@ -1442,17 +1449,25 @@ func setRepoCAConfigMap(job *batch.Job, chart *v1.HelmChart) {
}
}

func setFailurePolicy(job *batch.Job, failurePolicy string) {
func setFailurePolicy(job *batch.Job, failurePolicy v1.FailurePolicy) {
job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
Name: "FAILURE_POLICY",
Value: failurePolicy,
Value: string(failurePolicy),
})
}

func setServerSide(job *batch.Job, serverSide v1.ServerSide) {
job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
Name: "SERVER_SIDE",
Value: string(serverSide),
})
}

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")
}
job.Spec.Template.Spec.Containers[0].Env = append(job.Spec.Template.Spec.Containers[0].Env, corev1.EnvVar{
Name: "FORCE_CONFLICTS",
Value: fmt.Sprint(forceConflicts),
})
}

func hashObjects(job *batch.Job, objs ...metav1.Object) {
Expand Down
14 changes: 13 additions & 1 deletion pkg/crds/yaml/generated/helm.cattle.io_helmchartconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,21 @@ spec:
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.
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
serverSide:
description: |-
Set to true if helm should enable server-side apply when updating objects. Defaults to `true` for install, and `auto` for upgrade.
- `true` enables server-side apply.
- `false` disables server-side apply.
- `auto` enables server-side apply if the chart was installed with server-side apply enabled.
Helm CLI positional argument/flag: `--server-side`
enum:
- "true"
- "false"
- auto
type: string
values:
description: |-
Override complex Chart values via structured YAML. Takes precedence over options set via valuesContent.
Expand Down
14 changes: 13 additions & 1 deletion pkg/crds/yaml/generated/helm.cattle.io_helmcharts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ spec:
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.
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:
Expand Down Expand Up @@ -625,6 +625,18 @@ spec:
type: string
type: object
type: object
serverSide:
description: |-
Set to true if helm should enable server-side apply when updating objects. Defaults to `true` for install, and `auto` for upgrade.
- `true` enables server-side apply.
- `false` disables server-side apply.
- `auto` enables server-side apply if the chart was installed with server-side apply enabled.
Helm CLI positional argument/flag: `--server-side`
enum:
- "true"
- "false"
- auto
type: string
set:
additionalProperties:
anyOf:
Expand Down
67 changes: 67 additions & 0 deletions test/suite/helm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,73 @@ var _ = Describe("HelmChart Controller Tests", Ordered, func() {
})
})

Context("When a HelmChart is created with spec.serverSide=false and 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.ServerSide = v1.ServerSideFalse
chart.Spec.ForceConflicts = 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 deployed release", func() {
chart, err = framework.GetHelmChart(chart.Name, chart.Namespace)
chart.Spec.ServerSide = v1.ServerSideAuto
chart.Spec.Set["replicas"] = intstr.FromString("3")
chart, err = framework.UpdateHelmChart(chart, framework.Namespace)
Expect(err).ToNot(HaveOccurred())
Expect(chart.Spec.ServerSide).To(Equal(v1.ServerSideAuto))
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", "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 updated with spec.forceConflicts=true", func() {
var (
err error
Expand Down