forked from crossplane-contrib/function-patch-and-transform
-
Notifications
You must be signed in to change notification settings - Fork 1
/
patches.go
319 lines (274 loc) · 9.8 KB
/
patches.go
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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
package main
import (
"fmt"
"strings"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/stevendborrelli/function-conditional-patch-and-transform/input/v1beta1"
)
const (
errPatchSetType = "a patch in a PatchSet cannot be of type PatchSet"
errCombineRequiresVariables = "combine patch types require at least one variable"
errFmtUndefinedPatchSet = "cannot find PatchSet by name %s"
errFmtInvalidPatchType = "patch type %s is unsupported"
errFmtCombineStrategyNotSupported = "combine strategy %s is not supported"
errFmtCombineConfigMissing = "given combine strategy %s requires configuration"
errFmtCombineStrategyFailed = "%s strategy could not combine"
errFmtExpandingArrayFieldPaths = "cannot expand ToFieldPath %s"
)
// A PatchInterface is a patch that can be applied between resources.
type PatchInterface interface {
GetType() v1beta1.PatchType
GetFromFieldPath() string
GetToFieldPath() string
GetCombine() *v1beta1.Combine
GetTransforms() []v1beta1.Transform
GetPolicy() *v1beta1.PatchPolicy
}
// PatchWithPatchSetName is a PatchInterface that has a PatchSetName field.
type PatchWithPatchSetName interface {
PatchInterface
GetPatchSetName() string
}
// Apply executes a patching operation between the from and to resources.
// Applies all patch types unless an 'only' filter is supplied.
func Apply(p PatchInterface, xr resource.Composite, cd resource.Composed, only ...v1beta1.PatchType) error {
return ApplyToObjects(p, xr, cd, only...)
}
// ApplyToObjects works like Apply but accepts any kind of runtime.Object. It
// might be vulnerable to conversion panics (see
// https://github.com/crossplane/crossplane/pull/3394 for details).
func ApplyToObjects(p PatchInterface, a, b runtime.Object, only ...v1beta1.PatchType) error {
if filterPatch(p, only...) {
return nil
}
switch p.GetType() {
case v1beta1.PatchTypeFromCompositeFieldPath, v1beta1.PatchTypeFromEnvironmentFieldPath:
return ApplyFromFieldPathPatch(p, a, b)
case v1beta1.PatchTypeToCompositeFieldPath, v1beta1.PatchTypeToEnvironmentFieldPath:
return ApplyFromFieldPathPatch(p, b, a)
case v1beta1.PatchTypeCombineFromComposite, v1beta1.PatchTypeCombineFromEnvironment:
return ApplyCombineFromVariablesPatch(p, a, b)
case v1beta1.PatchTypeCombineToComposite, v1beta1.PatchTypeCombineToEnvironment:
return ApplyCombineFromVariablesPatch(p, b, a)
case v1beta1.PatchTypePatchSet:
// Already resolved - nothing to do.
}
return errors.Errorf(errFmtInvalidPatchType, p.GetType())
}
// filterPatch returns true if patch should be filtered (not applied)
func filterPatch(p PatchInterface, only ...v1beta1.PatchType) bool {
// filter does not apply if not set
if len(only) == 0 {
return false
}
for _, patchType := range only {
if patchType == p.GetType() {
return false
}
}
return true
}
// ResolveTransforms applies a list of transforms to a patch value.
func ResolveTransforms(ts []v1beta1.Transform, input any) (any, error) {
var err error
for i, t := range ts {
if input, err = Resolve(t, input); err != nil {
// TODO(negz): Including the type might help find the offending transform faster.
return nil, errors.Wrapf(err, errFmtTransformAtIndex, i)
}
}
return input, nil
}
// ApplyFromFieldPathPatch patches the "to" resource, using a source field
// on the "from" resource. Values may be transformed if any are defined on
// the patch.
func ApplyFromFieldPathPatch(p PatchInterface, from, to runtime.Object) error {
if p.GetFromFieldPath() == "" {
return errors.Errorf(errFmtRequiredField, "FromFieldPath", p.GetType())
}
fromMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(from)
if err != nil {
return err
}
in, err := fieldpath.Pave(fromMap).GetValue(p.GetFromFieldPath())
if IsOptionalFieldPathNotFound(err, p.GetPolicy()) {
return nil
}
if err != nil {
return err
}
// Apply transform pipeline
out, err := ResolveTransforms(p.GetTransforms(), in)
if err != nil {
return err
}
// ComposedPatch all expanded fields if the ToFieldPath contains wildcards
if strings.Contains(p.GetToFieldPath(), "[*]") {
return patchFieldValueToMultiple(p.GetToFieldPath(), out, to)
}
return errors.Wrap(patchFieldValueToObject(p.GetToFieldPath(), out, to), "cannot patch to object")
}
// ApplyCombineFromVariablesPatch patches the "to" resource, taking a list of
// input variables and combining them into a single output value.
// The single output value may then be further transformed if they are defined
// on the patch.
func ApplyCombineFromVariablesPatch(p PatchInterface, from, to runtime.Object) error {
// Combine patch requires configuration
if p.GetCombine() == nil {
return errors.Errorf(errFmtRequiredField, "Combine", p.GetType())
}
// Destination field path is required since we can't default to multiple
// fields.
if p.GetToFieldPath() == "" {
return errors.Errorf(errFmtRequiredField, "ToFieldPath", p.GetType())
}
combine := p.GetCombine()
vl := len(combine.Variables)
if vl < 1 {
return errors.New(errCombineRequiresVariables)
}
fromMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(from)
if err != nil {
return err
}
in := make([]any, vl)
// Get value of each variable
// NOTE: This currently assumes all variables define a 'fromFieldPath'
// value. If we add new variable types, this may not be the case and
// this code may be better served split out into a dedicated function.
for i, sp := range combine.Variables {
iv, err := fieldpath.Pave(fromMap).GetValue(sp.FromFieldPath)
// If any source field is not found, we will not
// apply the patch. This is to avoid situations
// where a combine patch is expecting a fixed
// number of inputs (e.g. a string format
// expecting 3 fields '%s-%s-%s' but only
// receiving 2 values).
if IsOptionalFieldPathNotFound(err, p.GetPolicy()) {
return nil
}
if err != nil {
return err
}
in[i] = iv
}
// Combine input values
cb, err := Combine(*p.GetCombine(), in)
if err != nil {
return err
}
// Apply transform pipeline
out, err := ResolveTransforms(p.GetTransforms(), cb)
if err != nil {
return err
}
return errors.Wrap(patchFieldValueToObject(p.GetToFieldPath(), out, to), "cannot patch to object")
}
// IsOptionalFieldPathNotFound returns true if the supplied error indicates a
// field path was not found, and the supplied policy indicates a patch from that
// field path was optional.
func IsOptionalFieldPathNotFound(err error, p *v1beta1.PatchPolicy) bool {
switch {
case p == nil:
fallthrough
case p.FromFieldPath == nil:
fallthrough
case *p.FromFieldPath == v1beta1.FromFieldPathPolicyOptional:
return fieldpath.IsNotFound(err)
default:
return false
}
}
// Combine calls the appropriate combiner.
func Combine(c v1beta1.Combine, vars []any) (any, error) {
var out any
var err error
switch c.Strategy {
case v1beta1.CombineStrategyString:
if c.String == nil {
return nil, errors.Errorf(errFmtCombineConfigMissing, c.Strategy)
}
out = CombineString(c.String.Format, vars)
default:
return nil, errors.Errorf(errFmtCombineStrategyNotSupported, c.Strategy)
}
// Note: There are currently no tests or triggers to exercise this error as
// our only strategy ("String") uses fmt.Sprintf, which cannot return an error.
return out, errors.Wrapf(err, errFmtCombineStrategyFailed, string(c.Strategy))
}
// CombineString returns a single output by running a string format with all of
// its input variables.
func CombineString(format string, vars []any) string {
return fmt.Sprintf(format, vars...)
}
// ComposedTemplates returns the supplied composed resource templates with any
// supplied patchsets dereferenced.
func ComposedTemplates(pss []v1beta1.PatchSet, cts []v1beta1.ComposedTemplate) ([]v1beta1.ComposedTemplate, error) {
pn := make(map[string][]v1beta1.ComposedPatch)
for _, s := range pss {
for _, p := range s.Patches {
if p.Type == v1beta1.PatchTypePatchSet {
return nil, errors.New(errPatchSetType)
}
}
pn[s.Name] = s.GetComposedPatches()
}
ct := make([]v1beta1.ComposedTemplate, len(cts))
for i, r := range cts {
var po []v1beta1.ComposedPatch
for _, p := range r.Patches {
if p.Type != v1beta1.PatchTypePatchSet {
po = append(po, p)
continue
}
if p.PatchSetName == nil {
return nil, errors.Errorf(errFmtRequiredField, "PatchSetName", p.Type)
}
ps, ok := pn[*p.PatchSetName]
if !ok {
return nil, errors.Errorf(errFmtUndefinedPatchSet, *p.PatchSetName)
}
po = append(po, ps...)
}
ct[i] = r
ct[i].Patches = po
}
return ct, nil
}
// patchFieldValueToObject applies the value to the "to" object at the given
// path, returning any errors as they occur.
func patchFieldValueToObject(fieldPath string, value any, to runtime.Object) error {
paved, err := fieldpath.PaveObject(to)
if err != nil {
return err
}
if err := paved.SetValue(fieldPath, value); err != nil {
return err
}
return runtime.DefaultUnstructuredConverter.FromUnstructured(paved.UnstructuredContent(), to)
}
// patchFieldValueToMultiple, given a path with wildcards in an array index,
// expands the arrays paths in the "to" object and patches the value into each
// of the resulting fields, returning any errors as they occur.
func patchFieldValueToMultiple(fieldPath string, value any, to runtime.Object) error {
paved, err := fieldpath.PaveObject(to)
if err != nil {
return err
}
arrayFieldPaths, err := paved.ExpandWildcards(fieldPath)
if err != nil {
return err
}
if len(arrayFieldPaths) == 0 {
return errors.Errorf(errFmtExpandingArrayFieldPaths, fieldPath)
}
for _, field := range arrayFieldPaths {
if err := paved.SetValue(field, value); err != nil {
return err
}
}
return runtime.DefaultUnstructuredConverter.FromUnstructured(paved.UnstructuredContent(), to)
}