...

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

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package framework
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"strings"
    10  	"text/template"
    11  
    12  	"sigs.k8s.io/kustomize/kyaml/errors"
    13  	"sigs.k8s.io/kustomize/kyaml/kio"
    14  	"sigs.k8s.io/kustomize/kyaml/openapi"
    15  	"sigs.k8s.io/kustomize/kyaml/yaml"
    16  	"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
    17  	"sigs.k8s.io/kustomize/kyaml/yaml/walk"
    18  )
    19  
    20  // ResourcePatchTemplate applies a patch to a collection of resources
    21  type ResourcePatchTemplate struct {
    22  	// Templates provides a list of templates to render into one or more patches.
    23  	Templates TemplateParser
    24  
    25  	// Selector targets the rendered patches to specific resources. If no Selector is provided,
    26  	// all resources will be patched.
    27  	//
    28  	// Although any Filter can be used, this framework provides several especially for Selector use:
    29  	// framework.Selector, framework.AndSelector, framework.OrSelector. You can also use any of the
    30  	// framework's ResourceMatchers here directly.
    31  	Selector kio.Filter
    32  
    33  	// TemplateData is the data to use when rendering the templates provided by the Templates field.
    34  	TemplateData interface{}
    35  }
    36  
    37  // DefaultTemplateData sets TemplateData to the provided default values if it has not already
    38  // been set.
    39  func (t *ResourcePatchTemplate) DefaultTemplateData(data interface{}) {
    40  	if t.TemplateData == nil {
    41  		t.TemplateData = data
    42  	}
    43  }
    44  
    45  // Filter applies the ResourcePatchTemplate to the appropriate resources in the input.
    46  // First, it applies the Selector to identify target resources. Then, it renders the Templates
    47  // into patches using TemplateData. Finally, it identifies applies the patch to each resource.
    48  func (t ResourcePatchTemplate) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
    49  	var err error
    50  	target := items
    51  	if t.Selector != nil {
    52  		target, err = t.Selector.Filter(items)
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  	}
    57  	if len(target) == 0 {
    58  		// nothing to do
    59  		return items, nil
    60  	}
    61  
    62  	if err := t.apply(target); err != nil {
    63  		return nil, errors.Wrap(err)
    64  	}
    65  	return items, nil
    66  }
    67  
    68  func (t *ResourcePatchTemplate) apply(matches []*yaml.RNode) error {
    69  	templates, err := t.Templates.Parse()
    70  	if err != nil {
    71  		return errors.Wrap(err)
    72  	}
    73  	var patches []*yaml.RNode
    74  	for i := range templates {
    75  		newP, err := renderPatches(templates[i], t.TemplateData)
    76  		if err != nil {
    77  			return errors.Wrap(err)
    78  		}
    79  		patches = append(patches, newP...)
    80  	}
    81  
    82  	// apply the patches to the matching resources
    83  	for j := range matches {
    84  		for i := range patches {
    85  			matches[j], err = merge2.Merge(patches[i], matches[j], yaml.MergeOptions{})
    86  			if err != nil {
    87  				return errors.WrapPrefixf(err, "failed to apply templated patch")
    88  			}
    89  		}
    90  	}
    91  	return nil
    92  }
    93  
    94  // ContainerPatchTemplate defines a patch to be applied to containers
    95  type ContainerPatchTemplate struct {
    96  	// Templates provides a list of templates to render into one or more patches that apply at the container level.
    97  	// For example, "name", "env" and "image" would be top-level fields in container patches.
    98  	Templates TemplateParser
    99  
   100  	// Selector targets the rendered patches to containers within specific resources.
   101  	// If no Selector is provided, all resources with containers will be patched (subject to
   102  	// ContainerMatcher, if provided).
   103  	//
   104  	// Although any Filter can be used, this framework provides several especially for Selector use:
   105  	// framework.Selector, framework.AndSelector, framework.OrSelector. You can also use any of the
   106  	// framework's ResourceMatchers here directly.
   107  	Selector kio.Filter
   108  
   109  	// TemplateData is the data to use when rendering the templates provided by the Templates field.
   110  	TemplateData interface{}
   111  
   112  	// ContainerMatcher targets the rendered patch to only those containers it matches.
   113  	// For example, it can be used with ContainerNameMatcher to patch only containers with
   114  	// specific names. If no ContainerMatcher is provided, all containers will be patched.
   115  	//
   116  	// The node passed to ContainerMatcher will be container-level, not a full resource node.
   117  	// For example, "name", "env" and "image" would be top level fields.
   118  	// To filter based on resource-level context, use the Selector field.
   119  	ContainerMatcher func(node *yaml.RNode) bool
   120  }
   121  
   122  // DefaultTemplateData sets TemplateData to the provided default values if it has not already
   123  // been set.
   124  func (cpt *ContainerPatchTemplate) DefaultTemplateData(data interface{}) {
   125  	if cpt.TemplateData == nil {
   126  		cpt.TemplateData = data
   127  	}
   128  }
   129  
   130  // Filter applies the ContainerPatchTemplate to the appropriate resources in the input.
   131  // First, it applies the Selector to identify target resources. Then, it renders the Templates
   132  // into patches using TemplateData. Finally, it identifies target containers and applies the
   133  // patches.
   134  func (cpt ContainerPatchTemplate) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
   135  	var err error
   136  	target := items
   137  	if cpt.Selector != nil {
   138  		target, err = cpt.Selector.Filter(items)
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  	}
   143  	if len(target) == 0 {
   144  		// nothing to do
   145  		return items, nil
   146  	}
   147  
   148  	if err := cpt.apply(target); err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	return items, nil
   153  }
   154  
   155  // PatchContainers applies the patch to each matching container in each resource.
   156  func (cpt ContainerPatchTemplate) apply(matches []*yaml.RNode) error {
   157  	templates, err := cpt.Templates.Parse()
   158  	if err != nil {
   159  		return errors.Wrap(err)
   160  	}
   161  	var patches []*yaml.RNode
   162  	for i := range templates {
   163  		newP, err := renderPatches(templates[i], cpt.TemplateData)
   164  		if err != nil {
   165  			return errors.Wrap(err)
   166  		}
   167  		patches = append(patches, newP...)
   168  	}
   169  
   170  	for i := range matches {
   171  		containers, err := matches[i].Pipe(yaml.LookupFirstMatch(yaml.ConventionalContainerPaths))
   172  		if err != nil {
   173  			return errors.Wrap(err)
   174  		}
   175  		if containers == nil {
   176  			continue
   177  		}
   178  		err = containers.VisitElements(func(node *yaml.RNode) error {
   179  			if cpt.ContainerMatcher != nil && !cpt.ContainerMatcher(node) {
   180  				return nil
   181  			}
   182  			for j := range patches {
   183  				merger := walk.Walker{
   184  					Sources:      []*yaml.RNode{node, patches[j]}, // dest, src
   185  					Visitor:      merge2.Merger{},
   186  					MergeOptions: yaml.MergeOptions{},
   187  					Schema: openapi.SchemaForResourceType(yaml.TypeMeta{
   188  						APIVersion: "v1",
   189  						Kind:       "Pod",
   190  					}).Lookup("spec", "containers").Elements(),
   191  				}
   192  				_, err = merger.Walk()
   193  				if err != nil {
   194  					return errors.WrapPrefixf(err, "failed to apply templated patch")
   195  				}
   196  			}
   197  			return nil
   198  		})
   199  		if err != nil {
   200  			return errors.Wrap(err)
   201  		}
   202  	}
   203  	return nil
   204  }
   205  
   206  func renderPatches(t *template.Template, data interface{}) ([]*yaml.RNode, error) {
   207  	// render the patches
   208  	var b bytes.Buffer
   209  	if err := t.Execute(&b, data); err != nil {
   210  		return nil, errors.WrapPrefixf(err, "failed to render patch template %v", t.DefinedTemplates())
   211  	}
   212  
   213  	// parse the patches into RNodes
   214  	var nodes []*yaml.RNode
   215  	for _, s := range strings.Split(b.String(), "\n---\n") {
   216  		s = strings.TrimSpace(s)
   217  		if s == "" {
   218  			continue
   219  		}
   220  		r := &kio.ByteReader{Reader: bytes.NewBufferString(s), OmitReaderAnnotations: true}
   221  		newNodes, err := r.Read()
   222  		if err != nil {
   223  			return nil, errors.WrapPrefixf(err,
   224  				"failed to parse rendered patch template into a resource:\n%s\n", addLineNumbers(s))
   225  		}
   226  		if err := yaml.ErrorIfAnyInvalidAndNonNull(yaml.MappingNode, newNodes...); err != nil {
   227  			return nil, errors.WrapPrefixf(err,
   228  				"failed to parse rendered patch template into a resource:\n%s\n", addLineNumbers(s))
   229  		}
   230  		nodes = append(nodes, newNodes...)
   231  	}
   232  	return nodes, nil
   233  }
   234  
   235  func addLineNumbers(s string) string {
   236  	lines := strings.Split(s, "\n")
   237  	for j := range lines {
   238  		lines[j] = fmt.Sprintf("%03d %s", j+1, lines[j])
   239  	}
   240  	return strings.Join(lines, "\n")
   241  }
   242  

View as plain text