...
Package command
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)
}
}
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(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)
▾ Example (GenerateReplace)
ExampleBuild_generateReplace generates a resource from a FunctionConfig.
If the resource already exists, it replaces the resource with a new copy.
Code:
type Spec struct {
Name string `yaml:"name,omitempty"`
}
type ExampleServiceGenerator struct {
Spec Spec `yaml:"spec,omitempty"`
}
functionConfig := &ExampleServiceGenerator{}
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
}
if meta.Name == functionConfig.Spec.Name &&
meta.Kind == service &&
meta.APIVersion == "v1" {
continue
}
newNodes = append(newNodes, items[i])
}
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)
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
`))
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)
▾ 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.
Code:
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{}
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
}
if meta.Name == functionConfig.Spec.Name &&
meta.Kind == service &&
meta.APIVersion == "v1" {
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
}
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)
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
`))
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.
Code:
var config struct {
Data map[string]string `yaml:"data"`
}
fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range items {
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)
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
`))
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
Code:
fn := func(rl *framework.ResourceList) error {
var validationResults framework.Results
for i := range rl.Items {
meta, err := rl.Items[i].GetMeta()
if err != nil {
return err
}
if meta.Kind != "Deployment" {
continue
}
r, err := rl.Items[i].Pipe(yaml.Lookup("spec", "replicas"))
if err != nil {
return err
}
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)
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
`))
if err := cmd.Execute(); err != nil {
}
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"
type CLIMode byte
const (
StandaloneEnabled CLIMode = iota
StandaloneDisabled
)