Files
kustomize/kyaml/fn/framework/example_test.go
2020-11-12 11:54:58 -08:00

606 lines
14 KiB
Go

// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package framework_test
import (
"bytes"
"fmt"
"path/filepath"
"text/template"
"github.com/spf13/pflag"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const service = "Service"
// 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
func ExampleResourceList_modify() {
// 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
}
// 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
func ExampleCommand_modify() {
// 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
}
// ExampleCommand_generateReplace generates a resource from a functionConfig.
// If the resource already exist s, it replaces the resource with a new copy.
func ExampleCommand_generateReplace() {
// 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
}
// ExampleResourceList_generateReplace generates a resource from a functionConfig.
// If the resource already exist s, it replaces the resource with a new copy.
func ExampleResourceList_generateReplace() {
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
}
// 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.
func ExampleCommand_generateUpdate() {
// 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
}
// 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
func ExampleCommand_validate() {
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"
}
// ExampleTemplateCommand provides an example for using the TemplateCommand
func ExampleTemplateCommand() {
// 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
}
// ExampleTemplateCommand_files provides an example for using the TemplateCommand
func ExampleTemplateCommand_files() {
// 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
}