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.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 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 ¶
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"
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() 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
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