Skip to content

Commit 876c9d9

Browse files
authored
Merge pull request #105 from ravilr/json_unmarshall
round-trip the fieldPath value in FromFieldPath patches through integer-preserving JSON unmarhsal
2 parents 2535533 + f2e64d8 commit 876c9d9

File tree

2 files changed

+98
-3
lines changed

2 files changed

+98
-3
lines changed

patches.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/pkg/errors"
88
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
99
"k8s.io/apimachinery/pkg/runtime"
10+
"k8s.io/apimachinery/pkg/util/json"
1011
"k8s.io/utils/ptr"
1112

1213
xpv1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
@@ -77,17 +78,39 @@ func ApplyFromFieldPathPatch(p PatchInterface, from, to runtime.Object) error {
7778
return err
7879
}
7980

81+
// Round-trip the "from" source field value through Kubernetes JSON decoder,
82+
// so that the json integers are unmarshalled as int64 consistent with "to"/dest value handling.
83+
// Kubernetes JSON decoder will get us a map[string]any where number values are int64,
84+
// but protojson and structpb will get us one where number values are float64.
85+
// https://pkg.go.dev/sigs.k8s.io/json#UnmarshalCaseSensitivePreserveInts
86+
v, err := toValidJSON(out)
87+
if err != nil {
88+
return err
89+
}
90+
8091
mo, err := toMergeOption(p)
8192
if err != nil {
8293
return err
8394
}
8495

8596
// ComposedPatch all expanded fields if the ToFieldPath contains wildcards
8697
if strings.Contains(p.GetToFieldPath(), "[*]") {
87-
return patchFieldValueToMultiple(p.GetToFieldPath(), out, to, mo)
98+
return patchFieldValueToMultiple(p.GetToFieldPath(), v, to, mo)
8899
}
89100

90-
return errors.Wrap(patchFieldValueToObject(p.GetToFieldPath(), out, to, mo), "cannot patch to object")
101+
return errors.Wrap(patchFieldValueToObject(p.GetToFieldPath(), v, to, mo), "cannot patch to object")
102+
}
103+
104+
func toValidJSON(value any) (any, error) {
105+
var v any
106+
j, err := json.Marshal(value)
107+
if err != nil {
108+
return nil, errors.Wrap(err, "cannot marshal value to JSON")
109+
}
110+
if err := json.Unmarshal(j, &v); err != nil {
111+
return nil, errors.Wrap(err, "cannot unmarshal value from JSON")
112+
}
113+
return v, nil
91114
}
92115

93116
// toMergeOption returns the MergeOptions from the PatchPolicy's ToFieldPathPolicy, if defined.

patches_test.go

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package main
22

33
import (
4-
"encoding/json"
54
"testing"
65

76
"github.com/google/go-cmp/cmp"
87
"github.com/pkg/errors"
98
extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
109
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1110
"k8s.io/apimachinery/pkg/runtime"
11+
"k8s.io/apimachinery/pkg/util/json"
1212
"k8s.io/utils/ptr"
1313

1414
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
@@ -195,6 +195,78 @@ func TestApplyFromFieldPathPatch(t *testing.T) {
195195
err: errors.Errorf(errFmtExpandingArrayFieldPaths, "metadata.ownerReferences[*].badField"),
196196
},
197197
},
198+
"ValidToFieldPathWithWildcardsAndMergePolicy": {
199+
reason: "When passed a wildcarded path with appendSlice policy, appends the from field slice items to each of the expanded array field, with slice deduplication",
200+
args: args{
201+
p: &v1beta1.ComposedPatch{
202+
Type: v1beta1.PatchTypeFromCompositeFieldPath,
203+
Patch: v1beta1.Patch{
204+
FromFieldPath: ptr.To[string]("spec.parameters.allowedGroups"),
205+
ToFieldPath: ptr.To[string]("spec.forProvider.accessRules[*].allowedGroups"),
206+
Policy: &v1beta1.PatchPolicy{
207+
ToFieldPath: ptr.To(v1beta1.ToFieldPathPolicyForceMergeObjectsAppendArrays),
208+
},
209+
},
210+
},
211+
from: &composite.Unstructured{
212+
Unstructured: unstructured.Unstructured{Object: MustObject(`{
213+
"apiVersion": "test.crossplane.io/v1",
214+
"kind": "XR",
215+
"spec": {
216+
"parameters": {
217+
"allowedGroups": [12345678, 7891234]
218+
}
219+
}
220+
}`)},
221+
},
222+
to: &composed.Unstructured{
223+
Unstructured: unstructured.Unstructured{Object: MustObject(`{
224+
"apiVersion": "test.crossplane.io/v1",
225+
"kind": "Composed",
226+
"spec": {
227+
"forProvider": {
228+
"accessRules": [
229+
{
230+
"action": "Allow",
231+
"destination": "e1",
232+
"allowedGroups": [12345678]
233+
},
234+
{
235+
"action": "Allow",
236+
"destination": "e2",
237+
"allowedGroups": [12345678]
238+
}
239+
]
240+
}
241+
}
242+
}`)},
243+
},
244+
},
245+
want: want{
246+
to: &composed.Unstructured{
247+
Unstructured: unstructured.Unstructured{Object: MustObject(`{
248+
"apiVersion": "test.crossplane.io/v1",
249+
"kind": "Composed",
250+
"spec": {
251+
"forProvider": {
252+
"accessRules": [
253+
{
254+
"action": "Allow",
255+
"destination": "e1",
256+
"allowedGroups": [12345678, 7891234]
257+
},
258+
{
259+
"action": "Allow",
260+
"destination": "e2",
261+
"allowedGroups": [12345678, 7891234]
262+
}
263+
]
264+
}
265+
}
266+
}`)},
267+
},
268+
},
269+
},
198270
"DefaultToFieldCompositeFieldPathPatch": {
199271
reason: "Should correctly default the ToFieldPath value if not specified.",
200272
args: args{

0 commit comments

Comments
 (0)