From 879cf5b46de6bbfde8d9ecb996d046ed0efd663f Mon Sep 17 00:00:00 2001 From: Hidde Beydals Date: Thu, 18 Mar 2021 21:05:49 +0100 Subject: [PATCH] Label release resources with HelmRelease origin This commit adds a new post renderer that labels all resources with `helm.toolkit.fluxcd.io/name` and `helm.toolkit.fluxcd.io/namespace` so their source of origin can be traced back by e.g. the Flux UI. The post renderer makes use of the Kustomize API without running a full Kustomize build, by making directly use of the builtin `LabelTransformerPlugin` on a `ResMap` that has been constructed from the bytes of the `bytes.Buffer` given by Helm. Signed-off-by: Hidde Beydals --- .../runner/post_renderer_origin_labels.go | 76 +++++++++++++++++ .../post_renderer_origin_labels_test.go | 83 +++++++++++++++++++ internal/runner/runner.go | 1 + 3 files changed, 160 insertions(+) create mode 100644 internal/runner/post_renderer_origin_labels.go create mode 100644 internal/runner/post_renderer_origin_labels_test.go diff --git a/internal/runner/post_renderer_origin_labels.go b/internal/runner/post_renderer_origin_labels.go new file mode 100644 index 000000000..0d01e653d --- /dev/null +++ b/internal/runner/post_renderer_origin_labels.go @@ -0,0 +1,76 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package runner + +import ( + "bytes" + "fmt" + + "sigs.k8s.io/kustomize/api/builtins" + "sigs.k8s.io/kustomize/api/k8sdeps/kunstruct" + "sigs.k8s.io/kustomize/api/resmap" + "sigs.k8s.io/kustomize/api/resource" + kustypes "sigs.k8s.io/kustomize/api/types" + + v2 "github.com/fluxcd/helm-controller/api/v2beta1" +) + +func newPostRendererOriginLabels(release *v2.HelmRelease) *postRendererOriginLabels { + return &postRendererOriginLabels{ + name: release.ObjectMeta.Name, + namespace: release.ObjectMeta.Namespace, + } +} + +type postRendererOriginLabels struct { + name string + namespace string +} + +func (k *postRendererOriginLabels) Run(renderedManifests *bytes.Buffer) (modifiedManifests *bytes.Buffer, err error) { + resFactory := resource.NewFactory(kunstruct.NewKunstructuredFactoryImpl()) + resMapFactory := resmap.NewFactory(resFactory, nil) + + resMap, err := resMapFactory.NewResMapFromBytes(renderedManifests.Bytes()) + if err != nil { + return nil, err + } + + labelTransformer := builtins.LabelTransformerPlugin{ + Labels: originLabels(k.name, k.namespace), + FieldSpecs: []kustypes.FieldSpec{ + {Path: "metadata/labels", CreateIfNotPresent: true}, + }, + } + if err := labelTransformer.Transform(resMap); err != nil { + return nil, err + } + + yaml, err := resMap.AsYaml() + if err != nil { + return nil, err + } + + return bytes.NewBuffer(yaml), nil +} + +func originLabels(name, namespace string) map[string]string { + return map[string]string{ + fmt.Sprintf("%s/name", v2.GroupVersion.Group): name, + fmt.Sprintf("%s/namespace", v2.GroupVersion.Group): namespace, + } +} diff --git a/internal/runner/post_renderer_origin_labels_test.go b/internal/runner/post_renderer_origin_labels_test.go new file mode 100644 index 000000000..14a03c23a --- /dev/null +++ b/internal/runner/post_renderer_origin_labels_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2021 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package runner + +import ( + "bytes" + "reflect" + "testing" +) + +const mixedResourceMock = `apiVersion: v1 +kind: Pod +metadata: + name: pod-without-labels +--- +apiVersion: v1 +kind: Service +metadata: + name: service-with-labels + labels: + existing: label +` + +func Test_postRendererOriginLabels_Run(t *testing.T) { + tests := []struct { + name string + renderedManifests string + expectManifests string + expectErr bool + }{ + { + name: "labels", + renderedManifests: mixedResourceMock, + expectManifests: `apiVersion: v1 +kind: Pod +metadata: + labels: + helm.toolkit.fluxcd.io/name: name + helm.toolkit.fluxcd.io/namespace: namespace + name: pod-without-labels +--- +apiVersion: v1 +kind: Service +metadata: + labels: + existing: label + helm.toolkit.fluxcd.io/name: name + helm.toolkit.fluxcd.io/namespace: namespace + name: service-with-labels +`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + k := &postRendererOriginLabels{ + name: "name", + namespace: "namespace", + } + gotModifiedManifests, err := k.Run(bytes.NewBufferString(tt.renderedManifests)) + if (err != nil) != tt.expectErr { + t.Errorf("Run() error = %v, expectErr %v", err, tt.expectErr) + return + } + if !reflect.DeepEqual(gotModifiedManifests, bytes.NewBufferString(tt.expectManifests)) { + t.Errorf("Run() gotModifiedManifests = %v, want %v", gotModifiedManifests, tt.expectManifests) + } + }) + } +} diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 376cfcd20..4a0f469b0 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -58,6 +58,7 @@ func postRenderers(hr v2.HelmRelease) (postrender.PostRenderer, error) { combinedRenderer.addRenderer(newPostRendererKustomize(r.Kustomize)) } } + combinedRenderer.addRenderer(newPostRendererOriginLabels(&hr)) if len(combinedRenderer.renderers) == 0 { return nil, nil }