framework

package
v0.10.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 29, 2020 License: Apache-2.0 Imports: 18 Imported by: 126

Documentation

Overview

Package framework contains a framework for writing functions in go. The function spec is defined at: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md

Functions are executables which generate, modify, delete or validate Kubernetes resources. They are often used used to implement abstractions ("kind: JavaSpringBoot") and cross-cutting logic ("kind: SidecarInjector").

Functions may be run as standalone executables or invoked as part of an orchestrated pipeline (e.g. kustomize).

Example standalone usage

Function template input:

# config.yaml -- this is the input to the template
apiVersion: example.com/v1alpha1
kind: Example
Key: a
Value: b

Additional function inputs:

# patch.yaml -- this will be applied as a patch
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    patch-key: patch-value

Manually run the function:

# build the function
$ go build example-fn/

# run the function using the
$ ./example-fn config.yaml patch.yaml

Go implementation

 // example-fn/main.go
 func main() {

   // Define the template used to generate resources
   tc := framework.TemplateCommand{
     Merge: true, // apply inputs as patches to the template output
     API: &struct {
       Key   string `json:"key" yaml:"key"`
       Value string `json:"value" yaml:"value"`
     }{},
     Template: template.Must(template.New("example").Parse(`
 apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: foo
   namespace: default
   annotations:
     {{ .Key }}: {{ .Value }}
 `))}

   // Run the command
   if err := tc.GetCommand().Execute(); err != nil {
     fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
     os.Exit(1)
   }
}

More Examples

Example function implementation using framework.Command with flag input

var value string
resourceList := &framework.ResourceList{}
cmd := framework.Command(resourceList, func() error {
  for i := range resourceList.Items {
    // modify the items...
  }
  return nil
})
cmd.Flags().StringVar(&value, "value", "", "annotation value")
if err := cmd.Execute(); err != nil { return err }

Example function implementation using framework.ResourceList with a struct input

type Spec struct {
  Value string `yaml:"value,omitempty"`
}
type Example struct {
  Spec Spec `yaml:"spec,omitempty"`
}
functionConfig := &Example{}

rl := framework.ResourceList{FunctionConfig: functionConfig}
if err := rl.Read(); err != nil { return err	}

for i := range rl.Items {
  // modify the items...
}
if err := rl.Write(); err != nil { return err }

Architecture

Functions modify a slice of resources (ResourceList.Items) which are read as input and written as output. The function itself may be configured through a functionConfig (ResourceList.FunctionConfig).

Example Function Input:

kind: ResourceList
items:
- kind: Deployment
  ...
- kind: Service
  ....
functionConfig:
  kind: Example
  spec:
    value: foo

The functionConfig may be specified declaratively and run with

config run DIR/

Declarative function declaration:

kind: Example
metadata:
  annotations:
    # run the function by creating this container and providing this
    # Example as the functionConfig
    config.kubernetes.io/function: |
      image: image/containing/function:impl
spec:
  value: foo

The framework takes care of serializing and deserializing the ResourceList.

Generated ResourceList.functionConfig -- ConfigMaps

Functions may also be specified imperatively and run using:

config run DIR/ --image image/containing/function:impl -- value=foo

When run imperatively, a ConfigMap is generated for the functionConfig, and the command arguments are set as ConfigMap data entries.

kind: ConfigMap
data:
  value: foo

To write a function that can be run imperatively on the commandline, have it take a ConfigMap as its functionConfig.

Mutator and Generator Functions

Functions may add, delete or modify resources by modifying the ResourceList.Items slice.

Validator Functions

A function may emit validation results by setting the ResourceList.Result

Configuring Functions

Functions may be configured through a functionConfig (i.e. a client side custom resource), or through flags (which the framework parses from a ConfigMap provided as input).

When using framework.Command, any flags registered on the cobra.Command will be parsed from the functionConfig input if they are defined as functionConfig.data entries.

When using framework.ResourceList, any flags set on the ResourceList.Flags will be parsed from the functionConfig input if they are defined as functionConfig.data entries.

Functions may also access environment variables set by the caller.

Building a container image for the function

The go program may be built into a container and run as a function. The framework can be used to generate a Dockerfile to build the function container.

# create the ./Dockerfile for the container
$ go run ./main.go gen ./

# build the function's container
$ docker build . -t gcr.io/my-project/my-image:my-version

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddGenerateDockerfile

func AddGenerateDockerfile(cmd *cobra.Command)

AddGenerateDockerfile adds a "gen" subcommand to create a Dockerfile for building the function as a container.

func Command

func Command(resourceList *ResourceList, function Function) *cobra.Command

Command returns a cobra.Command to run a function.

The cobra.Command will use the provided ResourceList to Read() the input, run the provided function, and then Write() the output.

The returned cobra.Command will have a "gen" subcommand which can be used to generate a Dockerfile to build the function into a container image

go run main.go gen DIR/
Example (GenerateReplace)

ExampleCommand_generateReplace generates a resource from a functionConfig. If the resource already exist s, it replaces the resource with a new copy.

package main

import (
	"bytes"
	"fmt"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

const service = "Service"

func main() {
	// function API definition which will be parsed from the ResourceList.functionConfig
	// read from stdin
	type Spec struct {
		Name string `yaml:"name,omitempty"`
	}
	type ExampleServiceGenerator struct {
		Spec Spec `yaml:"spec,omitempty"`
	}
	functionConfig := &ExampleServiceGenerator{}

	// function implementation -- generate a Service resource
	resourceList := &framework.ResourceList{FunctionConfig: functionConfig}
	cmd := framework.Command(resourceList, func() error {
		var newNodes []*yaml.RNode
		for i := range resourceList.Items {
			meta, err := resourceList.Items[i].GetMeta()
			if err != nil {
				return err
			}

			// something we already generated, remove it from the list so we regenerate it
			if meta.Name == functionConfig.Spec.Name &&
				meta.Kind == service &&
				meta.APIVersion == "v1" {
				continue
			}
			newNodes = append(newNodes, resourceList.Items[i])
		}

		// generate the resource
		n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
  name: %s
`, functionConfig.Spec.Name))
		if err != nil {
			return err
		}
		newNodes = append(newNodes, n)
		resourceList.Items = newNodes
		return nil
	})

	// for testing purposes only -- normally read from stdin when Executing
	cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
# functionConfig is parsed into flags by framework.Command
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: ExampleServiceGenerator
  spec:
    name: bar
`))

	// run the command
	if err := cmd.Execute(); err != nil {
		panic(err)
	}

}
Output:

apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
- apiVersion: v1
  kind: Service
  metadata:
    name: bar
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: ExampleServiceGenerator
  spec:
    name: bar
Example (GenerateUpdate)

ExampleCommand_generateUpdate generates a resource, updating the previously generated copy rather than replacing it.

Note: This will keep manual edits to the previously generated copy.

package main

import (
	"bytes"
	"fmt"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

const service = "Service"

func main() {
	// function API definition which will be parsed from the ResourceList.functionConfig
	// read from stdin
	type Spec struct {
		Name        string            `yaml:"name,omitempty"`
		Annotations map[string]string `yaml:"annotations,omitempty"`
	}
	type ExampleServiceGenerator struct {
		Spec Spec `yaml:"spec,omitempty"`
	}
	functionConfig := &ExampleServiceGenerator{}

	// function implementation -- generate or update a Service resource
	resourceList := &framework.ResourceList{FunctionConfig: functionConfig}
	cmd := framework.Command(resourceList, func() error {
		var found bool
		for i := range resourceList.Items {
			meta, err := resourceList.Items[i].GetMeta()
			if err != nil {
				return err
			}

			// something we already generated, reconcile it to make sure it matches what
			// is specified by the functionConfig
			if meta.Name == functionConfig.Spec.Name &&
				meta.Kind == service &&
				meta.APIVersion == "v1" {
				// set some values
				for k, v := range functionConfig.Spec.Annotations {
					err := resourceList.Items[i].PipeE(yaml.SetAnnotation(k, v))
					if err != nil {
						return err
					}
				}
				found = true
				break
			}
		}
		if found {
			return nil
		}

		// generate the resource if not found
		n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
  name: %s
`, functionConfig.Spec.Name))
		if err != nil {
			return err
		}
		for k, v := range functionConfig.Spec.Annotations {
			err := n.PipeE(yaml.SetAnnotation(k, v))
			if err != nil {
				return err
			}
		}
		resourceList.Items = append(resourceList.Items, n)

		return nil
	})

	// for testing purposes only -- normally read from stdin when Executing
	cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
- apiVersion: v1
  kind: Service
  metadata:
    name: bar
# functionConfig is parsed into flags by framework.Command
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: ExampleServiceGenerator
  spec:
    name: bar
    annotations:
      a: b
`))

	// run the command
	if err := cmd.Execute(); err != nil {
		panic(err)
	}

}
Output:

apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
- apiVersion: v1
  kind: Service
  metadata:
    name: bar
    annotations:
      a: 'b'
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: ExampleServiceGenerator
  spec:
    name: bar
    annotations:
      a: b
Example (Modify)

ExampleCommand_modify implements a function that sets an annotation on each resource. The annotation value is configured via a flag value parsed from ResourceList.functionConfig.data

package main

import (
	"bytes"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

func main() {
	// configure the annotation value using a flag parsed from
	// ResourceList.functionConfig.data.value
	resourceList := framework.ResourceList{}
	var value string
	cmd := framework.Command(&resourceList, func() error {
		for i := range resourceList.Items {
			// set the annotation on each resource item
			err := resourceList.Items[i].PipeE(yaml.SetAnnotation("value", value))
			if err != nil {
				return err
			}
		}
		return nil
	})
	cmd.Flags().StringVar(&value, "value", "", "annotation value")

	// for testing purposes only -- normally read from stdin when Executing
	cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
- apiVersion: v1
  kind: Service
  metadata:
    name: foo
# functionConfig is parsed into flags by framework.Command
functionConfig:
  apiVersion: v1
  kind: ConfigMap
  data:
    value: baz
`))
	// run the command
	if err := cmd.Execute(); err != nil {
		panic(err)
	}

}
Output:

apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
    annotations:
      value: 'baz'
- apiVersion: v1
  kind: Service
  metadata:
    name: foo
    annotations:
      value: 'baz'
functionConfig:
  apiVersion: v1
  kind: ConfigMap
  data:
    value: baz
Example (Validate)

ExampleCommand_validate validates that all Deployment resources have the replicas field set. If any Deployments do not contain spec.replicas, then the function will return results which will be set on ResourceList.results

package main

import (
	"bytes"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

func main() {
	resourceList := &framework.ResourceList{}
	cmd := framework.Command(resourceList, func() error {
		// validation results
		var validationResults []framework.Item

		// validate that each Deployment resource has spec.replicas set
		for i := range resourceList.Items {
			// only check Deployment resources
			meta, err := resourceList.Items[i].GetMeta()
			if err != nil {
				return err
			}
			if meta.Kind != "Deployment" {
				continue
			}

			// lookup replicas field
			r, err := resourceList.Items[i].Pipe(yaml.Lookup("spec", "replicas"))
			if err != nil {
				return err
			}

			// check replicas not specified
			if r != nil {
				continue
			}
			validationResults = append(validationResults, framework.Item{
				Severity:    framework.Error,
				Message:     "missing replicas",
				ResourceRef: meta,
				Field: framework.Field{
					Path:           "spec.field",
					SuggestedValue: "1",
				},
			})
		}

		if len(validationResults) > 0 {
			resourceList.Result = &framework.Result{
				Name:  "replicas-validator",
				Items: validationResults,
			}
		}

		return resourceList.Result
	})

	// for testing purposes only -- normally read from stdin when Executing
	cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
`))

	// run the command
	if err := cmd.Execute(); err != nil {
		// normally exit 1 here
	}

}
Output:

apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
results:
  name: replicas-validator
  items:
  - message: missing replicas
    severity: error
    resourceRef:
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: foo
    field:
      path: spec.field
      suggestedValue: "1"

func Filters added in v0.10.2

func Filters(fltrs ...kio.Filter) func(*ResourceList) []kio.Filter

Filters returns a function which returns the provided Filters

func PatchContainers added in v0.10.1

func PatchContainers(resources []*yaml.RNode, patch *yaml.RNode, containers ...string) error

PatchContainers applies patch to each container in each resource.

func PatchContainersWithString added in v0.10.1

func PatchContainersWithString(resources []*yaml.RNode, t string, input interface{}, containers ...string) error

PatchContainersWithString executes t as a template and patches each container in each resource with the result.

Example

ExamplePatchContainersWithString patches all containers.

package main

import (
	"fmt"
	"log"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/kio"
)

func main() {
	resources, err := kio.ParseAll(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
      - name: bar
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  selector:
    foo: bar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
      - name: baz
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: bar
spec:
  selector:
    foo: bar
`)
	if err != nil {
		log.Fatal(err)
	}

	input := struct{ Value string }{Value: "new-value"}
	err = framework.PatchContainersWithString(resources, `
env:
  KEY: {{ .Value }}
`, input)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(kio.StringAll(resources))

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
        env:
          KEY: new-value
      - name: bar
        image: b
        env:
          KEY: new-value
---
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  selector:
    foo: bar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
        env:
          KEY: new-value
      - name: baz
        image: b
        env:
          KEY: new-value
---
apiVersion: v1
kind: Service
metadata:
  name: bar
spec:
  selector:
    foo: bar
 <nil>
Example (Names)

PatchTemplateContainersWithString patches containers matching a specific name.

package main

import (
	"fmt"
	"log"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/kio"
)

func main() {
	resources, err := kio.ParseAll(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
      - name: bar
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  selector:
    foo: bar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
      - name: baz
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: bar
spec:
  selector:
    foo: bar
`)
	if err != nil {
		log.Fatal(err)
	}

	input := struct{ Value string }{Value: "new-value"}
	err = framework.PatchContainersWithString(resources, `
env:
  KEY: {{ .Value }}
`, input, "foo")
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(kio.StringAll(resources))

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
        env:
          KEY: new-value
      - name: bar
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: foo
spec:
  selector:
    foo: bar
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
spec:
  template:
    spec:
      containers:
      - name: foo
        image: a
        env:
          KEY: new-value
      - name: baz
        image: b
---
apiVersion: v1
kind: Service
metadata:
  name: bar
spec:
  selector:
    foo: bar
 <nil>

func PatchContainersWithTemplate added in v0.10.1

func PatchContainersWithTemplate(resources []*yaml.RNode, t *template.Template, input interface{}, containers ...string) error

PatchContainersWithTemplate executes t and patches each container in each resource with the result.

Types

type Applier added in v0.10.0

type Applier interface {
	Apply(rl *ResourceList) error
}

Applier applies some modification to a ResourceList

type CPT added in v0.10.2

type CPT struct {
	Selector func() *Selector
	Dir      pkger.Dir
	Names    []string
}

CPT applies a directory of container patches using the Selector

type ContainerPatchTemplate added in v0.10.1

type ContainerPatchTemplate struct {
	PatchTemplate

	ContainerNames []string
}

ContainerPatchTemplate defines a patch to be applied to containers

type ContainerPatchTemplateFn added in v0.10.2

type ContainerPatchTemplateFn func(*ResourceList) ([]ContainerPatchTemplate, error)

ContainerPatchTemplateFn returns a slice of ContainerPatchTemplate

func ContainerPatchTemplatesFromDir added in v0.10.2

func ContainerPatchTemplatesFromDir(templates ...CPT) ContainerPatchTemplateFn

ContainerPatchTemplatesFromDir applies a directory of templates as container patches.

type Defaulter added in v0.10.2

type Defaulter interface {
	Default() error
}

Defaulter is implemented by APIs to have Default invoked

type Field

type Field struct {
	// Path is the field path
	Path string `yaml:"path,omitempty"`

	// CurrentValue is the current field value
	CurrentValue string `yaml:"currentValue,omitempty"`

	// SuggestedValue is the suggested field value
	SuggestedValue string `yaml:"suggestedValue,omitempty"`
}

Field references a field in a resource

type File

type File struct {
	// Path is relative path to the file containing the resource
	Path string `yaml:"path,omitempty"`

	// Index is the index into the file containing the resource
	// (i.e. if there are multiple resources in a single file)
	Index int `yaml:"index,omitempty"`
}

File references a file containing a resource

type Function

type Function func() error

Function defines a function which mutates or validates a collection of configuration To create a structured validation result, return a Result as the error.

type Item

type Item struct {
	// Message is a human readable message
	Message string `yaml:"message,omitempty"`

	// Severity is the severity of the
	Severity Severity `yaml:"severity,omitempty"`

	// ResourceRef is a reference to a resource
	ResourceRef yaml.ResourceMeta `yaml:"resourceRef,omitempty"`

	Field Field `yaml:"field,omitempty"`

	File File `yaml:"file,omitempty"`
}

Item defines a validation result

type PT added in v0.10.2

type PT struct {
	Selector func() *Selector
	Dir      pkger.Dir
}

PT applies a directory of patches using the Selector

type PatchTemplate added in v0.10.0

type PatchTemplate struct {
	// Template is a template to render into one or more patches.
	Template *template.Template

	// Selector targets the rendered patch to specific resources.
	Selector *Selector
}

PatchTemplate applies a patch to a collection of Resources

func (PatchTemplate) Apply added in v0.10.0

func (p PatchTemplate) Apply(rl *ResourceList) error

Apply applies the patch to all matching resources in the list. The rl.FunctionConfig is provided to the template as input.

type PatchTemplatesFn added in v0.10.2

type PatchTemplatesFn func(*ResourceList) ([]PatchTemplate, error)

PatchTemplatesFn returns a slice of PatchTemplate

func PatchTemplatesFromDir added in v0.10.2

func PatchTemplatesFromDir(templates ...PT) PatchTemplatesFn

PatchTemplatesFromDir applies a directory of templates as patches.

type ResourceList

type ResourceList struct {
	// FunctionConfig is the ResourceList.functionConfig input value.  If FunctionConfig
	// is set to a value such as a struct or map[string]interface{} before ResourceList.Read()
	// is called, then the functionConfig will be parsed into that value.
	// If it is nil, the functionConfig will be set to a map[string]interface{}
	// before it is parsed.
	//
	// e.g. given the function input:
	//
	//    kind: ResourceList
	//    functionConfig:
	//      kind: Example
	//      spec:
	//        foo: var
	//
	// FunctionConfig will contain the Example unmarshalled into its value.
	FunctionConfig interface{}

	// Items is the ResourceList.items input and output value.  Items will be set by
	// ResourceList.Read() and written by ResourceList.Write().
	//
	// e.g. given the function input:
	//
	//    kind: ResourceList
	//    items:
	//    - kind: Deployment
	//      ...
	//    - kind: Service
	//      ...
	//
	// Items will be a slice containing the Deployment and Service resources
	Items []*yaml.RNode

	// Result is ResourceList.result output value.  Result will be written by
	// ResourceList.Write()
	Result *Result

	// DisableStandalone if set will not support standalone mode
	DisableStandalone bool

	// Args are the command args used for standalone mode
	Args []string

	// Flags are an optional set of flags to parse the ResourceList.functionConfig.data.
	// If non-nil, ResourceList.Read() will set the flag value for each flag name matching
	// a ResourceList.functionConfig.data map entry.
	//
	// e.g. given the function input:
	//
	//    kind: ResourceList
	//    functionConfig:
	//      data:
	//        foo: bar
	//        a: b
	//
	// The flags --a=b and --foo=bar will be set in Flags.
	Flags *pflag.FlagSet

	// Reader is used to read the function input (ResourceList).
	// Defaults to os.Stdin.
	Reader io.Reader

	// Writer is used to write the function output (ResourceList)
	// Defaults to os.Stdout.
	Writer io.Writer

	// NoPrintError if set will prevent the error from being printed
	NoPrintError bool

	Command *cobra.Command
	// contains filtered or unexported fields
}

ResourceList reads the function input and writes the function output.

Adheres to the spec: https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md

Example (GenerateReplace)

ExampleResourceList_generateReplace generates a resource from a functionConfig. If the resource already exist s, it replaces the resource with a new copy.

package main

import (
	"bytes"
	"fmt"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

const service = "Service"

func main() {
	input := bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
# functionConfig is parsed into flags by framework.Command
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: ExampleServiceGenerator
  spec:
    name: bar
`)

	// function API definition which will be parsed from the ResourceList.functionConfig
	// read from stdin
	type Spec struct {
		Name string `yaml:"name,omitempty"`
	}
	type ExampleServiceGenerator struct {
		Spec Spec `yaml:"spec,omitempty"`
	}
	functionConfig := &ExampleServiceGenerator{}

	rl := framework.ResourceList{
		FunctionConfig: functionConfig,
		Reader:         input, // for testing only
	}
	if err := rl.Read(); err != nil {
		panic(err)
	}

	// remove the last generated resource
	var newNodes []*yaml.RNode
	for i := range rl.Items {
		meta, err := rl.Items[i].GetMeta()
		if err != nil {
			panic(err)
		}
		// something we already generated, remove it from the list so we regenerate it
		if meta.Name == functionConfig.Spec.Name &&
			meta.Kind == service &&
			meta.APIVersion == "v1" {
			continue
		}
		newNodes = append(newNodes, rl.Items[i])
	}
	rl.Items = newNodes

	// generate the resource again
	n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
  name: %s
`, functionConfig.Spec.Name))
	if err != nil {
		panic(err)
	}
	rl.Items = append(rl.Items, n)

	if err := rl.Write(); err != nil {
		panic(err)
	}

}
Output:

apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
- apiVersion: v1
  kind: Service
  metadata:
    name: bar
functionConfig:
  apiVersion: example.com/v1alpha1
  kind: ExampleServiceGenerator
  spec:
    name: bar
Example (Modify)

ExampleResourceList_modify implements a function that sets an annotation on each resource. The annotation value is configured via a flag value parsed from ResourceList.functionConfig.data

package main

import (
	"bytes"

	"github.com/spf13/pflag"
	"sigs.k8s.io/kustomize/kyaml/fn/framework"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

func main() {
	// for testing purposes only -- normally read from stdin when Executing
	input := bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
# items are provided as nodes
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
- apiVersion: v1
  kind: Service
  metadata:
    name: foo
# functionConfig is parsed into flags by framework.Command
functionConfig:
  apiVersion: v1
  kind: ConfigMap
  data:
    value: baz
`)

	// configure the annotation value using a flag parsed from
	// ResourceList.functionConfig.data.value
	fs := pflag.NewFlagSet("tests", pflag.ContinueOnError)
	value := fs.String("value", "", "annotation value")
	rl := framework.ResourceList{
		Flags:  fs,
		Reader: input, // for testing only
	}
	if err := rl.Read(); err != nil {
		panic(err)
	}
	for i := range rl.Items {
		// set the annotation on each resource item
		if err := rl.Items[i].PipeE(yaml.SetAnnotation("value", *value)); err != nil {
			panic(err)
		}
	}
	if err := rl.Write(); err != nil {
		panic(err)
	}

}
Output:

apiVersion: config.kubernetes.io/v1alpha1
kind: ResourceList
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
    annotations:
      value: 'baz'
- apiVersion: v1
  kind: Service
  metadata:
    name: foo
    annotations:
      value: 'baz'
functionConfig:
  apiVersion: v1
  kind: ConfigMap
  data:
    value: baz

func (*ResourceList) Read

func (r *ResourceList) Read() error

Read reads the ResourceList

func (*ResourceList) Write

func (r *ResourceList) Write() error

Write writes the ResourceList

type Result

type Result struct {
	// Name is the name of the function creating the result
	Name string `yaml:"name,omitempty"`

	// Items are the individual results
	Items []Item `yaml:"items,omitempty"`
}

Result defines a function result which will be set on the emitted ResourceList

func (Result) Error

func (e Result) Error() string

Error implement error

func (Result) ExitCode

func (e Result) ExitCode() int

ExitCode provides the exit code based on the result

type Selector added in v0.10.0

type Selector struct {
	// Names is a list of metadata.names to match.  If empty match all names.
	// e.g. Names: ["foo", "bar"] matches if `metadata.name` is either "foo" or "bar".
	Names []string `json:"names" yaml:"names"`

	// Namespaces is a list of metadata.namespaces to match.  If empty match all namespaces.
	// e.g. Namespaces: ["foo", "bar"] matches if `metadata.namespace` is either "foo" or "bar".
	Namespaces []string `json:"namespaces" yaml:"namespaces"`

	// Kinds is a list of kinds to match.  If empty match all kinds.
	// e.g. Kinds: ["foo", "bar"] matches if `kind` is either "foo" or "bar".
	Kinds []string `json:"kinds" yaml:"kinds"`

	// APIVersions is a list of apiVersions to match.  If empty apply match all apiVersions.
	// e.g. APIVersions: ["foo/v1", "bar/v1"] matches if `apiVersion` is either "foo/v1" or "bar/v1".
	APIVersions []string `json:"apiVersions" yaml:"apiVersions"`

	// Labels is a collection of labels to match.  All labels must match exactly.
	// e.g. Labels: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" labels match.
	Labels map[string]string `json:"labels" yaml:"labels"`

	// Annotations is a collection of annotations to match.  All annotations must match exactly.
	// e.g. Annotations: {"foo": "bar", "baz": "buz"] matches if BOTH "foo" and "baz" annotations match.
	Annotations map[string]string `json:"annotations" yaml:"annotations"`

	// Filter is an arbitrary filter function to match a resource.
	// Selector matches if the function returns true.
	Filter func(*yaml.RNode) bool

	// TemplatizeValues if set to true will parse the selector values as templates
	// and execute them with the functionConfig
	TemplatizeValues bool
	// contains filtered or unexported fields
}

Selector matches resources. A resource matches if and only if ALL of the Selector fields match the resource. An empty Selector matches all resources.

Example (TemplatizeAnnotations)
package main

import (
	"bytes"
	"os"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

func main() {
	type api struct {
		Value string `yaml:"vaue"`
	}
	rl := &framework.ResourceList{
		FunctionConfig: &api{Value: "bar"},
		Reader: bytes.NewBufferString(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    key: foo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    key: bar
`),
		Writer: os.Stdout,
	}
	if err := rl.Read(); err != nil {
		panic(err)
	}

	var err error
	s := &framework.Selector{
		TemplatizeValues: true,
		Annotations:      map[string]string{"key": "{{ .Value }}"},
	}
	rl.Items, err = s.GetMatches(rl)
	if err != nil {
		panic(err)
	}

	if err := rl.Write(); err != nil {
		panic(err)
	}

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    key: bar
    config.kubernetes.io/index: '1'
Example (TemplatizeKinds)
package main

import (
	"bytes"
	"os"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

func main() {
	type api struct {
		KindName string `yaml:"kindName"`
	}
	rl := &framework.ResourceList{
		FunctionConfig: &api{KindName: "Deployment"},
		Reader: bytes.NewBufferString(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: bar
  namespace: default
`),
		Writer: os.Stdout,
	}
	if err := rl.Read(); err != nil {
		panic(err)
	}

	var err error
	s := &framework.Selector{
		TemplatizeValues: true,
		Kinds:            []string{"{{ .KindName }}"},
	}
	rl.Items, err = s.GetMatches(rl)
	if err != nil {
		panic(err)
	}

	if err := rl.Write(); err != nil {
		panic(err)
	}

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    config.kubernetes.io/index: '0'

func (*Selector) GetMatches added in v0.10.0

func (s *Selector) GetMatches(rl *ResourceList) ([]*yaml.RNode, error)

GetMatches returns them matching resources from rl

type Severity

type Severity string

Severity indicates the severity of the result

const (
	// Error indicates the result is an error.  Will cause the function to exit non-0.
	Error Severity = "error"
	// Warning indicates the result is a warning
	Warning Severity = "warning"
	// Info indicates the result is an informative message
	Info Severity = "info"
)

type TemplateCommand added in v0.10.0

type TemplateCommand struct {
	// API is the function API provide to the template as input
	API interface{}

	// Template is a go template to render and is appended to Templates.
	Template *template.Template

	// Templates is a list of templates to render.
	Templates []*template.Template

	// TemplatesFn returns a list of templates
	TemplatesFn func(*ResourceList) ([]*template.Template, error)

	// PatchTemplates is a list of templates to render into Patches and apply.
	PatchTemplates []PatchTemplate

	// PatchTemplateFn returns a list of templates to render into Patches and apply.
	// PatchTemplateFn is called after the ResourceList has been parsed.
	PatchTemplatesFn func(*ResourceList) ([]PatchTemplate, error)

	// PatchContainerTemplates applies patches to matching container fields
	PatchContainerTemplates []ContainerPatchTemplate

	// PatchContainerTemplates returns a list of PatchContainerTemplates
	PatchContainerTemplatesFn func(*ResourceList) ([]ContainerPatchTemplate, error)

	// TemplateFiles list of templates to read from disk which are appended
	// to Templates.
	TemplatesFiles []string

	// MergeResources if set to true will apply input resources
	// as patches to the templates
	MergeResources bool

	// PreProcess is run on the ResourceList before the template is invoked
	PreProcess func(*ResourceList) error

	PreProcessFilters []kio.Filter

	// PostProcess is run on the ResourceList after the template is invoked
	PostProcess func(*ResourceList) error

	PostProcessFilters []kio.Filter
}

TemplateCommand provides a cobra command to invoke a template

Example

ExampleTemplateCommand provides an example for using the TemplateCommand

package main

import (
	"fmt"
	"path/filepath"
	"text/template"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

func main() {
	// create the template
	cmd := framework.TemplateCommand{
		// Template input
		API: &struct {
			Key   string `json:"key" yaml:"key"`
			Value string `json:"value" yaml:"value"`
		}{},
		// Template
		Template: template.Must(template.New("example").Parse(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
`)),
	}.GetCommand()

	cmd.SetArgs([]string{filepath.Join("testdata", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    a: b
Example (Files)

ExampleTemplateCommand_files provides an example for using the TemplateCommand

package main

import (
	"fmt"
	"path/filepath"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

func main() {
	// create the template
	cmd := framework.TemplateCommand{
		// Template input
		API: &struct {
			Key   string `json:"key" yaml:"key"`
			Value string `json:"value" yaml:"value"`
		}{},
		// Template
		TemplatesFiles: []string{filepath.Join("testdata", "templatefiles", "deployment.template")},
	}.GetCommand()

	cmd.SetArgs([]string{filepath.Join("testdata", "templatefiles", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    a: b
Example (Patch)

ExampleTemplateCommand_patch provides an example for using the TemplateCommand to create a function which patches resources.

package main

import (
	"fmt"
	"path/filepath"
	"text/template"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

func main() {
	// patch the foo resource only
	s := framework.Selector{Names: []string{"foo"}}

	cmd := framework.TemplateCommand{
		API: &struct {
			Key   string `json:"key" yaml:"key"`
			Value string `json:"value" yaml:"value"`
		}{},
		Template: template.Must(template.New("example").Parse(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
`)),
		// PatchTemplates are applied to BOTH ResourceList input resources AND templated resources
		PatchTemplates: []framework.PatchTemplate{{
			Selector: &s,
			Template: template.Must(template.New("test").Parse(`
metadata:
  annotations:
    patched: 'true'
`)),
		}},
	}.GetCommand()

	cmd.SetArgs([]string{filepath.Join("testdata", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    a: b
    patched: 'true'
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    a: b
Example (Postprocess)

ExampleTemplateCommand_postprocess provides an example for using the TemplateCommand with PostProcess to modify the results.

package main

import (
	"fmt"
	"path/filepath"
	"text/template"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

func main() {
	config := &struct {
		Key   string `json:"key" yaml:"key"`
		Value string `json:"value" yaml:"value"`
	}{}

	// create the template
	cmd := framework.TemplateCommand{
		// Template input
		API: config,
		// Template
		Template: template.Must(template.New("example").Parse(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
`)),
		PostProcess: func(rl *framework.ResourceList) error {
			// trim the first resources
			rl.Items = rl.Items[1:]
			return nil
		},
	}.GetCommand()

	cmd.SetArgs([]string{filepath.Join("testdata", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    a: b
Example (Preprocess)

ExampleTemplateCommand_preprocess provides an example for using the TemplateCommand with PreProcess to configure the template based on the input resources observed.

package main

import (
	"fmt"
	"path/filepath"
	"text/template"

	"sigs.k8s.io/kustomize/kyaml/fn/framework"
)

func main() {
	config := &struct {
		Key   string `json:"key" yaml:"key"`
		Value string `json:"value" yaml:"value"`
		Short bool
	}{}

	// create the template
	cmd := framework.TemplateCommand{
		// Template input
		API: config,
		PreProcess: func(rl *framework.ResourceList) error {
			config.Short = len(rl.Items) < 3
			return nil
		},
		// Template
		Template: template.Must(template.New("example").Parse(`
apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
{{- if .Short }}
    short: 'true'
{{- end }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    {{ .Key }}: {{ .Value }}
{{- if .Short }}
    short: 'true'
{{- end }}
`)),
	}.GetCommand()

	cmd.SetArgs([]string{filepath.Join("testdata", "template", "config.yaml")})
	if err := cmd.Execute(); err != nil {
		fmt.Fprintf(cmd.ErrOrStderr(), "%v\n", err)
	}

}
Output:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: foo
  namespace: default
  annotations:
    a: b
    short: 'true'
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: bar
  namespace: default
  annotations:
    a: b
    short: 'true'

func (TemplateCommand) GetCommand added in v0.10.0

func (tc TemplateCommand) GetCommand() *cobra.Command

GetCommand returns a new cobra command

type TemplatesFn added in v0.10.2

type TemplatesFn func(*ResourceList) ([]*template.Template, error)

func TemplatesFromDir added in v0.10.2

func TemplatesFromDir(dirs ...pkger.Dir) TemplatesFn

TemplatesFromDir applies a directory of templates as generated resources.

Directories

Path Synopsis
Package main contains an example using the the framework.
Package main contains an example using the the framework.
Package frameworktestutil contains utilities for testing functions written using the framework.
Package frameworktestutil contains utilities for testing functions written using the framework.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL