From 9bbfbeddb4659224b73ce4d5bf8dc9be4532396c Mon Sep 17 00:00:00 2001 From: Guillaume Le Biller Date: Thu, 10 Jun 2021 19:38:11 +0200 Subject: [PATCH] Implement generic patches in Kustomization Allow patching multiple resources instead of a single existing one as StrategicMerge & JSON6902 are forced to target existing named resources. --- api/v1beta1/kustomization_types.go | 4 ++ api/v1beta1/zz_generated.deepcopy.go | 5 ++ ...mize.toolkit.fluxcd.io_kustomizations.yaml | 35 +++++++++++ .../kustomization_controller_patch_test.go | 61 +++++++++++++++++-- controllers/kustomization_generator.go | 7 +++ docs/api/kustomize.md | 28 +++++++++ docs/spec/v1beta1/kustomization.md | 41 ++++++++++--- 7 files changed, 169 insertions(+), 12 deletions(-) diff --git a/api/v1beta1/kustomization_types.go b/api/v1beta1/kustomization_types.go index f2c0c8ec6..8d6a2e8a3 100644 --- a/api/v1beta1/kustomization_types.go +++ b/api/v1beta1/kustomization_types.go @@ -83,6 +83,10 @@ type KustomizationSpec struct { // +optional HealthChecks []meta.NamespacedObjectKindReference `json:"healthChecks,omitempty"` + // Patches (also called overlays), defined as inline YAML objects. + // +optional + Patches []kustomize.Patch `json:"patches,omitempty"` + // Strategic merge patches, defined as inline YAML objects. // +optional PatchesStrategicMerge []apiextensionsv1.JSON `json:"patchesStrategicMerge,omitempty"` diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 74448f616..933861a7f 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -173,6 +173,11 @@ func (in *KustomizationSpec) DeepCopyInto(out *KustomizationSpec) { *out = make([]meta.NamespacedObjectKindReference, len(*in)) copy(*out, *in) } + if in.Patches != nil { + in, out := &in.Patches, &out.Patches + *out = make([]kustomize.Patch, len(*in)) + copy(*out, *in) + } if in.PatchesStrategicMerge != nil { in, out := &in.PatchesStrategicMerge, &out.PatchesStrategicMerge *out = make([]apiextensionsv1.JSON, len(*in)) diff --git a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml index c3890d064..2e9147c61 100644 --- a/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml +++ b/config/crd/bases/kustomize.toolkit.fluxcd.io_kustomizations.yaml @@ -142,6 +142,41 @@ spec: - name type: object type: object + patches: + description: Patches (also called overlays), defined as inline YAML objects. + items: + description: Patch contains either a StrategicMerge or a JSON6902 patch, either a file or inline, and the target the patch should be applied to. + properties: + patch: + description: Patch contains the JSON6902 patch document with an array of operation objects. + type: string + target: + description: Target points to the resources that the patch document should be applied to. + properties: + annotationSelector: + description: AnnotationSelector is a string that follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource annotations. + type: string + group: + description: Group is the API group to select resources from. Together with Version and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + kind: + description: Kind of the API Group to select resources from. Together with Group and Version it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + labelSelector: + description: LabelSelector is a string that follows the label selection expression https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#api It matches with the resource labels. + type: string + name: + description: Name to match resources with. + type: string + namespace: + description: Namespace to select resources from. + type: string + version: + description: Version of the API Group to select resources from. Together with Group and Kind it is capable of unambiguously identifying and/or selecting resources. https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/api-group.md + type: string + type: object + type: object + type: array patchesJson6902: description: JSON 6902 patches, defined as inline YAML objects. items: diff --git a/controllers/kustomization_controller_patch_test.go b/controllers/kustomization_controller_patch_test.go index b890d419a..039e3f97c 100644 --- a/controllers/kustomization_controller_patch_test.go +++ b/controllers/kustomization_controller_patch_test.go @@ -144,10 +144,62 @@ var _ = Describe("KustomizationReconciler", func() { Expect(deployment.Spec.Template.Spec.Containers[0].Image).To(Equal("ghcr.io/stefanprodan/podinfo:5.2.0")) }) + It("patches as JSON", func() { + kustomization.Spec.Patches = []kustomize.Patch{ + { + Patch: ` +- op: add + path: /metadata/labels/patch + value: inline-json + `, + Target: kustomize.Selector{ + LabelSelector: "app=podinfo", + }, + }, + } + Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed()) + + Eventually(func() bool { + var obj kustomizev1.Kustomization + _ = k8sClient.Get(context.Background(), ObjectKey(kustomization), &obj) + return obj.Status.LastAppliedRevision == "main/"+artifactChecksum + }, timeout, time.Second).Should(BeTrue()) + + var deployment appsv1.Deployment + Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: "podinfo", Namespace: namespace.Name}, &deployment)).To(Succeed()) + Expect(deployment.ObjectMeta.Labels["patch"]).To(Equal("inline-json")) + }) + + It("patches as YAML", func() { + kustomization.Spec.Patches = []kustomize.Patch{ + { + Patch: ` +apiVersion: v1 +kind: Pod +metadata: + name: podinfo + labels: + patch: inline-yaml + `, + }, + } + Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed()) + + Eventually(func() bool { + var obj kustomizev1.Kustomization + _ = k8sClient.Get(context.Background(), ObjectKey(kustomization), &obj) + return obj.Status.LastAppliedRevision == "main/"+artifactChecksum + }, timeout, time.Second).Should(BeTrue()) + + var deployment appsv1.Deployment + Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: "podinfo", Namespace: namespace.Name}, &deployment)).To(Succeed()) + Expect(deployment.ObjectMeta.Labels["patch"]).To(Equal("inline-yaml")) + }) + It("strategic merge patches", func() { kustomization.Spec.PatchesStrategicMerge = []apiextensionsv1.JSON{ { - Raw: []byte(`{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"podinfo","labels":{"xxxx":"yyyy"}}}`), + Raw: []byte(`{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"podinfo","labels":{"patch":"strategic-merge"}}}`), }, } Expect(k8sClient.Create(context.TODO(), kustomization)).To(Succeed()) @@ -160,15 +212,14 @@ var _ = Describe("KustomizationReconciler", func() { var deployment appsv1.Deployment Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: "podinfo", Namespace: namespace.Name}, &deployment)).To(Succeed()) - Expect(deployment.ObjectMeta.Labels["xxxx"]).To(Equal("yyyy")) + Expect(deployment.ObjectMeta.Labels["patch"]).To(Equal("strategic-merge")) }) It("JSON6902 patches", func() { kustomization.Spec.PatchesJSON6902 = []kustomize.JSON6902Patch{ { - Patch: []kustomize.JSON6902{ - {Op: "add", Path: "/metadata/labels/yyyy", Value: &apiextensionsv1.JSON{Raw: []byte(`"xxxx"`)}}, + {Op: "add", Path: "/metadata/labels/patch", Value: &apiextensionsv1.JSON{Raw: []byte(`"json6902"`)}}, {Op: "replace", Path: "/spec/replicas", Value: &apiextensionsv1.JSON{Raw: []byte("2")}}, }, Target: kustomize.Selector{ @@ -189,7 +240,7 @@ var _ = Describe("KustomizationReconciler", func() { var deployment appsv1.Deployment Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: "podinfo", Namespace: namespace.Name}, &deployment)).To(Succeed()) - Expect(deployment.ObjectMeta.Labels["yyyy"]).To(Equal("xxxx")) + Expect(deployment.ObjectMeta.Labels["patch"]).To(Equal("json6902")) Expect(*deployment.Spec.Replicas).To(Equal(int32(2))) }) }) diff --git a/controllers/kustomization_generator.go b/controllers/kustomization_generator.go index 7c47114ed..a1c5973f7 100644 --- a/controllers/kustomization_generator.go +++ b/controllers/kustomization_generator.go @@ -104,6 +104,13 @@ func (kg *KustomizeGenerator) WriteFile(ctx context.Context, dirPath string) (st kus.Namespace = kg.kustomization.Spec.TargetNamespace } + for _, m := range kg.kustomization.Spec.Patches { + kus.Patches = append(kus.Patches, kustypes.Patch{ + Patch: m.Patch, + Target: adaptSelector(&m.Target), + }) + } + for _, m := range kg.kustomization.Spec.PatchesStrategicMerge { kus.PatchesStrategicMerge = append(kus.PatchesStrategicMerge, kustypes.PatchStrategicMerge(m.Raw)) } diff --git a/docs/api/kustomize.md b/docs/api/kustomize.md index 84f1cb06d..5ccdfec94 100644 --- a/docs/api/kustomize.md +++ b/docs/api/kustomize.md @@ -198,6 +198,20 @@ bool +patches
+ + +[]github.com/fluxcd/pkg/apis/kustomize.Patch + + + + +(Optional) +

Patches (also called overlays), defined as inline YAML objects.

+ + + + patchesStrategicMerge
@@ -657,6 +671,20 @@ bool +patches
+ +
+[]github.com/fluxcd/pkg/apis/kustomize.Patch + + + + +(Optional) +

Patches (also called overlays), defined as inline YAML objects.

+ + + + patchesStrategicMerge
diff --git a/docs/spec/v1beta1/kustomization.md b/docs/spec/v1beta1/kustomization.md index 3e58ed1e8..92b609209 100644 --- a/docs/spec/v1beta1/kustomization.md +++ b/docs/spec/v1beta1/kustomization.md @@ -598,14 +598,15 @@ The Kustomization has a set of fields to extend and/or override the Kustomize patches and namespace on all the Kubernetes objects reconciled by the resource, offering support for the following Kustomize directives: -- [namespace](https://kubectl.docs.kubernetes.io/references/kustomize/namespace/) +- [namespace](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/namespace/) +- [patches](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/) - [patchesStrategicMerge](https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/) -- [patchesJson6902](https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/) -- [images](https://kubectl.docs.kubernetes.io/references/kustomize/images/) +- [patchesJson6902](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patchesjson6902/) +- [images](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/images/) ### Target namespace -To configure the [Kustomize `namespace`](https://kubectl.docs.kubernetes.io/references/kustomize/namespace/) +To configure the [Kustomize `namespace`](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/namespace/) and overwrite the namespace of all the Kubernetes objects reconciled by the `Kustomization`, `spec.targetNamespace` can be defined: @@ -622,9 +623,35 @@ spec: The `targetNamespace` is expected to exist. +### Patches + +To add [Kustomize `patches` entries](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patches/) +to the configuration, and patch resources using either a [strategic merge](https://kubectl.docs.kubernetes.io/references/kustomize/glossary#patchstrategicmerge) +patch or a [JSON](https://kubectl.docs.kubernetes.io/references/kustomize/glossary#patchjson6902) patch, +`spec.patches` items must contain a `target` selector and a `patch` document. +The patch can target a single resource or multiple resources + +```yaml +apiVersion: kustomize.toolkit.fluxcd.io/v1beta1 +kind: Kustomization +metadata: + name: podinfo + namespace: flux-system +spec: + # ...omitted for brevity + patches: + - patch: |- + apiVersion: v1 + kind: Pod + metadata: + name: dummy-app + labels: + app.kubernetes.io/part-of: test-app +``` + ### Strategic Merge patches -To add [Kustomize `patchesStrategicMerge` entries](https://kubectl.docs.kubernetes.io/references/kustomize/patchesstrategicmerge/) +To add [Kustomize `patchesStrategicMerge` entries](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patchesstrategicmerge/) to the configuration, `spec.patchesStrategicMerge` can be defined with a list of strategic merge patches in YAML format: @@ -649,7 +676,7 @@ spec: ### JSON 6902 patches -To add [Kustomize `patchesJson6902` entries](https://kubectl.docs.kubernetes.io/references/kustomize/patchesjson6902/) +To add [Kustomize `patchesJson6902` entries](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patchesjson6902/) to the configuration, and patch resources using the [JSON 6902 standard](https://tools.ietf.org/html/rfc6902), `spec.patchesJson6902`, the items must contain a `target` selector and JSON 6902 `patch` document: @@ -675,7 +702,7 @@ spec: ### Images -To add [Kustomize `images` entries](https://kubectl.docs.kubernetes.io/references/kustomize/images/) +To add [Kustomize `images` entries](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/images/) to the configuration, and overwrite the name, tag or digest of container images without creating patches, `spec.images` can be defined: