Implement "kind: KustomizationPatch" to support composition of Kustomize files

This commit is contained in:
Paul Martin
2019-12-04 18:30:39 -08:00
parent 7d5304928b
commit 4454edc14c
3 changed files with 367 additions and 20 deletions

View File

@@ -155,8 +155,12 @@ func (kt *KustTarget) addHashesToNames(
// not yet fixed. // not yet fixed.
func (kt *KustTarget) AccumulateTarget() ( func (kt *KustTarget) AccumulateTarget() (
ra *accumulator.ResAccumulator, err error) { ra *accumulator.ResAccumulator, err error) {
ra = accumulator.MakeEmptyAccumulator() return kt.accumulateTarget(accumulator.MakeEmptyAccumulator())
err = kt.accumulateResources(ra, kt.kustomization.Resources) }
func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator) (
resRa *accumulator.ResAccumulator, err error) {
ra, err = kt.accumulateResources(ra, kt.kustomization.Resources)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "accumulating resources") return nil, errors.Wrap(err, "accumulating resources")
} }
@@ -224,7 +228,7 @@ func (kt *KustTarget) runGenerators(
func (kt *KustTarget) configureExternalGenerators() ([]resmap.Generator, error) { func (kt *KustTarget) configureExternalGenerators() ([]resmap.Generator, error) {
ra := accumulator.MakeEmptyAccumulator() ra := accumulator.MakeEmptyAccumulator()
err := kt.accumulateResources(ra, kt.kustomization.Generators) ra, err := kt.accumulateResources(ra, kt.kustomization.Generators)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -250,7 +254,7 @@ func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error {
func (kt *KustTarget) configureExternalTransformers() ([]resmap.Transformer, error) { func (kt *KustTarget) configureExternalTransformers() ([]resmap.Transformer, error) {
ra := accumulator.MakeEmptyAccumulator() ra := accumulator.MakeEmptyAccumulator()
err := kt.accumulateResources(ra, kt.kustomization.Transformers) ra, err := kt.accumulateResources(ra, kt.kustomization.Transformers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -260,44 +264,52 @@ func (kt *KustTarget) configureExternalTransformers() ([]resmap.Transformer, err
// accumulateResources fills the given resourceAccumulator // accumulateResources fills the given resourceAccumulator
// with resources read from the given list of paths. // with resources read from the given list of paths.
func (kt *KustTarget) accumulateResources( func (kt *KustTarget) accumulateResources(
ra *accumulator.ResAccumulator, paths []string) error { ra *accumulator.ResAccumulator, paths []string) (*accumulator.ResAccumulator, error) {
for _, path := range paths { for _, path := range paths {
// try loading resource as file then as base (directory or git repository) // try loading resource as file then as base (directory or git repository)
if errF := kt.accumulateFile(ra, path); errF != nil { if errF := kt.accumulateFile(ra, path); errF != nil {
ldr, errL := kt.ldr.New(path) ldr, errL := kt.ldr.New(path)
if errL != nil { if errL != nil {
return fmt.Errorf("accumulateFile %q, loader.New %q", errF, errL) return nil, fmt.Errorf("accumulateFile %q, loader.New %q", errF, errL)
} }
errD := kt.accumulateDirectory(ra, ldr) var errD error
ra, errD = kt.accumulateDirectory(ra, ldr)
if errD != nil { if errD != nil {
return fmt.Errorf("accumulateFile %q, accumulateDirector: %q", errF, errD) return nil, fmt.Errorf("accumulateFile %q, accumulateDirector: %q", errF, errD)
} }
} }
} }
return nil return ra, nil
} }
func (kt *KustTarget) accumulateDirectory( func (kt *KustTarget) accumulateDirectory(
ra *accumulator.ResAccumulator, ldr ifc.Loader) error { ra *accumulator.ResAccumulator, ldr ifc.Loader) (*accumulator.ResAccumulator, error) {
defer ldr.Cleanup() defer ldr.Cleanup()
subKt := NewKustTarget( subKt := NewKustTarget(
ldr, kt.validator, kt.rFactory, kt.tFactory, kt.pLdr) ldr, kt.validator, kt.rFactory, kt.tFactory, kt.pLdr)
err := subKt.Load() err := subKt.Load()
if err != nil { if err != nil {
return errors.Wrapf( return nil, errors.Wrapf(
err, "couldn't make target for path '%s'", ldr.Root()) err, "couldn't make target for path '%s'", ldr.Root())
} }
subRa, err := subKt.AccumulateTarget()
var subRa *accumulator.ResAccumulator
if subKt.kustomization.Kind == types.KustomizationPatchKind {
subRa, err = subKt.accumulateTarget(ra)
ra = accumulator.MakeEmptyAccumulator()
} else {
subRa, err = subKt.AccumulateTarget()
}
if err != nil { if err != nil {
return errors.Wrapf( return nil, errors.Wrapf(
err, "recursed accumulation of path '%s'", ldr.Root()) err, "recursed accumulation of path '%s'", ldr.Root())
} }
err = ra.MergeAccumulator(subRa) err = ra.MergeAccumulator(subRa)
if err != nil { if err != nil {
return errors.Wrapf( return nil, errors.Wrapf(
err, "recursed merging from path '%s'", ldr.Root()) err, "recursed merging from path '%s'", ldr.Root())
} }
return nil return ra, nil
} }
func (kt *KustTarget) accumulateFile( func (kt *KustTarget) accumulateFile(

View File

@@ -0,0 +1,334 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package krusty_test
import (
"testing"
kusttest_test "sigs.k8s.io/kustomize/api/testutils/kusttest"
)
// Test kustomization.yaml files with kind: KustomizationPatch
func writeKustomizationPatchBase(th kusttest_test.Harness) {
th.WriteK("/app/base", `
resources:
- deploy.yaml
configMapGenerator:
- name: my-configmap
literals:
- testValue=1
- otherValue=10
`)
th.WriteF("/app/base/deploy.yaml", `
apiVersion: v1
kind: Deployment
metadata:
name: storefront
spec:
replicas: 1
`)
}
func writeKustomizationPatchPatch(th kusttest_test.Harness) {
th.WriteF("/app/patch/kustomization.yaml", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: KustomizationPatch
namePrefix: patched-
replicas:
- name: storefront
count: 3
resources:
- stub.yaml
configMapGenerator:
- name: my-configmap
behavior: merge
literals:
- testValue=2
- patchValue=5
`)
th.WriteF("/app/patch/stub.yaml", `
apiVersion: v1
kind: Deployment
metadata:
name: stub
spec:
replicas: 1
`)
}
func writeKustomizationPatchProd(th kusttest_test.Harness) {
th.WriteK("/app/prod", `
resources:
- ../base
- ../patch
- db
`)
th.WriteF("/app/prod/db", `
apiVersion: v1
kind: Deployment
metadata:
name: db
spec:
type: Logical
`)
}
// KustomizationPatch are inserted into the resource hierarchy as the parent of those
// resources that come before it in the resources list of the parent Kustomization.
func TestBasicKustomizationPatch(t *testing.T) {
th := kusttest_test.MakeHarness(t)
writeKustomizationPatchBase(th)
writeKustomizationPatchPatch(th)
writeKustomizationPatchProd(th)
m := th.Run("/app/prod", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: patched-storefront
spec:
replicas: 3
---
apiVersion: v1
data:
otherValue: "10"
patchValue: "5"
testValue: "2"
kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: patched-my-configmap-5g55h28cdh
---
apiVersion: v1
kind: Deployment
metadata:
name: patched-stub
spec:
replicas: 1
---
apiVersion: v1
kind: Deployment
metadata:
name: db
spec:
type: Logical
`)
}
func TestMultipleKustomizationPatches(t *testing.T) {
th := kusttest_test.MakeHarness(t)
writeKustomizationPatchBase(th)
writeKustomizationPatchPatch(th)
writeKustomizationPatchProd(th)
th.WriteF("/app/additionalpatch/kustomization.yaml", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: KustomizationPatch
configMapGenerator:
- name: my-configmap
behavior: merge
literals:
- otherValue=9
`)
th.WriteK("/app/prod", `
resources:
- ../base
- ../patch
- ../additionalpatch
- db
`)
m := th.Run("/app/prod", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: patched-storefront
spec:
replicas: 3
---
apiVersion: v1
data:
otherValue: "9"
patchValue: "5"
testValue: "2"
kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: patched-my-configmap-9fddc87cdk
---
apiVersion: v1
kind: Deployment
metadata:
name: patched-stub
spec:
replicas: 1
---
apiVersion: v1
kind: Deployment
metadata:
name: db
spec:
type: Logical
`)
}
func TestNestedKustomizationPatches(t *testing.T) {
th := kusttest_test.MakeHarness(t)
writeKustomizationPatchBase(th)
writeKustomizationPatchPatch(th)
writeKustomizationPatchProd(th)
th.WriteF("/app/additionalpatch/kustomization.yaml", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: KustomizationPatch
resources:
- ../patch
configMapGenerator:
- name: my-configmap
behavior: merge
literals:
- otherValue=9
`)
th.WriteK("/app/prod", `
resources:
- ../base
- ../additionalpatch
- db
`)
m := th.Run("/app/prod", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: patched-storefront
spec:
replicas: 3
---
apiVersion: v1
data:
otherValue: "9"
patchValue: "5"
testValue: "2"
kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: patched-my-configmap-9fddc87cdk
---
apiVersion: v1
kind: Deployment
metadata:
name: patched-stub
spec:
replicas: 1
---
apiVersion: v1
kind: Deployment
metadata:
name: db
spec:
type: Logical
`)
}
// If a patch sets a name prefix on a base, then that base can also be separately included
// without being affected by the patch in another branch of the resource tree
func TestBasicKustomizationPatchWithRepeatedBase(t *testing.T) {
th := kusttest_test.MakeHarness(t)
writeKustomizationPatchBase(th)
writeKustomizationPatchPatch(th)
writeKustomizationPatchProd(th)
th.WriteK("/app/repeated", `
resources:
- ../base
- ../prod
`)
m := th.Run("/app/repeated", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: storefront
spec:
replicas: 1
---
apiVersion: v1
data:
otherValue: "10"
testValue: "1"
kind: ConfigMap
metadata:
name: my-configmap-7k9t4h74f8
---
apiVersion: v1
kind: Deployment
metadata:
name: patched-storefront
spec:
replicas: 3
---
apiVersion: v1
data:
otherValue: "10"
patchValue: "5"
testValue: "2"
kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: patched-my-configmap-5g55h28cdh
---
apiVersion: v1
kind: Deployment
metadata:
name: patched-stub
spec:
replicas: 1
---
apiVersion: v1
kind: Deployment
metadata:
name: db
spec:
type: Logical
`)
}
func TestApplyingKustomizationPatchDirectlySameAsKustomization(t *testing.T) {
th := kusttest_test.MakeHarness(t)
writeKustomizationPatchBase(th)
writeKustomizationPatchPatch(th)
th.WriteF("/app/solopatch/kustomization.yaml", `
apiVersion: kustomize.config.k8s.io/v1beta1
kind: KustomizationPatch
resources:
- ../base
configMapGenerator:
- name: my-configmap
behavior: merge
literals:
- patchValue=5
- testValue=2
`)
m := th.Run("/app/solopatch", th.MakeDefaultOptions())
th.AssertActualEqualsExpected(m, `
apiVersion: v1
kind: Deployment
metadata:
name: storefront
spec:
replicas: 1
---
apiVersion: v1
data:
otherValue: "10"
patchValue: "5"
testValue: "2"
kind: ConfigMap
metadata:
annotations: {}
labels: {}
name: my-configmap-t86ktk6tdk
`)
}

View File

@@ -4,9 +4,10 @@
package types package types
const ( const (
KustomizationVersion = "kustomize.config.k8s.io/v1beta1" KustomizationVersion = "kustomize.config.k8s.io/v1beta1"
KustomizationKind = "Kustomization" KustomizationKind = "Kustomization"
MetadataNamespacePath = "metadata/namespace" KustomizationPatchKind = "KustomizationPatch"
MetadataNamespacePath = "metadata/namespace"
) )
// Kustomization holds the information needed to generate customized k8s api resources. // Kustomization holds the information needed to generate customized k8s api resources.
@@ -143,8 +144,8 @@ func (k *Kustomization) EnforceFields() []string {
if k.APIVersion != "" && k.APIVersion != KustomizationVersion { if k.APIVersion != "" && k.APIVersion != KustomizationVersion {
errs = append(errs, "apiVersion should be "+KustomizationVersion) errs = append(errs, "apiVersion should be "+KustomizationVersion)
} }
if k.Kind != "" && k.Kind != KustomizationKind { if k.Kind != "" && k.Kind != KustomizationKind && k.Kind != KustomizationPatchKind {
errs = append(errs, "kind should be "+KustomizationKind) errs = append(errs, "kind should be "+KustomizationKind+" or "+KustomizationPatchKind)
} }
return errs return errs
} }