From 2cc2fee41025c54a94f8f0b0bd0f12a6836156af Mon Sep 17 00:00:00 2001 From: Raffael Sahli Date: Mon, 6 Mar 2023 13:50:07 +0000 Subject: [PATCH 1/2] feat: manage label and annotations for a helmchart Signed-off-by: Raffael Sahli --- api/v2beta1/helmrelease_types.go | 19 ++++++ api/v2beta1/zz_generated.deepcopy.go | 30 +++++++++ .../helm.toolkit.fluxcd.io_helmreleases.yaml | 21 +++++++ docs/api/helmrelease.md | 63 +++++++++++++++++++ .../helmrelease_controller_chart.go | 14 ++++- 5 files changed, 145 insertions(+), 2 deletions(-) diff --git a/api/v2beta1/helmrelease_types.go b/api/v2beta1/helmrelease_types.go index 69743fca1..c74ce00d7 100644 --- a/api/v2beta1/helmrelease_types.go +++ b/api/v2beta1/helmrelease_types.go @@ -219,11 +219,30 @@ 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 including Labels and Annotations + // +optional + HelmChartTemplateObjectMeta `json:"metadata,omitempty"` + // Spec holds the template for the v1beta2.HelmChartSpec for this HelmRelease. // +required Spec HelmChartTemplateSpec `json:"spec"` } +type HelmChartTemplateObjectMeta struct { + // Map of string keys and values that can be used to organize and categorize + // (scope and select) objects. + // More info: http://kubernetes.io/docs/user-guide/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: http://kubernetes.io/docs/user-guide/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..7862e8220 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.HelmChartTemplateObjectMeta.DeepCopyInto(&out.HelmChartTemplateObjectMeta) 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..5dc274baa 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 including + 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: + http://kubernetes.io/docs/user-guide/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: http://kubernetes.io/docs/user-guide/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..ff863a39e 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 including Labels and Annotations

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

HelmChartTemplateObjectMeta +

+

+(Appears on: +HelmChartTemplate) +

+
+
+ + + + + + + + + + + + + + + + + +
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: http://kubernetes.io/docs/user-guide/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: http://kubernetes.io/docs/user-guide/annotations

+
+
+

HelmChartTemplateSpec

diff --git a/internal/controllers/helmrelease_controller_chart.go b/internal/controllers/helmrelease_controller_chart.go index daee87f40..9aa192150 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.Labels, + Annotations: hr.Spec.Chart.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.Annotations, chart.Annotations): + return true + case !apiequality.Semantic.DeepEqual(template.Labels, chart.Labels): + return true case !reflect.DeepEqual(templateVerificationToSourceVerification(template.Spec.Verify), chart.Spec.Verify): return true default: From a72a2fc6ca7b294956e6bd6dba8b3fde34b2f759 Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Wed, 29 Mar 2023 14:32:13 +0200 Subject: [PATCH 2/2] misc: tidy HelmChart annotation and labels feat - Assing `ObjectMeta` field in Helm chart template. - Ensure things are at least lightly mentioned in spec documentation. - Add two simple test cases. - Fix broken links to Kubernetes documentation. Signed-off-by: Hidde Beydals --- api/v2beta1/helmrelease_types.go | 10 ++++--- api/v2beta1/zz_generated.deepcopy.go | 2 +- .../helm.toolkit.fluxcd.io_helmreleases.yaml | 8 ++--- docs/api/helmrelease.md | 8 +++-- docs/spec/v2beta1/helmreleases.md | 30 +++++++++++++++++-- .../helmrelease_controller_chart.go | 8 ++--- .../helmrelease_controller_chart_test.go | 14 +++++++++ 7 files changed, 61 insertions(+), 19 deletions(-) diff --git a/api/v2beta1/helmrelease_types.go b/api/v2beta1/helmrelease_types.go index c74ce00d7..1cec7f3d2 100644 --- a/api/v2beta1/helmrelease_types.go +++ b/api/v2beta1/helmrelease_types.go @@ -219,26 +219,28 @@ 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 including Labels and Annotations + // ObjectMeta holds the template for metadata like labels and annotations. // +optional - HelmChartTemplateObjectMeta `json:"metadata,omitempty"` + 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: http://kubernetes.io/docs/user-guide/labels + // 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: http://kubernetes.io/docs/user-guide/annotations + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ // +optional Annotations map[string]string `json:"annotations,omitempty"` } diff --git a/api/v2beta1/zz_generated.deepcopy.go b/api/v2beta1/zz_generated.deepcopy.go index 7862e8220..252c475b3 100644 --- a/api/v2beta1/zz_generated.deepcopy.go +++ b/api/v2beta1/zz_generated.deepcopy.go @@ -47,7 +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.HelmChartTemplateObjectMeta.DeepCopyInto(&out.HelmChartTemplateObjectMeta) + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) } diff --git a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml index 5dc274baa..8e60df492 100644 --- a/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml +++ b/config/crd/bases/helm.toolkit.fluxcd.io_helmreleases.yaml @@ -52,8 +52,8 @@ spec: should be created for this HelmRelease. properties: metadata: - description: ObjectMeta holds the template for metadata including - Labels and Annotations + description: ObjectMeta holds the template for metadata like labels + and annotations. properties: annotations: additionalProperties: @@ -62,14 +62,14 @@ spec: 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: - http://kubernetes.io/docs/user-guide/annotations' + 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: http://kubernetes.io/docs/user-guide/labels' + info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/' type: object type: object spec: diff --git a/docs/api/helmrelease.md b/docs/api/helmrelease.md index ff863a39e..844aa2d14 100644 --- a/docs/api/helmrelease.md +++ b/docs/api/helmrelease.md @@ -466,7 +466,7 @@ HelmChartTemplateObjectMeta (Optional) -

ObjectMeta holds the template for metadata including Labels and Annotations

+

ObjectMeta holds the template for metadata like labels and annotations.

@@ -611,6 +611,8 @@ Chart dependencies, which are not bundled in the umbrella chart artifact, are no (Appears on: HelmChartTemplate)

+

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

@@ -632,7 +634,7 @@ map[string]string (Optional)

Map of string keys and values that can be used to organize and categorize (scope and select) objects. -More info: http://kubernetes.io/docs/user-guide/labels

+More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/

@@ -647,7 +649,7 @@ map[string]string

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: http://kubernetes.io/docs/user-guide/annotations

+More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/

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 9aa192150..532e56959 100644 --- a/internal/controllers/helmrelease_controller_chart.go +++ b/internal/controllers/helmrelease_controller_chart.go @@ -202,8 +202,8 @@ func buildHelmChartFromTemplate(hr *v2.HelmRelease) *sourcev1.HelmChart { ObjectMeta: metav1.ObjectMeta{ Name: hr.GetHelmChartName(), Namespace: hr.Spec.Chart.GetNamespace(hr.Namespace), - Labels: hr.Spec.Chart.Labels, - Annotations: hr.Spec.Chart.Annotations, + Labels: hr.Spec.Chart.ObjectMeta.Labels, + Annotations: hr.Spec.Chart.ObjectMeta.Annotations, }, Spec: sourcev1.HelmChartSpec{ Chart: template.Spec.Chart, @@ -245,9 +245,9 @@ 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.Annotations, chart.Annotations): + case !apiequality.Semantic.DeepEqual(template.ObjectMeta.Annotations, chart.Annotations): return true - case !apiequality.Semantic.DeepEqual(template.Labels, chart.Labels): + case !apiequality.Semantic.DeepEqual(template.ObjectMeta.Labels, chart.Labels): return true case !reflect.DeepEqual(templateVerificationToSourceVerification(template.Spec.Verify), chart.Spec.Verify): return true 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) {