File: PatchTransformer.go

package info (click to toggle)
golang-k8s-sigs-kustomize-api 0.20.1%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 3,768 kB
  • sloc: makefile: 206; sh: 67
file content (174 lines) | stat: -rw-r--r-- 5,440 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Code generated by pluginator on PatchTransformer; DO NOT EDIT.
// pluginator {(devel)  unknown   }

package builtins

import (
	"fmt"
	"strings"

	jsonpatch "github.com/evanphx/json-patch/v5"
	"sigs.k8s.io/kustomize/api/filters/patchjson6902"
	"sigs.k8s.io/kustomize/api/resmap"
	"sigs.k8s.io/kustomize/api/resource"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kyaml/errors"
	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
	"sigs.k8s.io/yaml"
)

type PatchTransformerPlugin struct {
	smPatches   []*resource.Resource // strategic-merge patches
	jsonPatches jsonpatch.Patch      // json6902 patch
	// patchText is pure patch text created by Path or Patch
	patchText string
	// patchSource is patch source message
	patchSource string
	Path        string          `json:"path,omitempty"    yaml:"path,omitempty"`
	Patch       string          `json:"patch,omitempty"   yaml:"patch,omitempty"`
	Target      *types.Selector `json:"target,omitempty"  yaml:"target,omitempty"`
	Options     map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
}

func (p *PatchTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error {
	if err := yaml.Unmarshal(c, p); err != nil {
		return err
	}

	p.Patch = strings.TrimSpace(p.Patch)
	switch {
	case p.Patch == "" && p.Path == "":
		return fmt.Errorf("must specify one of patch and path in\n%s", string(c))
	case p.Patch != "" && p.Path != "":
		return fmt.Errorf("patch and path can't be set at the same time\n%s", string(c))
	case p.Patch != "":
		p.patchText = p.Patch
		p.patchSource = fmt.Sprintf("[patch: %q]", p.patchText)
	case p.Path != "":
		loaded, err := h.Loader().Load(p.Path)
		if err != nil {
			return fmt.Errorf("failed to get the patch file from path(%s): %w", p.Path, err)
		}
		p.patchText = string(loaded)
		p.patchSource = fmt.Sprintf("[path: %q]", p.Path)
	}

	patchesSM, errSM := h.ResmapFactory().RF().SliceFromBytes([]byte(p.patchText))
	patchesJson, errJson := jsonPatchFromBytes([]byte(p.patchText))

	if ((errSM == nil && errJson == nil) ||
		(patchesSM != nil && patchesJson != nil)) &&
		(len(patchesSM) > 0 && len(patchesJson) > 0) {
		return fmt.Errorf(
			"illegally qualifies as both an SM and JSON patch: %s",
			p.patchSource)
	}
	if errSM != nil && errJson != nil {
		return fmt.Errorf(
			"unable to parse SM or JSON patch from %s", p.patchSource)
	}
	if errSM == nil {
		p.smPatches = patchesSM
		for _, loadedPatch := range p.smPatches {
			if p.Options["allowNameChange"] {
				loadedPatch.AllowNameChange()
			}
			if p.Options["allowKindChange"] {
				loadedPatch.AllowKindChange()
			}
		}
	} else {
		p.jsonPatches = patchesJson
	}
	return nil
}

func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
	if p.smPatches != nil {
		return p.transformStrategicMerge(m)
	}
	return p.transformJson6902(m)
}

// transformStrategicMerge applies each loaded strategic merge patch
// to the resource in the ResMap that matches the identifier of the patch.
// If only one patch is specified, the Target can be used instead.
func (p *PatchTransformerPlugin) transformStrategicMerge(m resmap.ResMap) error {
	if p.Target != nil {
		if len(p.smPatches) > 1 {
			// detail: https://github.com/kubernetes-sigs/kustomize/issues/5049#issuecomment-1440604403
			return fmt.Errorf("Multiple Strategic-Merge Patches in one `patches` entry is not allowed to set `patches.target` field: %s", p.patchSource)
		}

		// single patch
		patch := p.smPatches[0]
		selected, err := m.Select(*p.Target)
		if err != nil {
			return fmt.Errorf("unable to find patch target %q in `resources`: %w", p.Target, err)
		}
		return errors.Wrap(m.ApplySmPatch(resource.MakeIdSet(selected), patch))
	}

	for _, patch := range p.smPatches {
		target, err := m.GetById(patch.OrgId())
		if err != nil {
			return fmt.Errorf("no resource matches strategic merge patch %q: %w", patch.OrgId(), err)
		}
		if err := target.ApplySmPatch(patch); err != nil {
			return errors.Wrap(err)
		}
	}
	return nil
}

// transformJson6902 applies json6902 Patch to all the resources in the ResMap that match Target.
func (p *PatchTransformerPlugin) transformJson6902(m resmap.ResMap) error {
	if p.Target == nil {
		return fmt.Errorf("must specify a target for JSON patch %s", p.patchSource)
	}
	resources, err := m.Select(*p.Target)
	if err != nil {
		return err
	}
	for _, res := range resources {
		res.StorePreviousId()
		internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
		err = res.ApplyFilter(patchjson6902.Filter{
			Patch: p.patchText,
		})
		if err != nil {
			return err
		}

		annotations := res.GetAnnotations()
		for key, value := range internalAnnotations {
			annotations[key] = value
		}
		err = res.SetAnnotations(annotations)
	}
	return nil
}

// jsonPatchFromBytes loads a Json 6902 patch from a bytes input
func jsonPatchFromBytes(in []byte) (jsonpatch.Patch, error) {
	ops := string(in)
	if ops == "" {
		return nil, fmt.Errorf("empty json patch operations")
	}

	if ops[0] != '[' {
		// TODO(5049):
		//   In the case of multiple yaml documents, return error instead of ignoring all but first.
		//   Details: https://github.com/kubernetes-sigs/kustomize/pull/5194#discussion_r1256686728
		jsonOps, err := yaml.YAMLToJSON(in)
		if err != nil {
			return nil, err
		}
		ops = string(jsonOps)
	}
	return jsonpatch.DecodePatch([]byte(ops))
}

func NewPatchTransformerPlugin() resmap.TransformerPlugin {
	return &PatchTransformerPlugin{}
}