framework

package
v0.1.9 Latest Latest
Warning

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

Go to latest
Published: May 7, 2020 License: Apache-2.0 Imports: 11 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

Examples

Example function implementation using framework.ResourceList with functionConfig

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 }

Example function implementation using framework.Command with flags

var value string
cmd := framework.Command(nil, func(items []*yaml.RNode) ([]*yaml.RNode, error) {
  for i := range items {
    // modify the items...
  }
  return items, nil
})
cmd.Flags().StringVar(&value, "value", "", "annotation value")
if err := cmd.Execute(); 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/fuction: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/fuction: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 items slice. When using framework.Command this is done through returning the new items slice. When using framework.ResourceList this is done through modifying ResourceList.Items in place.

Validator Functions

A function may validate resources by providing a Result. When using framework.Command this is done through returning a framework.Result as an error. WHen using framework.ResourceList this is done through setting 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 must be built into a container to be 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(functionConfig interface{}, function Function) cobra.Command

Command returns a cobra.Command to run a function.

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

If functionConfig is non-nil, the ResourceList.functionConfig will be unmarshalled into it.

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
	cmd := framework.Command(functionConfig, func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
		var newNodes []*yaml.RNode
		for i := range nodes {
			meta, err := nodes[i].GetMeta()
			if err != nil {
				return nil, 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, nodes[i])
		}

		// generate the resource
		n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
  name: %s
`, functionConfig.Spec.Name))
		if err != nil {
			return nil, err
		}
		return append(newNodes, n), 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
	cmd := framework.Command(functionConfig, func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
		var found bool
		for i := range nodes {
			meta, err := nodes[i].GetMeta()
			if err != nil {
				return nil, 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 := nodes[i].PipeE(yaml.SetAnnotation(k, v))
					if err != nil {
						return nil, err
					}
				}
				found = true
				break
			}
		}
		if found {
			return nodes, nil
		}

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

		return nodes, 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
	var value string
	cmd := framework.Command(nil, func(items []*yaml.RNode) ([]*yaml.RNode, error) {
		for i := range items {
			// set the annotation on each resource item
			if err := items[i].PipeE(yaml.SetAnnotation("value", value)); err != nil {
				return nil, err
			}
		}
		return items, 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() {
	cmd := framework.Command(nil, func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
		// validation results
		var validationResults []framework.Item

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

			// lookup replicas field
			r, err := nodes[i].Pipe(yaml.Lookup("spec", "replicas"))
			if err != nil {
				return nil, 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",
				},
			})
		}

		// framework will only consider results an error if it has at least 1 item
		return nodes, framework.Result{
			Name:  "replicas-validator",
			Items: validationResults,
		}
	})

	// 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"

Types

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(nodes []*yaml.RNode) ([]*yaml.RNode, 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 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

	// 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
	// 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 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"
)

Directories

Path Synopsis
Package main contains an example using the the framework.
Package main contains an example using the the framework.

Jump to

Keyboard shortcuts

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