...

Source file src/sigs.k8s.io/kustomize/kyaml/fn/framework/command/example_test.go

Documentation: sigs.k8s.io/kustomize/kyaml/fn/framework/command

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package command_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  
    10  	"sigs.k8s.io/kustomize/kyaml/fn/framework"
    11  	"sigs.k8s.io/kustomize/kyaml/fn/framework/command"
    12  	"sigs.k8s.io/kustomize/kyaml/kio"
    13  	"sigs.k8s.io/kustomize/kyaml/yaml"
    14  )
    15  
    16  const service = "Service"
    17  
    18  // ExampleBuild_modify implements a function that sets an annotation on each resource.
    19  // The annotation value is configured via ResourceList.FunctionConfig.
    20  func ExampleBuild_modify() {
    21  	// create a struct matching the structure of ResourceList.FunctionConfig to hold its data
    22  	var config struct {
    23  		Data map[string]string `yaml:"data"`
    24  	}
    25  	fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
    26  		for i := range items {
    27  			// set the annotation on each resource item
    28  			err := items[i].PipeE(yaml.SetAnnotation("value", config.Data["value"]))
    29  			if err != nil {
    30  				return nil, err
    31  			}
    32  		}
    33  		return items, nil
    34  	}
    35  	p := framework.SimpleProcessor{Filter: kio.FilterFunc(fn), Config: &config}
    36  	cmd := command.Build(p, command.StandaloneDisabled, false)
    37  
    38  	// for testing purposes only -- normally read from stdin when Executing
    39  	cmd.SetIn(bytes.NewBufferString(`
    40  apiVersion: config.kubernetes.io/v1
    41  kind: ResourceList
    42  # items are provided as nodes
    43  items:
    44  - apiVersion: apps/v1
    45    kind: Deployment
    46    metadata:
    47      name: foo
    48  - apiVersion: v1
    49    kind: Service
    50    metadata:
    51      name: foo
    52  functionConfig:
    53    apiVersion: v1
    54    kind: ConfigMap
    55    data:
    56      value: baz
    57  `))
    58  	// run the command
    59  	if err := cmd.Execute(); err != nil {
    60  		panic(err)
    61  	}
    62  
    63  	// Output:
    64  	// apiVersion: config.kubernetes.io/v1
    65  	// kind: ResourceList
    66  	// items:
    67  	// - apiVersion: apps/v1
    68  	//   kind: Deployment
    69  	//   metadata:
    70  	//     name: foo
    71  	//     annotations:
    72  	//       value: 'baz'
    73  	// - apiVersion: v1
    74  	//   kind: Service
    75  	//   metadata:
    76  	//     name: foo
    77  	//     annotations:
    78  	//       value: 'baz'
    79  	// functionConfig:
    80  	//   apiVersion: v1
    81  	//   kind: ConfigMap
    82  	//   data:
    83  	//     value: baz
    84  }
    85  
    86  // ExampleBuild_generateReplace generates a resource from a FunctionConfig.
    87  // If the resource already exists, it replaces the resource with a new copy.
    88  func ExampleBuild_generateReplace() {
    89  	// function API definition which will be parsed from the ResourceList.FunctionConfig
    90  	// read from stdin
    91  	type Spec struct {
    92  		Name string `yaml:"name,omitempty"`
    93  	}
    94  	type ExampleServiceGenerator struct {
    95  		Spec Spec `yaml:"spec,omitempty"`
    96  	}
    97  	functionConfig := &ExampleServiceGenerator{}
    98  
    99  	// function implementation -- generate a Service resource
   100  	p := &framework.SimpleProcessor{
   101  		Config: functionConfig,
   102  		Filter: kio.FilterFunc(func(items []*yaml.RNode) ([]*yaml.RNode, error) {
   103  			var newNodes []*yaml.RNode
   104  			for i := range items {
   105  				meta, err := items[i].GetMeta()
   106  				if err != nil {
   107  					return nil, err
   108  				}
   109  
   110  				// something we already generated, remove it from the list so we regenerate it
   111  				if meta.Name == functionConfig.Spec.Name &&
   112  					meta.Kind == service &&
   113  					meta.APIVersion == "v1" {
   114  					continue
   115  				}
   116  				newNodes = append(newNodes, items[i])
   117  			}
   118  
   119  			// generate the resource
   120  			n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
   121  kind: Service
   122  metadata:
   123   name: %s
   124  `, functionConfig.Spec.Name))
   125  			if err != nil {
   126  				return nil, err
   127  			}
   128  			newNodes = append(newNodes, n)
   129  			return newNodes, nil
   130  		}),
   131  	}
   132  	cmd := command.Build(p, command.StandaloneDisabled, false)
   133  
   134  	// for testing purposes only -- normally read from stdin when Executing
   135  	cmd.SetIn(bytes.NewBufferString(`
   136  apiVersion: config.kubernetes.io/v1
   137  kind: ResourceList
   138  # items are provided as nodes
   139  items:
   140  - apiVersion: apps/v1
   141    kind: Deployment
   142    metadata:
   143      name: foo
   144  functionConfig:
   145    apiVersion: example.com/v1alpha1
   146    kind: ExampleServiceGenerator
   147    spec:
   148      name: bar
   149  `))
   150  
   151  	// run the command
   152  	if err := cmd.Execute(); err != nil {
   153  		panic(err)
   154  	}
   155  
   156  	// Output:
   157  	// apiVersion: config.kubernetes.io/v1
   158  	// kind: ResourceList
   159  	// items:
   160  	// - apiVersion: apps/v1
   161  	//   kind: Deployment
   162  	//   metadata:
   163  	//     name: foo
   164  	// - apiVersion: v1
   165  	//   kind: Service
   166  	//   metadata:
   167  	//     name: bar
   168  	// functionConfig:
   169  	//   apiVersion: example.com/v1alpha1
   170  	//   kind: ExampleServiceGenerator
   171  	//   spec:
   172  	//     name: bar
   173  }
   174  
   175  // ExampleBuild_generateUpdate generates a resource, updating the previously generated
   176  // copy rather than replacing it.
   177  //
   178  // Note: This will keep manual edits to the previously generated copy.
   179  func ExampleBuild_generateUpdate() {
   180  	// function API definition which will be parsed from the ResourceList.FunctionConfig
   181  	// read from stdin
   182  	type Spec struct {
   183  		Name        string            `yaml:"name,omitempty"`
   184  		Annotations map[string]string `yaml:"annotations,omitempty"`
   185  	}
   186  	type ExampleServiceGenerator struct {
   187  		Spec Spec `yaml:"spec,omitempty"`
   188  	}
   189  	functionConfig := &ExampleServiceGenerator{}
   190  
   191  	// function implementation -- generate or update a Service resource
   192  	fn := func(items []*yaml.RNode) ([]*yaml.RNode, error) {
   193  		var found bool
   194  		for i := range items {
   195  			meta, err := items[i].GetMeta()
   196  			if err != nil {
   197  				return nil, err
   198  			}
   199  
   200  			// something we already generated, reconcile it to make sure it matches what
   201  			// is specified by the FunctionConfig
   202  			if meta.Name == functionConfig.Spec.Name &&
   203  				meta.Kind == service &&
   204  				meta.APIVersion == "v1" {
   205  				// set some values
   206  				for k, v := range functionConfig.Spec.Annotations {
   207  					err := items[i].PipeE(yaml.SetAnnotation(k, v))
   208  					if err != nil {
   209  						return nil, err
   210  					}
   211  				}
   212  				found = true
   213  				break
   214  			}
   215  		}
   216  		if found {
   217  			return items, nil
   218  		}
   219  
   220  		// generate the resource if not found
   221  		n, err := yaml.Parse(fmt.Sprintf(`apiVersion: v1
   222  kind: Service
   223  metadata:
   224   name: %s
   225  `, functionConfig.Spec.Name))
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  		for k, v := range functionConfig.Spec.Annotations {
   230  			err := n.PipeE(yaml.SetAnnotation(k, v))
   231  			if err != nil {
   232  				return nil, err
   233  			}
   234  		}
   235  		items = append(items, n)
   236  		return items, nil
   237  	}
   238  
   239  	p := &framework.SimpleProcessor{Config: functionConfig, Filter: kio.FilterFunc(fn)}
   240  	cmd := command.Build(p, command.StandaloneDisabled, false)
   241  
   242  	// for testing purposes only -- normally read from stdin when Executing
   243  	cmd.SetIn(bytes.NewBufferString(`
   244  apiVersion: config.kubernetes.io/v1
   245  kind: ResourceList
   246  # items are provided as nodes
   247  items:
   248  - apiVersion: apps/v1
   249    kind: Deployment
   250    metadata:
   251      name: foo
   252  - apiVersion: v1
   253    kind: Service
   254    metadata:
   255      name: bar
   256  functionConfig:
   257    apiVersion: example.com/v1alpha1
   258    kind: ExampleServiceGenerator
   259    spec:
   260      name: bar
   261      annotations:
   262        a: b
   263  `))
   264  
   265  	// run the command
   266  	if err := cmd.Execute(); err != nil {
   267  		panic(err)
   268  	}
   269  
   270  	// Output:
   271  	// apiVersion: config.kubernetes.io/v1
   272  	// kind: ResourceList
   273  	// items:
   274  	// - apiVersion: apps/v1
   275  	//   kind: Deployment
   276  	//   metadata:
   277  	//     name: foo
   278  	// - apiVersion: v1
   279  	//   kind: Service
   280  	//   metadata:
   281  	//     name: bar
   282  	//     annotations:
   283  	//       a: 'b'
   284  	// functionConfig:
   285  	//   apiVersion: example.com/v1alpha1
   286  	//   kind: ExampleServiceGenerator
   287  	//   spec:
   288  	//     name: bar
   289  	//     annotations:
   290  	//       a: b
   291  }
   292  
   293  // ExampleBuild_validate validates that all Deployment resources have the replicas field set.
   294  // If any Deployments do not contain spec.replicas, then the function will return results
   295  // which will be set on ResourceList.results
   296  func ExampleBuild_validate() {
   297  	fn := func(rl *framework.ResourceList) error {
   298  		// validation results
   299  		var validationResults framework.Results
   300  
   301  		// validate that each Deployment resource has spec.replicas set
   302  		for i := range rl.Items {
   303  			// only check Deployment resources
   304  			meta, err := rl.Items[i].GetMeta()
   305  			if err != nil {
   306  				return err
   307  			}
   308  			if meta.Kind != "Deployment" {
   309  				continue
   310  			}
   311  
   312  			// lookup replicas field
   313  			r, err := rl.Items[i].Pipe(yaml.Lookup("spec", "replicas"))
   314  			if err != nil {
   315  				return err
   316  			}
   317  
   318  			// check replicas not specified
   319  			if r != nil {
   320  				continue
   321  			}
   322  			validationResults = append(validationResults, &framework.Result{
   323  				Severity: framework.Error,
   324  				Message:  "field is required",
   325  				ResourceRef: &yaml.ResourceIdentifier{
   326  					TypeMeta: meta.TypeMeta,
   327  					NameMeta: meta.ObjectMeta.NameMeta,
   328  				},
   329  				Field: &framework.Field{
   330  					Path:          "spec.replicas",
   331  					ProposedValue: "1",
   332  				},
   333  			})
   334  		}
   335  
   336  		if len(validationResults) > 0 {
   337  			rl.Results = validationResults
   338  		}
   339  
   340  		return rl.Results
   341  	}
   342  
   343  	cmd := command.Build(framework.ResourceListProcessorFunc(fn), command.StandaloneDisabled, true)
   344  	// for testing purposes only -- normally read from stdin when Executing
   345  	cmd.SetIn(bytes.NewBufferString(`
   346  apiVersion: config.kubernetes.io/v1
   347  kind: ResourceList
   348  # items are provided as nodes
   349  items:
   350  - apiVersion: apps/v1
   351    kind: Deployment
   352    metadata:
   353      name: foo
   354  `))
   355  
   356  	// run the command
   357  	if err := cmd.Execute(); err != nil {
   358  		// normally exit 1 here
   359  	}
   360  
   361  	// Output:
   362  	// apiVersion: config.kubernetes.io/v1
   363  	// kind: ResourceList
   364  	// items:
   365  	// - apiVersion: apps/v1
   366  	//   kind: Deployment
   367  	//   metadata:
   368  	//     name: foo
   369  	// results:
   370  	// - message: field is required
   371  	//   severity: error
   372  	//   resourceRef:
   373  	//     apiVersion: apps/v1
   374  	//     kind: Deployment
   375  	//     name: foo
   376  	//   field:
   377  	//     path: spec.replicas
   378  	//     proposedValue: "1"
   379  }
   380  

View as plain text