diff --git a/api/internal/target/kusttarget.go b/api/internal/target/kusttarget.go index 4cade158d..275ebc990 100644 --- a/api/internal/target/kusttarget.go +++ b/api/internal/target/kusttarget.go @@ -209,6 +209,10 @@ func (kt *KustTarget) accumulateTarget(ra *accumulator.ResAccumulator) ( if err != nil { return nil, err } + err = kt.runValidators(ra) + if err != nil { + return nil, err + } err = ra.MergeVars(kt.kustomization.Vars) if err != nil { return nil, errors.Wrapf( @@ -260,7 +264,7 @@ func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error { return err } r = append(r, lts...) - lts, err = kt.configureExternalTransformers() + lts, err = kt.configureExternalTransformers(kt.kustomization.Transformers) if err != nil { return err } @@ -269,15 +273,54 @@ func (kt *KustTarget) runTransformers(ra *accumulator.ResAccumulator) error { return ra.Transform(t) } -func (kt *KustTarget) configureExternalTransformers() ([]resmap.Transformer, error) { +func (kt *KustTarget) configureExternalTransformers(transformers []string) ([]resmap.Transformer, error) { ra := accumulator.MakeEmptyAccumulator() - ra, err := kt.accumulateResources(ra, kt.kustomization.Transformers) + ra, err := kt.accumulateResources(ra, transformers) + if err != nil { return nil, err } return kt.pLdr.LoadTransformers(kt.ldr, kt.validator, ra.ResMap()) } +func (kt *KustTarget) runValidators(ra *accumulator.ResAccumulator) error { + validators, err := kt.configureExternalTransformers(kt.kustomization.Validators) + if err != nil { + return err + } + for _, v := range validators { + // Validators shouldn't modify the resource map + orignal := ra.ResMap().DeepCopy() + err = v.Transform(ra.ResMap()) + if err != nil { + return err + } + new := ra.ResMap().DeepCopy() + kt.removeValidatedByLabel(new) + if err = orignal.ErrorIfNotEqualSets(new); err != nil { + return fmt.Errorf("validator shouldn't modify the resource map: %v", err) + } + } + return nil +} + +func (kt *KustTarget) removeValidatedByLabel(rm resmap.ResMap) { + + resources := rm.Resources() + for _, r := range resources { + labels := r.GetLabels() + if _, found := labels[konfig.ValidatedByLabelKey]; !found { + continue + } + delete(labels, konfig.ValidatedByLabelKey) + if len(labels) == 0 { + r.SetLabels(nil) + } else { + r.SetLabels(labels) + } + } +} + // accumulateResources fills the given resourceAccumulator // with resources read from the given list of paths. func (kt *KustTarget) accumulateResources( diff --git a/api/konfig/general.go b/api/konfig/general.go index 80a7d9be1..de0292415 100644 --- a/api/konfig/general.go +++ b/api/konfig/general.go @@ -36,4 +36,7 @@ const ( // An environment variable to turn on/off adding the ManagedByLabelKey EnableManagedbyLabelEnv = "KUSTOMIZE_ENABLE_MANAGEDBY_LABEL" + + // Label key that indicates the resources are validated by a validator + ValidatedByLabelKey = "validated-by" ) diff --git a/api/types/kustomization.go b/api/types/kustomization.go index 4a2c42d36..e7a1f9dac 100644 --- a/api/types/kustomization.go +++ b/api/types/kustomization.go @@ -124,6 +124,9 @@ type Kustomization struct { // Transformers is a list of files containing transformers Transformers []string `json:"transformers,omitempty" yaml:"transformers,omitempty"` + // Validators is a list of files containing validators + Validators []string `json:"validators,omitempty" yaml:"validators,omitempty"` + // Inventory appends an object that contains the record // of all other objects, which can be used in apply, prune and delete Inventory *Inventory `json:"inventory,omitempty" yaml:"inventory,omitempty"` diff --git a/examples/validatorPlugin.md b/examples/validatorPlugin.md new file mode 100644 index 000000000..6c350410e --- /dev/null +++ b/examples/validatorPlugin.md @@ -0,0 +1,224 @@ +# Examples for Validator Plugin + +Previously, Kustomize suggested to used a transformer plugin to [perform validation](https://github.com/kubernetes-sigs/kustomize/tree/master/examples/validationTransformer). Now we introduce a new type of plugin: validator. As the name says, validator is used to validate the result YAML output. It works in the same way with *transformers* but cannot *modify* the input YAML content. Let's take a look at how it works. + +## Make a Place to Work + + +``` +DEMO_HOME=$(mktemp -d) +mkdir -p $DEMO_HOME/valid +PLUGINDIR=$DEMO_HOME/kustomize/plugin/someteam.example.com/v1/validator +mkdir -p $PLUGINDIR +``` + +## Write a Validator Plugin + +Kustomize has the following assumption of a validator plugin: +- The resources are passed to the validator plugin from stdin. +- The configuration file for the validator plugin is passed in + as the first argument. +- The working directory of the plugin is the kustomization + directory where it is used as a validator. +- The validated resources are written to stdout by the plugin. Or the validator can print nothing to the stdout if there is no need to change the input. +- Validator can **only** add a label named `validated-by` (case-sensitive) to the **top-level** resources. If there is any other modification in the validator, Kustomize will throw an error. +- If the return code of the transformer plugin is non zero, + Kustomize regards there is an error during the validation. + +You can use either exec plugin or Go plugin as a validator. Here we use a bash script as an exec plugin. + + +```bash +cat <<'EOF' > $PLUGINDIR/Validator +#!/bin/bash + +# Do whatever you want here. In this example we +# just print out the input + +cat + +EOF +chmod +x $PLUGINDIR/Validator +``` + +## Use the Validator Plugin + +Define a kustomization containing a valid ConfigMap +and the transformer plugin. + + +```bash +cat <<'EOF' >$DEMO_HOME/valid/configmap.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: cm +data: + foo: bar +EOF + +cat <<'EOF' >$DEMO_HOME/valid/validator.yaml +apiVersion: someteam.example.com/v1 +kind: Validator +metadata: + name: notImportantHere +EOF + +cat <<'EOF' >$DEMO_HOME/valid/kustomization.yaml +resources: +- configmap.yaml + +validators: +- validator.yaml +EOF +``` + +The directory structure is as the following: + +``` +/tmp/tmp.69tTCuXuYc +├── kustomize +│   └── plugin +│   └── someteam.example.com +│   └── v1 +│   └── validator +│   └── Validator +└── valid + ├── configmap.yaml + ├── kustomization.yaml + └── validator.yaml +``` + +Define a helper function to run kustomize with the +correct environment and flags for plugins: + + +```bash +function kustomizeBd { + XDG_CONFIG_HOME=$DEMO_HOME \ + kustomize build \ + --enable_alpha_plugins \ + $DEMO_HOME/$1 +} +``` + +Build the valid variant + + +```bash +kustomizeBd valid +``` +The output contains a ConfigMap as + +```yaml +apiVersion: v1 +data: + foo: bar +kind: ConfigMap +metadata: + name: cm +``` + +### Validator Failure + +Now lets try a failed validator + +```bash +cat <<'EOF' > $PLUGINDIR/Validator +#!/bin/bash + +# Non-zero indicates a failed validation +>&2 echo "Validation failed" +exit 1 + +EOF +chmod +x $PLUGINDIR/Validator +``` + +Build the valid variant + +```bash +kustomizeBd valid +``` +The output contains the error information that is printed to stderr +by validator. + +``` +Validation failed +Error: failure in plugin configured via /tmp/kust-plugin-config-369137659; exit status 1: exit status 1 +``` + +### Input Modification + +Typically a validator shouldn't modify the content to be validated. If it does, Kustomize will complain about it. + +```bash +cat <<'EOF' > $PLUGINDIR/Validator +#!/bin/bash + +# Modify the input content + +sed 's/bar/baz/g' + +EOF +chmod +x $PLUGINDIR/Validator +``` + +Then build + +``` +kustomizeBd valid +``` + +The error output will indicate you where is modified by the validator + +``` +Error: validator shouldn't modify the resource map: kunstruct not equal: + -- {"apiVersion":"v1","data":{"foo":"bar"},"kind":"ConfigMap","metadata":{"name":"cm"}}{nsfx:false,beh:unspecified}, + -- {"apiVersion":"v1","data":{"foo":"baz"},"kind":"ConfigMap","metadata":{"name":"cm"}}{nsfx:false,beh:unspecified} + +-- +&resource.Resource{Kunstructured:(*kunstruct.UnstructAdapter)(0xc000118408), originalName:"cm", originalNs:"", options:(*types.GenArgs)(0xc00059e5e8), refBy:[]resid.ResId(nil), refVarNames:[]string(nil), namePrefixes:[]string{""}, nameSuffixes:[]string{""}} +------ +&resource.Resource{Kunstructured:(*kunstruct.UnstructAdapter)(0xc000118510), originalName:"cm", originalNs:"", options:(*types.GenArgs)(0xc00059e5e8), refBy:[]resid.ResId(nil), refVarNames:[]string(nil), namePrefixes:[]string{""}, nameSuffixes:[]string{""}} +``` + +There is an exception that the validator can add a `validated-by` label to the **top** level resources. + + +```bash +cat <<'EOF' > $PLUGINDIR/Validator +#!/usr/bin/bash + +sed 's/^ name: cm$/ name: cm\n labels:\n validated-by: whatever/' + +EOF +chmod +x $PLUGINDIR/Validator +``` + +Then build + + +``` +kustomizeBd valid +``` + +The output will be + +```yaml +apiVersion: v1 +data: + foo: bar +kind: ConfigMap +metadata: + labels: + validated-by: whatever + name: cm +``` + +## cleanup + + +``` +rm -rf $DEMO_HOME +``` \ No newline at end of file