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
4 changes: 3 additions & 1 deletion doc/helmchart.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `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.<br />Helm CLI positional argument/flag: `--values` | | |
| `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 />- `reinstall` will perform a clean uninstall and reinstall of the chart.<br />- `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] <br /> |
Expand Down Expand Up @@ -142,7 +143,8 @@ _Appears in:_
| `repo` _string_ | Helm Chart repository URL.<br />Helm CLI positional argument/flag: `--repo` | | |
| `repoCA` _string_ | Verify certificates of HTTPS-enabled servers using this CA bundle. Should be a string containing one or more PEM-encoded CA Certificates.<br />Helm CLI positional argument/flag: `--ca-file` | | |
| `repoCAConfigMap` _[LocalObjectReference](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#localobjectreference-v1-core)_ | Reference to a ConfigMap containing CA Certificates to be be trusted by Helm. Can be used along with or instead of `.spec.repoCA`<br />Helm CLI positional argument/flag: `--ca-file` | | |
| `set` _object (keys:string, values:[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#intorstring-intstr-util))_ | Override simple Chart values. These take precedence over options set via valuesContent.<br />Helm CLI positional argument/flag: `--set`, `--set-string` | | |
| `set` _object (keys:string, values:[IntOrString](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.32/#intorstring-intstr-util))_ | Override simple Chart values. These take precedence over options set via values or valuesContent.<br />Helm CLI positional argument/flag: `--set`, `--set-string` | | |
| `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.<br />Helm CLI positional argument/flag: `--values` | | |
| `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` | | |
| `helmVersion` _string_ | DEPRECATED. Helm version to use. Only v3 is currently supported. | | |
Expand Down
9 changes: 8 additions & 1 deletion pkg/apis/helm.cattle.io/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package v1

import (
corev1 "k8s.io/api/core/v1"
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/util/intstr"
Expand Down Expand Up @@ -56,9 +57,12 @@ type HelmChartSpec struct {
// Reference to a ConfigMap containing CA Certificates to be be trusted by Helm. Can be used along with or instead of `.spec.repoCA`
// Helm CLI positional argument/flag: `--ca-file`
RepoCAConfigMap *corev1.LocalObjectReference `json:"repoCAConfigMap,omitempty"`
// Override simple Chart values. These take precedence over options set via valuesContent.
// Override simple Chart values. These take precedence over options set via values or valuesContent.
// Helm CLI positional argument/flag: `--set`, `--set-string`
Set map[string]intstr.IntOrString `json:"set,omitempty"`
// Override complex Chart values via structured YAML. Takes precedence over options set via valuesContent.
// Helm CLI positional argument/flag: `--values`
Values *apiextv1.JSON `json:"values,omitempty"`
// Override complex Chart values via inline YAML content.
// Helm CLI positional argument/flag: `--values`
ValuesContent string `json:"valuesContent,omitempty"`
Expand Down Expand Up @@ -137,6 +141,9 @@ type HelmChartConfig struct {
// HelmChartConfigSpec represents additional user-configurable details of an installed and configured Helm chart release.
// These fields are merged with or override the corresponding fields on the related HelmChart resource.
type HelmChartConfigSpec struct {
// Override complex Chart values via structured YAML. Takes precedence over options set via valuesContent.
// Helm CLI positional argument/flag: `--values`
Values *apiextv1.JSON `json:"values,omitempty"`
// Override complex Chart values via inline YAML content.
// Helm CLI positional argument/flag: `--values`
ValuesContent string `json:"valuesContent,omitempty"`
Expand Down
71 changes: 44 additions & 27 deletions pkg/controllers/chart/chart.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"time"

v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1"
"github.com/k3s-io/helm-controller/pkg/controllers/extjson"
helmcontroller "github.com/k3s-io/helm-controller/pkg/generated/controllers/helm.cattle.io/v1"
"github.com/k3s-io/helm-controller/pkg/remove"
"github.com/rancher/wrangler/v3/pkg/apply"
Expand Down Expand Up @@ -798,6 +799,9 @@ func valuesSecret(chart *v1.HelmChart) *corev1.Secret {
if chart.Spec.ValuesContent != "" {
secret.Data["HelmChartValuesContent"] = []byte(chart.Spec.ValuesContent)
}
if !extjson.IsEmpty(chart.Spec.Values) {
secret.Data["HelmChartValues"] = []byte(extjson.TryToYAML(chart.Spec.Values))
}
if chart.Spec.RepoCA != "" {
secret.Data["RepoCA"] = []byte(chart.Spec.RepoCA)
}
Expand All @@ -808,38 +812,47 @@ func valuesSecret(chart *v1.HelmChart) *corev1.Secret {
func valuesSecretAddConfig(job *batch.Job, secret *corev1.Secret, config *v1.HelmChartConfig) {
if config.Spec.ValuesContent != "" {
secret.Data["HelmChartConfigValuesContent"] = []byte(config.Spec.ValuesContent)
}
if !extjson.IsEmpty(config.Spec.Values) {
secret.Data["HelmChartConfigValues"] = []byte(extjson.TryToYAML(config.Spec.Values))
}

// modify projected volume to hold collected secret keys
for i := range job.Spec.Template.Spec.Volumes {
if job.Spec.Template.Spec.Volumes[i].Name != "values" {
continue
}
valuesVolume := &job.Spec.Template.Spec.Volumes[i]
// modify projected volumes to hold collected secret keys
for i := range job.Spec.Template.Spec.Volumes {
if job.Spec.Template.Spec.Volumes[i].Name != "values" {
continue
}
valuesVolume := &job.Spec.Template.Spec.Volumes[i]

// add item for HelmChartConfig ValuesContent
// the first source in this volume is always the managed secret for this HelmChart
// the first source in this volume is always the managed secret for this HelmChart
// add item for HelmChartConfig ValuesContent
if config.Spec.ValuesContent != "" {
valuesVolume.Projected.Sources[0].Secret.Items = append(valuesVolume.Projected.Sources[0].Secret.Items, corev1.KeyToPath{Key: "HelmChartConfigValuesContent", Path: "values-1-000-HelmChartConfig-ValuesContent.yaml"})
}
// add item for HelmChartConfig Values
if !extjson.IsEmpty(config.Spec.Values) {
valuesVolume.Projected.Sources[0].Secret.Items = append(valuesVolume.Projected.Sources[0].Secret.Items, corev1.KeyToPath{Key: "HelmChartConfigValues", Path: "values-1-001-HelmChartConfig-Values.yaml"})
}

items := 0
// add projection and items for HelmChartConfig ValuesSecrets
for _, secret := range config.Spec.ValuesSecrets {
if len(secret.Keys) == 0 || secret.Name == "chart-values-"+config.Name {
continue
}
volumeProjection := corev1.VolumeProjection{
Secret: &corev1.SecretProjection{
Optional: ptr.To(secret.IgnoreUpdates),
LocalObjectReference: corev1.LocalObjectReference{
Name: secret.Name,
},
items := 1
// add projection and items for HelmChartConfig ValuesSecrets
for _, secret := range config.Spec.ValuesSecrets {
if len(secret.Keys) == 0 || secret.Name == "chart-values-"+config.Name {
continue
}
volumeProjection := corev1.VolumeProjection{
Secret: &corev1.SecretProjection{
Optional: ptr.To(secret.IgnoreUpdates),
LocalObjectReference: corev1.LocalObjectReference{
Name: secret.Name,
},
}
for _, key := range secret.Keys {
items++
volumeProjection.Secret.Items = append(volumeProjection.Secret.Items, corev1.KeyToPath{Key: key, Path: fmt.Sprintf("values-1-%03d-HelmChartConfig-ValuesSecret.yaml", items)})
}
valuesVolume.VolumeSource.Projected.Sources = append(valuesVolume.VolumeSource.Projected.Sources, volumeProjection)
},
}
for _, key := range secret.Keys {
items++
volumeProjection.Secret.Items = append(volumeProjection.Secret.Items, corev1.KeyToPath{Key: key, Path: fmt.Sprintf("values-1-%03d-HelmChartConfig-ValuesSecret.yaml", items)})
}
valuesVolume.VolumeSource.Projected.Sources = append(valuesVolume.VolumeSource.Projected.Sources, volumeProjection)
}
}
}
Expand Down Expand Up @@ -1032,12 +1045,16 @@ func setValuesSecret(job *batch.Job, chart *v1.HelmChart) *corev1.Secret {
if chart.Spec.ValuesContent != "" {
valuesVolume.VolumeSource.Projected.Sources[0].Secret.Items = append(valuesVolume.VolumeSource.Projected.Sources[0].Secret.Items, corev1.KeyToPath{Key: "HelmChartValuesContent", Path: "values-0-000-HelmChart-ValuesContent.yaml"})
}
// add item for HelmChart Values
if !extjson.IsEmpty(chart.Spec.Values) {
valuesVolume.VolumeSource.Projected.Sources[0].Secret.Items = append(valuesVolume.VolumeSource.Projected.Sources[0].Secret.Items, corev1.KeyToPath{Key: "HelmChartValues", Path: "values-0-001-HelmChart-Values.yaml"})
}
// add item for HelmChart RepoCA
if chart.Spec.RepoCA != "" {
valuesVolume.VolumeSource.Projected.Sources[0].Secret.Items = append(valuesVolume.VolumeSource.Projected.Sources[0].Secret.Items, corev1.KeyToPath{Key: "RepoCA", Path: "ca-file.pem"})
}

items := 0
items := 1
// add projection and items for HelmChart ValuesSecrets
for _, secret := range chart.Spec.ValuesSecrets {
if len(secret.Keys) == 0 || secret.Name == "chart-values-"+chart.Name {
Expand Down
33 changes: 31 additions & 2 deletions pkg/controllers/chart/chart_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"time"

v1 "github.com/k3s-io/helm-controller/pkg/apis/helm.cattle.io/v1"
"github.com/k3s-io/helm-controller/pkg/controllers/extjson"

"github.com/rancher/wrangler/v3/pkg/yaml"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
Expand All @@ -20,7 +22,9 @@ func init() {

func TestHashObjects(t *testing.T) {
type args struct {
chartValues string
chartValuesContent string
configValues string
configValuesContent string
hash string
}
Expand All @@ -41,6 +45,10 @@ func TestHashObjects(t *testing.T) {
hash: "SHA256=FFE4DB5EFB61ACC03F197C464414B5BB65885E8F03AE11B9EBB657D5DD3CCC55",
chartValuesContent: "{}",
},
"Chart Only 4": {
hash: "SHA256=EA4FB70C0432FC1EEC700C96FA28530DD2B47A84D09F33AC2B9D666FA887C302",
chartValues: "foo: bar\n",
},
"Config Only 1": {
hash: "SHA256=E00641CFFEB2D8EA3403D56DD456DAAF9578B4871F2FDB41B0F1AA33C25B69AF",
configValuesContent: "foo: baz\n",
Expand All @@ -53,6 +61,11 @@ func TestHashObjects(t *testing.T) {
hash: "SHA256=E1D81D53C173950A8F35BB397759CF49B3F43C0C797AD4F7C7AD6A3A47180E03",
configValuesContent: "{}",
},
"Config Only 4": {
hash: "SHA256=88F5E5BF9826DD95940FC3DC702C5E69F46BA280D6C6E688875DFCD56FB8F629",
configValues: "foo: bar\n",
configValuesContent: "foo: baz\n",
},
"Chart and Config 1": {
hash: "SHA256=F81EFF0BAF43F57D87FB53BCFAB06271091B411C4A582FCC130C33951CB7C81D",
chartValuesContent: "foo: bar\n",
Expand All @@ -63,14 +76,28 @@ func TestHashObjects(t *testing.T) {
chartValuesContent: "foo:\n a: true\n b: 1\n c: 'true'\n",
configValuesContent: "bar:\n a: false\n b: 0\n c: 'false'\n",
},
"Chart and Config 3": {
hash: "SHA256=2C7889180BF017CF2F09368453178255F7E10B4883134AFE660CFF61D55CE20D",
chartValues: "foo: bar\n",
configValues: "foo: baz\n",
},
"Chart and Config 4": {
hash: "SHA256=D4FA2B666B5A61C632A5AFD92BBF776279DB51D24357C4A461D1088135562DE4",
chartValues: "foo:\n a: true\n b: 1\n c: 'true'\n",
chartValuesContent: "foo:\n a: true\n b: 1\n c: 'true'\n",
configValues: "bar:\n a: false\n b: 0\n c: 'false'\n",
configValuesContent: "bar:\n a: false\n b: 0\n c: 'false'\n",
},
}

for name, test := range tests {
t.Run(name, func(t *testing.T) {
assert := assert.New(t)
chart := NewChart()
config := &v1.HelmChartConfig{}
chart.Spec.Values = extjson.TryFromYAML(test.chartValues)
chart.Spec.ValuesContent = test.chartValuesContent
config.Spec.Values = extjson.TryFromYAML(test.configValues)
config.Spec.ValuesContent = test.configValuesContent

job, secret, configMap := job(chart, "6443")
Expand All @@ -81,8 +108,8 @@ func TestHashObjects(t *testing.T) {
assert.Nil(secret.StringData, "Secret StringData should be nil")
assert.Nil(configMap.BinaryData, "ConfigMap BinaryData should be nil")

if test.chartValuesContent == "" && test.configValuesContent == "" {
assert.Empty(secret.Data, "Secret Data should be empty if HelmChart and HelmChartConfig ValuesContent are empty")
if test.chartValues == "" && test.chartValuesContent == "" && test.configValues == "" && test.configValuesContent == "" {
assert.Empty(secret.Data, "Secret Data should be empty if HelmChart and HelmChartConfig Values and ValuesContent are empty")
} else {
assert.NotEmpty(secret.Data, "Secret Data should not be empty if HelmChart and/or HelmChartConfig ValuesContent are not empty")
}
Expand All @@ -91,6 +118,8 @@ func TestHashObjects(t *testing.T) {

b, _ := yaml.ToBytes([]runtime.Object{job})
t.Logf("Generated Job:\n%s", b)
s, _ := yaml.ToBytes([]runtime.Object{secret})
t.Logf("Generated Secret:\n%s", s)

assert.Equalf(test.hash, job.Spec.Template.ObjectMeta.Annotations[AnnotationConfigHash], "%s annotation value does not match", AnnotationConfigHash)
})
Expand Down
38 changes: 38 additions & 0 deletions pkg/controllers/extjson/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package extjson

import (
"bytes"

apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"sigs.k8s.io/yaml"
)

func IsEmpty(j *apiextv1.JSON) bool {
return j == nil || len(j.Raw) == 0 || bytes.Equal(j.Raw, []byte(`null`))
}

// TryToYAML attempts to convert [apiextv1.JSON] to a YAML string.
// It returns an empty string if the input is nil, contains no data, or if the conversion from JSON to YAML fails.
func TryToYAML(j *apiextv1.JSON) string {
if IsEmpty(j) {
return ""
}
b, err := yaml.JSONToYAML(j.Raw)
if err != nil {
return ""
}
return string(b)
}

// TryFromYAML attempts to convert a YAML string to [apiextv1.JSON].
// It returns nil if the input is empty or if the conversion fails, effectively swallowing any parsing errors.
func TryFromYAML(s string) *apiextv1.JSON {
if len(s) == 0 {
return nil
}
b, err := yaml.YAMLToJSON([]byte(s))
if err != nil {
return nil
}
return &apiextv1.JSON{Raw: b}
}
5 changes: 5 additions & 0 deletions pkg/crds/yaml/generated/helm.cattle.io_helmchartconfigs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ spec:
- abort
- reinstall
type: string
values:
description: |-
Override complex Chart values via structured YAML. Takes precedence over options set via valuesContent.
Helm CLI positional argument/flag: `--values`
x-kubernetes-preserve-unknown-fields: true
valuesContent:
description: |-
Override complex Chart values via inline YAML content.
Expand Down
7 changes: 6 additions & 1 deletion pkg/crds/yaml/generated/helm.cattle.io_helmcharts.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ spec:
- type: string
x-kubernetes-int-or-string: true
description: |-
Override simple Chart values. These take precedence over options set via valuesContent.
Override simple Chart values. These take precedence over options set via values or valuesContent.
Helm CLI positional argument/flag: `--set`, `--set-string`
type: object
takeOwnership:
Expand All @@ -627,6 +627,11 @@ spec:
Timeout for Helm operations.
Helm CLI positional argument/flag: `--timeout`
type: string
values:
description: |-
Override complex Chart values via structured YAML. Takes precedence over options set via valuesContent.
Helm CLI positional argument/flag: `--values`
x-kubernetes-preserve-unknown-fields: true
valuesContent:
description: |-
Override complex Chart values via inline YAML content.
Expand Down
Loading