command

package
v0.14.2 Latest Latest
Warning

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

Go to latest
Published: May 2, 2023 License: Apache-2.0 Imports: 10 Imported by: 74

Documentation

Overview

Package command contains a builder for creating cobra.Commands based on configuration functions written using the kyaml function framework. The commands this package generates can be used as standalone executables or as part of a configuration management pipeline that complies with the Configuration Functions Specification (e.g. Kustomize generators or transformers): https://github.com/kubernetes-sigs/kustomize/blob/master/cmd/config/docs/api-conventions/functions-spec.md

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
$ ./example-fn config.yaml patch.yaml

Go implementation

  // example-fn/main.go
	func main() {
		// Define the template used to generate resources
		p := framework.TemplateProcessor{
			MergeResources: true, // apply inputs as patches to the template output
			TemplateData: new(struct {
				Key   string `json:"key" yaml:"key"`
				Value string `json:"value" yaml:"value"`
			}),
			ResourceTemplates: []framework.ResourceTemplate{{
				Templates: framework.StringTemplates(`
	  apiVersion: apps/v1
	  kind: Deployment
	  metadata:
		name: foo
		namespace: default
		annotations:
		  {{ .Key }}: {{ .Value }}
	  `)}},
		}

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

Example function implementation using command.Build with flag input

func main() {
	var value string
	fn := func(rl *framework.ResourceList) error {
		for i := range rl.Items {
			// set the annotation on each resource item
			if err := rl.Items[i].PipeE(yaml.SetAnnotation("value", value)); err != nil {
				return err
			}
		}
		return nil
	}
	cmd := command.Build(framework.ResourceListProcessorFunc(fn), command.StandaloneEnabled, false)
	cmd.Flags().StringVar(&value, "value", "", "annotation value")

	if err := cmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

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 into a container image. The gen command takes one argument: the directory where the Dockerfile will be created.

go run main.go gen DIR/

func Build

func Build(p framework.ResourceListProcessor, mode CLIMode, noPrintError bool) *cobra.Command

Build returns a cobra.Command to run a function.

The cobra.Command reads the input from STDIN, invokes the provided processor, and then writes the output to STDOUT.

The cobra.Command has a boolean `--stack` flag to print stack traces on failure.

By default, invoking the returned cobra.Command with arguments triggers "standalone" mode. In this mode: - The first argument must be the name of a file containing the FunctionConfig. - The remaining arguments must be filenames containing input resources for ResourceList.Items. - The argument "-", if present, will cause resources to be read from STDIN as well. The output will be a raw stream of resources (not wrapped in a List type). Example usage: `cat input1.yaml | go run main.go config.yaml input2.yaml input3.yaml -`

If mode is `StandaloneDisabled`, all arguments are ignored, and STDIN must contain a Kubernetes List type. To pass a function config in this mode, use a ResourceList as the input. The output will be of the same type as the input (e.g. ResourceList). Example usage: `cat resource_list.yaml | go run main.go`

By default, any error returned by the ResourceListProcessor will be printed to STDERR. Set noPrintError to true to suppress this.

Example (GenerateReplace)

ExampleBuild_generateReplace generates a resource from a FunctionConfig. If the resource already exists, 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/fn/framework/command"
	"sigs.k8s.io/kustomize/kyaml/kio"
	"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
	p := &framework.SimpleProcessor{
		Config: functionConfig,
		Filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
			var newNodes []*yaml.RNode
			for i := range items {
				meta, err := items[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, 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 nil, err
			}
			newNodes = append(newNodes, n)
			return newNodes, nil
		}),
	}
	cmd := command.Build(p, command.StandaloneDisabled, false)

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

ExampleBuild_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/fn/framework/command"
	"sigs.k8s.io/kustomize/kyaml/kio"
	"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
	fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
		var found bool
		for i := range items {
			meta, err := items[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 := items[i].PipeE(yaml.SetAnnotation(k, v))
					if err != nil {
						return nil, err
					}
				}
				found = true
				break
			}
		}
		if found {
			return items, 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 nil, err
		}
		for k, v := range functionConfig.Spec.Annotations {
			err := n.PipeE(yaml.SetAnnotation(k, v))
			if err != nil {
				return nil, err
			}
		}
		items = append(items, n)
		return items, nil
	}

	p := &framework.SimpleProcessor{Config: functionConfig, Filter: kio.FilterFunc(fn)}
	cmd := command.Build(p, command.StandaloneDisabled, false)

	// for testing purposes only -- normally read from stdin when Executing
	cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
kind: ResourceList
# items are provided as nodes
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
    annotations:
      a: b
`))

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

}
Output:

apiVersion: config.kubernetes.io/v1
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)

ExampleBuild_modify implements a function that sets an annotation on each resource. The annotation value is configured via ResourceList.FunctionConfig.

package main

import (
	"bytes"

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

func main() {
	// create a struct matching the structure of ResourceList.FunctionConfig to hold its data
	var config struct {
		Data map[string]string `yaml:"data"`
	}
	fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
		for i := range items {
			// set the annotation on each resource item
			err := items[i].PipeE(yaml.SetAnnotation("value", config.Data["value"]))
			if err != nil {
				return nil, err
			}
		}
		return items, nil
	}
	p := framework.SimpleProcessor{Filter: kio.FilterFunc(fn), Config: &config}
	cmd := command.Build(p, command.StandaloneDisabled, false)

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

}
Output:

apiVersion: config.kubernetes.io/v1
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)

ExampleBuild_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/fn/framework/command"
	"sigs.k8s.io/kustomize/kyaml/yaml"
)

func main() {
	fn := func(rl *framework.ResourceList) error {
		// validation results
		var validationResults framework.Results

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

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

			// check replicas not specified
			if r != nil {
				continue
			}
			validationResults = append(validationResults, &framework.Result{
				Severity: framework.Error,
				Message:  "field is required",
				ResourceRef: &yaml.ResourceIdentifier{
					TypeMeta: meta.TypeMeta,
					NameMeta: meta.ObjectMeta.NameMeta,
				},
				Field: &framework.Field{
					Path:          "spec.replicas",
					ProposedValue: "1",
				},
			})
		}

		if len(validationResults) > 0 {
			rl.Results = validationResults
		}

		return rl.Results
	}

	cmd := command.Build(framework.ResourceListProcessorFunc(fn), command.StandaloneDisabled, true)
	// for testing purposes only -- normally read from stdin when Executing
	cmd.SetIn(bytes.NewBufferString(`
apiVersion: config.kubernetes.io/v1
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/v1
kind: ResourceList
items:
- apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: foo
results:
- message: field is required
  severity: error
  resourceRef:
    apiVersion: apps/v1
    kind: Deployment
    name: foo
  field:
    path: spec.replicas
    proposedValue: "1"

Types

type CLIMode

type CLIMode byte
const (
	StandaloneEnabled CLIMode = iota
	StandaloneDisabled
)

Jump to

Keyboard shortcuts

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