...
1
2
3
4 package builtins
5
6 import (
7 "fmt"
8 "strings"
9
10 jsonpatch "gopkg.in/evanphx/json-patch.v4"
11 "sigs.k8s.io/kustomize/api/filters/patchjson6902"
12 "sigs.k8s.io/kustomize/api/resmap"
13 "sigs.k8s.io/kustomize/api/resource"
14 "sigs.k8s.io/kustomize/api/types"
15 "sigs.k8s.io/kustomize/kyaml/errors"
16 "sigs.k8s.io/kustomize/kyaml/kio/kioutil"
17 "sigs.k8s.io/yaml"
18 )
19
20 type PatchTransformerPlugin struct {
21 smPatches []*resource.Resource
22 jsonPatches jsonpatch.Patch
23
24 patchText string
25
26 patchSource string
27 Path string `json:"path,omitempty" yaml:"path,omitempty"`
28 Patch string `json:"patch,omitempty" yaml:"patch,omitempty"`
29 Target *types.Selector `json:"target,omitempty" yaml:"target,omitempty"`
30 Options map[string]bool `json:"options,omitempty" yaml:"options,omitempty"`
31 }
32
33 func (p *PatchTransformerPlugin) Config(h *resmap.PluginHelpers, c []byte) error {
34 if err := yaml.Unmarshal(c, p); err != nil {
35 return err
36 }
37
38 p.Patch = strings.TrimSpace(p.Patch)
39 switch {
40 case p.Patch == "" && p.Path == "":
41 return fmt.Errorf("must specify one of patch and path in\n%s", string(c))
42 case p.Patch != "" && p.Path != "":
43 return fmt.Errorf("patch and path can't be set at the same time\n%s", string(c))
44 case p.Patch != "":
45 p.patchText = p.Patch
46 p.patchSource = fmt.Sprintf("[patch: %q]", p.patchText)
47 case p.Path != "":
48 loaded, err := h.Loader().Load(p.Path)
49 if err != nil {
50 return fmt.Errorf("failed to get the patch file from path(%s): %w", p.Path, err)
51 }
52 p.patchText = string(loaded)
53 p.patchSource = fmt.Sprintf("[path: %q]", p.Path)
54 }
55
56 patchesSM, errSM := h.ResmapFactory().RF().SliceFromBytes([]byte(p.patchText))
57 patchesJson, errJson := jsonPatchFromBytes([]byte(p.patchText))
58
59 if (errSM == nil && errJson == nil) ||
60 (patchesSM != nil && patchesJson != nil) {
61 return fmt.Errorf(
62 "illegally qualifies as both an SM and JSON patch: %s",
63 p.patchSource)
64 }
65 if errSM != nil && errJson != nil {
66 return fmt.Errorf(
67 "unable to parse SM or JSON patch from %s", p.patchSource)
68 }
69 if errSM == nil {
70 p.smPatches = patchesSM
71 for _, loadedPatch := range p.smPatches {
72 if p.Options["allowNameChange"] {
73 loadedPatch.AllowNameChange()
74 }
75 if p.Options["allowKindChange"] {
76 loadedPatch.AllowKindChange()
77 }
78 }
79 } else {
80 p.jsonPatches = patchesJson
81 }
82 return nil
83 }
84
85 func (p *PatchTransformerPlugin) Transform(m resmap.ResMap) error {
86 if p.smPatches != nil {
87 return p.transformStrategicMerge(m)
88 }
89 return p.transformJson6902(m)
90 }
91
92
93
94
95 func (p *PatchTransformerPlugin) transformStrategicMerge(m resmap.ResMap) error {
96 if p.Target != nil {
97 if len(p.smPatches) > 1 {
98
99 return fmt.Errorf("Multiple Strategic-Merge Patches in one `patches` entry is not allowed to set `patches.target` field: %s", p.patchSource)
100 }
101
102
103 patch := p.smPatches[0]
104 selected, err := m.Select(*p.Target)
105 if err != nil {
106 return fmt.Errorf("unable to find patch target %q in `resources`: %w", p.Target, err)
107 }
108 return errors.Wrap(m.ApplySmPatch(resource.MakeIdSet(selected), patch))
109 }
110
111 for _, patch := range p.smPatches {
112 target, err := m.GetById(patch.OrgId())
113 if err != nil {
114 return fmt.Errorf("no resource matches strategic merge patch %q: %w", patch.OrgId(), err)
115 }
116 if err := target.ApplySmPatch(patch); err != nil {
117 return errors.Wrap(err)
118 }
119 }
120 return nil
121 }
122
123
124 func (p *PatchTransformerPlugin) transformJson6902(m resmap.ResMap) error {
125 if p.Target == nil {
126 return fmt.Errorf("must specify a target for JSON patch %s", p.patchSource)
127 }
128 resources, err := m.Select(*p.Target)
129 if err != nil {
130 return err
131 }
132 for _, res := range resources {
133 res.StorePreviousId()
134 internalAnnotations := kioutil.GetInternalAnnotations(&res.RNode)
135 err = res.ApplyFilter(patchjson6902.Filter{
136 Patch: p.patchText,
137 })
138 if err != nil {
139 return err
140 }
141
142 annotations := res.GetAnnotations()
143 for key, value := range internalAnnotations {
144 annotations[key] = value
145 }
146 err = res.SetAnnotations(annotations)
147 }
148 return nil
149 }
150
151
152 func jsonPatchFromBytes(in []byte) (jsonpatch.Patch, error) {
153 ops := string(in)
154 if ops == "" {
155 return nil, fmt.Errorf("empty json patch operations")
156 }
157
158 if ops[0] != '[' {
159
160
161
162 jsonOps, err := yaml.YAMLToJSON(in)
163 if err != nil {
164 return nil, err
165 }
166 ops = string(jsonOps)
167 }
168 return jsonpatch.DecodePatch([]byte(ops))
169 }
170
171 func NewPatchTransformerPlugin() resmap.TransformerPlugin {
172 return &PatchTransformerPlugin{}
173 }
174
View as plain text