diff --git a/api/v2beta1/helmrelease_types.go b/api/v2beta1/helmrelease_types.go index 69743fca1..1cec7f3d2 100644 --- a/api/v2beta1/helmrelease_types.go +++ b/api/v2beta1/helmrelease_types.go @@ -219,11 +219,32 @@ func (in HelmReleaseSpec) GetUninstall() Uninstall { // generate a v1beta2.HelmChart object in the same namespace as the referenced // v1beta2.Source. type HelmChartTemplate struct { + // ObjectMeta holds the template for metadata like labels and annotations. + // +optional + ObjectMeta HelmChartTemplateObjectMeta `json:"metadata,omitempty"` + // Spec holds the template for the v1beta2.HelmChartSpec for this HelmRelease. // +required Spec HelmChartTemplateSpec `json:"spec"` } +// HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a +// v1beta2.HelmChart. +type HelmChartTemplateObjectMeta struct { + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. They are not + // queryable and should be preserved when modifying objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // HelmChartTemplateSpec defines the template from which the controller will // generate a v1beta2.HelmChartSpec object. type HelmChartTemplateSpec struct { diff --git a/api/v2beta1/zz_generated.deepcopy.go b/api/v2beta1/zz_generated.deepcopy.go index 30dee88ee..252c475b3 100644 --- a/api/v2beta1/zz_generated.deepcopy.go +++ b/api/v2beta1/zz_generated.deepcopy.go @@ -47,6 +47,7 @@ func (in *CrossNamespaceObjectReference) DeepCopy() *CrossNamespaceObjectReferen // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HelmChartTemplate) DeepCopyInto(out *HelmChartTemplate) { *out = *in + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) } @@ -60,6 +61,35 @@ func (in *HelmChartTemplate) DeepCopy() *HelmChartTemplate { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HelmChartTemplateObjectMeta) DeepCopyInto(out *HelmChartTemplateObjectMeta) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartTemplateObjectMeta. +func (in *HelmChartTemplateObjectMeta) DeepCopy() *HelmChartTemplateObjectMeta { + if in == nil { + return nil + } + out := new(HelmChartTemplateObjectMeta) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HelmChartTemplateSpec) DeepCopyInto(out *HelmChartTemplateSpec) { *out = *in diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml index fd5d268f4..8e60df492 100644 --- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml +++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml @@ -51,6 +51,27 @@ spec: description: Chart defines the template of the v1beta2.HelmChart that should be created for this HelmRelease. properties: + metadata: + description: ObjectMeta holds the template for metadata like labels + and annotations. + properties: + annotations: + additionalProperties: + type: string + description: 'Annotations is an unstructured key value map + stored with a resource that may be set by external tools + to store and retrieve arbitrary metadata. They are not queryable + and should be preserved when modifying objects. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/' + type: object + labels: + additionalProperties: + type: string + description: 'Map of string keys and values that can be used + to organize and categorize (scope and select) objects. More + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/' + type: object + type: object spec: description: Spec holds the template for the v1beta2.HelmChartSpec for this HelmRelease. diff --git a/docs/api/helmrelease.md b/docs/api/helmrelease.md index 2f6277671..844aa2d14 100644 --- a/docs/api/helmrelease.md +++ b/docs/api/helmrelease.md @@ -457,6 +457,20 @@ v1beta2.Source.

+metadata
+ + +HelmChartTemplateObjectMeta + + + + +(Optional) +

ObjectMeta holds the template for metadata like labels and annotations.

+ + + + spec
@@ -591,6 +605,57 @@ Chart dependencies, which are not bundled in the umbrella chart artifact, are no +

HelmChartTemplateObjectMeta +

+

+(Appears on: +HelmChartTemplate) +

+

HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a +v1beta2.HelmChart.

+
+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+labels
+ +map[string]string + +
+(Optional) +

Map of string keys and values that can be used to organize and categorize +(scope and select) objects. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/

+
+annotations
+ +map[string]string + +
+(Optional) +

Annotations is an unstructured key value map stored with a resource that may be +set by external tools to store and retrieve arbitrary metadata. They are not +queryable and should be preserved when modifying objects. +More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/

+
+
+

HelmChartTemplateSpec

diff --git a/docs/spec/v2beta1/helmreleases.md b/docs/spec/v2beta1/helmreleases.md index 2fdc42095..8dfe39206 100644 --- a/docs/spec/v2beta1/helmreleases.md +++ b/docs/spec/v2beta1/helmreleases.md @@ -134,11 +134,32 @@ type KubeConfig struct { // generate a v1beta1.HelmChart object in the same namespace as the referenced // v1beta1.Source. type HelmChartTemplate struct { + // ObjectMeta holds the template for metadata like labels and annotations. + // +optional + ObjectMeta HelmChartTemplateObjectMeta `json:"metadata,omitempty"` + // Spec holds the template for the v1beta1.HelmChartSpec for this HelmRelease. // +required Spec HelmChartTemplateSpec `json:"spec"` } +// HelmChartTemplateObjectMeta defines the template for the ObjectMeta of a +// v1beta2.HelmChart. +type HelmChartTemplateObjectMeta struct { + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // Annotations is an unstructured key value map stored with a resource that may be + // set by external tools to store and retrieve arbitrary metadata. They are not + // queryable and should be preserved when modifying objects. + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + // +optional + Annotations map[string]string `json:"annotations,omitempty"` +} + // HelmChartTemplateSpec defines the template from which the controller will // generate a v1beta1.HelmChartSpec object. type HelmChartTemplateSpec struct { @@ -681,7 +702,7 @@ of the `HelmRelease`. These can be overridden respectively via `spec.targetNames ## Helm chart template -The `spec.chart.spec` values are used by the helm-controller as a template +The `spec.chart` values are used by the helm-controller as a template to create a new `HelmChart` resource with the given spec. The `spec.chart.spec.sourceRef` is a reference to an object managed by @@ -699,8 +720,8 @@ The `HelmChart` is created in the same namespace as the `sourceRef`, with a name matching the `HelmRelease` `-`. > **Note** that on multi-tenant clusters, platform admins can disable cross-namespace references -> with the `--no-cross-namespace-refs=true` flag. When this flag is set, the helmrelease can only -> refer to sources in the same namespace as the helmrelease object. +> with the `--no-cross-namespace-refs=true` flag. When this flag is set, the HelmRelease can only +> refer to sources in the same namespace as the HelmRelease object. The `chart.spec.chart` can either contain: @@ -713,6 +734,9 @@ The `chart.spec.version` can be a fixed semver, or any semver range (i.e. `>=4.0.0 <5.0.0`). It is ignored for `HelmRelease` resources that reference a `GitRepository` or `Bucket` source. +Annotations and labels can be added by configuring the respective `.spec.chart.metadata` +fields. + ## Values overrides The simplest way to define values overrides is inline via `spec.values`. diff --git a/internal/controllers/helmrelease_controller_chart.go b/internal/controllers/helmrelease_controller_chart.go index daee87f40..532e56959 100644 --- a/internal/controllers/helmrelease_controller_chart.go +++ b/internal/controllers/helmrelease_controller_chart.go @@ -34,6 +34,7 @@ import ( _ "github.com/opencontainers/go-digest/blake3" "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" + apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -82,6 +83,9 @@ func (r *HelmReleaseReconciler) reconcileChart(ctx context.Context, hr *v2.HelmR case helmChartRequiresUpdate(hr, &helmChart): ctrl.LoggerFrom(ctx).Info("chart diverged from template", strings.ToLower(sourcev1.HelmChartKind), chartName.String()) helmChart.Spec = hc.Spec + helmChart.Labels = hc.Labels + helmChart.Annotations = hc.Annotations + if err = r.Client.Update(ctx, &helmChart); err != nil { return nil, err } @@ -196,8 +200,10 @@ func buildHelmChartFromTemplate(hr *v2.HelmRelease) *sourcev1.HelmChart { template := hr.Spec.Chart return &sourcev1.HelmChart{ ObjectMeta: metav1.ObjectMeta{ - Name: hr.GetHelmChartName(), - Namespace: hr.Spec.Chart.GetNamespace(hr.Namespace), + Name: hr.GetHelmChartName(), + Namespace: hr.Spec.Chart.GetNamespace(hr.Namespace), + Labels: hr.Spec.Chart.ObjectMeta.Labels, + Annotations: hr.Spec.Chart.ObjectMeta.Annotations, }, Spec: sourcev1.HelmChartSpec{ Chart: template.Spec.Chart, @@ -239,6 +245,10 @@ func helmChartRequiresUpdate(hr *v2.HelmRelease, chart *sourcev1.HelmChart) bool return true case template.Spec.ValuesFile != chart.Spec.ValuesFile: return true + case !apiequality.Semantic.DeepEqual(template.ObjectMeta.Annotations, chart.Annotations): + return true + case !apiequality.Semantic.DeepEqual(template.ObjectMeta.Labels, chart.Labels): + return true case !reflect.DeepEqual(templateVerificationToSourceVerification(template.Spec.Verify), chart.Spec.Verify): return true default: diff --git a/internal/controllers/helmrelease_controller_chart_test.go b/internal/controllers/helmrelease_controller_chart_test.go index 43001e5d4..bd676c72a 100644 --- a/internal/controllers/helmrelease_controller_chart_test.go +++ b/internal/controllers/helmrelease_controller_chart_test.go @@ -514,6 +514,20 @@ func Test_helmChartRequiresUpdate(t *testing.T) { }, want: true, }, + { + name: "detects labels change", + modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) { + hr.Spec.Chart.ObjectMeta.Labels = map[string]string{"foo": "bar"} + }, + want: true, + }, + { + name: "detects annotations change", + modify: func(hr *v2.HelmRelease, hc *sourcev1.HelmChart) { + hr.Spec.Chart.ObjectMeta.Annotations = map[string]string{"foo": "bar"} + }, + want: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {