mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-18 04:25:31 +00:00
242 lines
7.5 KiB
Go
242 lines
7.5 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package framework
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
|
"sigs.k8s.io/kustomize/kyaml/openapi"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml/walk"
|
|
)
|
|
|
|
// ResourcePatchTemplate applies a patch to a collection of resources
|
|
type ResourcePatchTemplate struct {
|
|
// Templates provides a list of templates to render into one or more patches.
|
|
Templates TemplateParser
|
|
|
|
// Selector targets the rendered patches to specific resources. If no Selector is provided,
|
|
// all resources will be patched.
|
|
//
|
|
// Although any Filter can be used, this framework provides several especially for Selector use:
|
|
// framework.Selector, framework.AndSelector, framework.OrSelector. You can also use any of the
|
|
// framework's ResourceMatchers here directly.
|
|
Selector kio.Filter
|
|
|
|
// TemplateData is the data to use when rendering the templates provided by the Templates field.
|
|
TemplateData interface{}
|
|
}
|
|
|
|
// DefaultTemplateData sets TemplateData to the provided default values if it has not already
|
|
// been set.
|
|
func (t *ResourcePatchTemplate) DefaultTemplateData(data interface{}) {
|
|
if t.TemplateData == nil {
|
|
t.TemplateData = data
|
|
}
|
|
}
|
|
|
|
// Filter applies the ResourcePatchTemplate to the appropriate resources in the input.
|
|
// First, it applies the Selector to identify target resources. Then, it renders the Templates
|
|
// into patches using TemplateData. Finally, it identifies applies the patch to each resource.
|
|
func (t ResourcePatchTemplate) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
var err error
|
|
target := items
|
|
if t.Selector != nil {
|
|
target, err = t.Selector.Filter(items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(target) == 0 {
|
|
// nothing to do
|
|
return items, nil
|
|
}
|
|
|
|
if err := t.apply(target); err != nil {
|
|
return nil, errors.Wrap(err)
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (t *ResourcePatchTemplate) apply(matches []*yaml.RNode) error {
|
|
templates, err := t.Templates.Parse()
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
var patches []*yaml.RNode
|
|
for i := range templates {
|
|
newP, err := renderPatches(templates[i], t.TemplateData)
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
patches = append(patches, newP...)
|
|
}
|
|
|
|
// apply the patches to the matching resources
|
|
for j := range matches {
|
|
for i := range patches {
|
|
matches[j], err = merge2.Merge(patches[i], matches[j], yaml.MergeOptions{})
|
|
if err != nil {
|
|
return errors.WrapPrefixf(err, "failed to apply templated patch")
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ContainerPatchTemplate defines a patch to be applied to containers
|
|
type ContainerPatchTemplate struct {
|
|
// Templates provides a list of templates to render into one or more patches that apply at the container level.
|
|
// For example, "name", "env" and "image" would be top-level fields in container patches.
|
|
Templates TemplateParser
|
|
|
|
// Selector targets the rendered patches to containers within specific resources.
|
|
// If no Selector is provided, all resources with containers will be patched (subject to
|
|
// ContainerMatcher, if provided).
|
|
//
|
|
// Although any Filter can be used, this framework provides several especially for Selector use:
|
|
// framework.Selector, framework.AndSelector, framework.OrSelector. You can also use any of the
|
|
// framework's ResourceMatchers here directly.
|
|
Selector kio.Filter
|
|
|
|
// TemplateData is the data to use when rendering the templates provided by the Templates field.
|
|
TemplateData interface{}
|
|
|
|
// ContainerMatcher targets the rendered patch to only those containers it matches.
|
|
// For example, it can be used with ContainerNameMatcher to patch only containers with
|
|
// specific names. If no ContainerMatcher is provided, all containers will be patched.
|
|
//
|
|
// The node passed to ContainerMatcher will be container-level, not a full resource node.
|
|
// For example, "name", "env" and "image" would be top level fields.
|
|
// To filter based on resource-level context, use the Selector field.
|
|
ContainerMatcher func(node *yaml.RNode) bool
|
|
}
|
|
|
|
// DefaultTemplateData sets TemplateData to the provided default values if it has not already
|
|
// been set.
|
|
func (cpt *ContainerPatchTemplate) DefaultTemplateData(data interface{}) {
|
|
if cpt.TemplateData == nil {
|
|
cpt.TemplateData = data
|
|
}
|
|
}
|
|
|
|
// Filter applies the ContainerPatchTemplate to the appropriate resources in the input.
|
|
// First, it applies the Selector to identify target resources. Then, it renders the Templates
|
|
// into patches using TemplateData. Finally, it identifies target containers and applies the
|
|
// patches.
|
|
func (cpt ContainerPatchTemplate) Filter(items []*yaml.RNode) ([]*yaml.RNode, error) {
|
|
var err error
|
|
target := items
|
|
if cpt.Selector != nil {
|
|
target, err = cpt.Selector.Filter(items)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if len(target) == 0 {
|
|
// nothing to do
|
|
return items, nil
|
|
}
|
|
|
|
if err := cpt.apply(target); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// PatchContainers applies the patch to each matching container in each resource.
|
|
func (cpt ContainerPatchTemplate) apply(matches []*yaml.RNode) error {
|
|
templates, err := cpt.Templates.Parse()
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
var patches []*yaml.RNode
|
|
for i := range templates {
|
|
newP, err := renderPatches(templates[i], cpt.TemplateData)
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
patches = append(patches, newP...)
|
|
}
|
|
|
|
for i := range matches {
|
|
containers, err := matches[i].Pipe(yaml.LookupFirstMatch(yaml.ConventionalContainerPaths))
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
if containers == nil {
|
|
continue
|
|
}
|
|
err = containers.VisitElements(func(node *yaml.RNode) error {
|
|
if cpt.ContainerMatcher != nil && !cpt.ContainerMatcher(node) {
|
|
return nil
|
|
}
|
|
for j := range patches {
|
|
merger := walk.Walker{
|
|
Sources: []*yaml.RNode{node, patches[j]}, // dest, src
|
|
Visitor: merge2.Merger{},
|
|
MergeOptions: yaml.MergeOptions{},
|
|
Schema: openapi.SchemaForResourceType(yaml.TypeMeta{
|
|
APIVersion: "v1",
|
|
Kind: "Pod",
|
|
}).Lookup("spec", "containers").Elements(),
|
|
}
|
|
_, err = merger.Walk()
|
|
if err != nil {
|
|
return errors.WrapPrefixf(err, "failed to apply templated patch")
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func renderPatches(t *template.Template, data interface{}) ([]*yaml.RNode, error) {
|
|
// render the patches
|
|
var b bytes.Buffer
|
|
if err := t.Execute(&b, data); err != nil {
|
|
return nil, errors.WrapPrefixf(err, "failed to render patch template %v", t.DefinedTemplates())
|
|
}
|
|
|
|
// parse the patches into RNodes
|
|
var nodes []*yaml.RNode
|
|
for _, s := range strings.Split(b.String(), "\n---\n") {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" {
|
|
continue
|
|
}
|
|
r := &kio.ByteReader{Reader: bytes.NewBufferString(s), OmitReaderAnnotations: true}
|
|
newNodes, err := r.Read()
|
|
if err != nil {
|
|
return nil, errors.WrapPrefixf(err,
|
|
"failed to parse rendered patch template into a resource:\n%s\n", addLineNumbers(s))
|
|
}
|
|
if err := yaml.ErrorIfAnyInvalidAndNonNull(yaml.MappingNode, newNodes...); err != nil {
|
|
return nil, errors.WrapPrefixf(err,
|
|
"failed to parse rendered patch template into a resource:\n%s\n", addLineNumbers(s))
|
|
}
|
|
nodes = append(nodes, newNodes...)
|
|
}
|
|
return nodes, nil
|
|
}
|
|
|
|
func addLineNumbers(s string) string {
|
|
lines := strings.Split(s, "\n")
|
|
for j := range lines {
|
|
lines[j] = fmt.Sprintf("%03d %s", j+1, lines[j])
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|