@@ -18,15 +18,20 @@ package resource
1818
1919import (
2020 "context"
21+ "encoding/json"
2122 "testing"
2223
24+ jsonpatch "github.com/evanphx/json-patch"
2325 "github.com/google/go-cmp/cmp"
26+ corev1 "k8s.io/api/core/v1"
2427 kerrors "k8s.io/apimachinery/pkg/api/errors"
2528 "k8s.io/apimachinery/pkg/api/meta"
2629 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
30+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2731 "k8s.io/apimachinery/pkg/runtime"
2832 "k8s.io/apimachinery/pkg/runtime/schema"
2933 "sigs.k8s.io/controller-runtime/pkg/client"
34+ "sigs.k8s.io/yaml"
3035
3136 "github.com/crossplane/crossplane-runtime/pkg/errors"
3237 "github.com/crossplane/crossplane-runtime/pkg/resource/fake"
@@ -54,6 +59,14 @@ func TestAPIPatchingApplicator(t *testing.T) {
5459 fakeRESTMapper := meta .NewDefaultRESTMapper ([]schema.GroupVersion {gvk .GroupVersion ()})
5560 fakeRESTMapper .AddSpecific (gvk , gvr , singular , meta .RESTScopeRoot )
5661
62+ // for additive merge patch option test
63+ currentYAML := `
64+ metadata:
65+ resourceVersion: "42"
66+ a: old
67+ b: old
68+ `
69+
5770 type args struct {
5871 ctx context.Context
5972 o client.Object
@@ -221,6 +234,63 @@ func TestAPIPatchingApplicator(t *testing.T) {
221234 err : kerrors .NewConflict (schema.GroupResource {Group : "example.com" , Resource : "things" }, current .GetName (), errors .New (errOptimisticLock )),
222235 },
223236 },
237+ "AdditiveMergePatch" : {
238+ reason : "No error with the old additive behaviour if desired" ,
239+ c : & test.MockClient {
240+ MockGet : test .NewMockGetFn (nil , func (o client.Object ) error {
241+ o .(* unstructured.Unstructured ).Object = map [string ]interface {}{}
242+ return yaml .Unmarshal ([]byte (currentYAML ), & o .(* unstructured.Unstructured ).Object )
243+ }),
244+ MockPatch : func (_ context.Context , o client.Object , patch client.Patch , _ ... client.PatchOption ) error {
245+ bs , err := patch .Data (o )
246+ if err != nil {
247+ return err
248+ }
249+ currentJSON , err := yaml .YAMLToJSON ([]byte (currentYAML ))
250+ if err != nil {
251+ return err
252+ }
253+ patched , err := jsonpatch .MergePatch (currentJSON , bs )
254+ if err != nil {
255+ return err
256+ }
257+ o .(* unstructured.Unstructured ).Object = map [string ]interface {}{}
258+ if err := json .Unmarshal (patched , & o .(* unstructured.Unstructured ).Object ); err != nil {
259+ return err
260+ }
261+ o .SetResourceVersion ("43" )
262+ return nil
263+ },
264+ MockGroupVersionKindFor : test .NewMockGroupVersionKindForFn (nil , gvk ),
265+ MockRESTMapper : test .NewMockRESTMapperFn (fakeRESTMapper ),
266+ },
267+ args : args {
268+ o : & unstructured.Unstructured {
269+ Object : map [string ]interface {}{
270+ "kind" : "Thing" ,
271+ "metadata" : map [string ]interface {}{
272+ "resourceVersion" : "42" ,
273+ },
274+ "b" : "changed" ,
275+ "c" : "added" ,
276+ },
277+ },
278+ ao : []ApplyOption {AdditiveMergePatchApplyOption },
279+ },
280+ want : want {
281+ o : & unstructured.Unstructured {
282+ Object : map [string ]interface {}{
283+ "kind" : "Thing" ,
284+ "metadata" : map [string ]interface {}{
285+ "resourceVersion" : "43" ,
286+ },
287+ "a" : "old" ,
288+ "b" : "changed" ,
289+ "c" : "added" ,
290+ },
291+ },
292+ },
293+ },
224294 }
225295
226296 for name , tc := range cases {
@@ -518,3 +588,204 @@ func TestAPIFinalizerAdder(t *testing.T) {
518588 })
519589 }
520590}
591+
592+ func TestAdditiveMergePatchApplyOption (t * testing.T ) {
593+ type args struct {
594+ current runtime.Object
595+ desired runtime.Object
596+ }
597+ type want struct {
598+ err error
599+ current runtime.Object
600+ desired runtime.Object
601+ }
602+ tests := []struct {
603+ name string
604+ args args
605+ want want
606+ }{
607+ {
608+ name : "equal unstructed" ,
609+ args : args {
610+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
611+ "kind" : "Thing" ,
612+ "a" : "foo" ,
613+ }},
614+ desired : & unstructured.Unstructured {Object : map [string ]interface {}{
615+ "kind" : "Thing" ,
616+ "a" : "foo" ,
617+ }},
618+ },
619+ want : want {
620+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
621+ "kind" : "Thing" ,
622+ "a" : "foo" ,
623+ }},
624+ desired : & unstructured.Unstructured {Object : map [string ]interface {}{
625+ "kind" : "Thing" ,
626+ "a" : "foo" ,
627+ }},
628+ },
629+ },
630+ {
631+ name : "overlapping unstructed" ,
632+ args : args {
633+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
634+ "kind" : "Thing" ,
635+ "a" : "foo" ,
636+ "b" : "foo" ,
637+ "c" : "foo" ,
638+ }},
639+ desired : & unstructured.Unstructured {Object : map [string ]interface {}{
640+ "kind" : "Thing" ,
641+ "a" : "foo" ,
642+ "b" : "bar" ,
643+ "d" : "bar" ,
644+ }},
645+ },
646+ want : want {
647+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
648+ "kind" : "Thing" ,
649+ "a" : "foo" ,
650+ "b" : "foo" ,
651+ "c" : "foo" ,
652+ }},
653+ desired : & unstructured.Unstructured {Object : map [string ]interface {}{
654+ "kind" : "Thing" ,
655+ "a" : "foo" ,
656+ "b" : "bar" ,
657+ "c" : "foo" ,
658+ "d" : "bar" ,
659+ }},
660+ },
661+ },
662+ {
663+ name : "equal typed" ,
664+ args : args {
665+ current : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
666+ "a" : "foo" ,
667+ }}},
668+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
669+ "a" : "foo" ,
670+ }}},
671+ },
672+ want : want {
673+ current : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
674+ "a" : "foo" ,
675+ }}},
676+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
677+ "a" : "foo" ,
678+ }}},
679+ },
680+ },
681+ {
682+ name : "overlapping typed" ,
683+ args : args {
684+ current : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
685+ "a" : "foo" ,
686+ "b" : "foo" ,
687+ "c" : "foo" ,
688+ }}},
689+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
690+ "a" : "foo" ,
691+ "b" : "bar" ,
692+ "d" : "bar" ,
693+ }}},
694+ },
695+ want : want {
696+ current : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
697+ "a" : "foo" ,
698+ "b" : "foo" ,
699+ "c" : "foo" ,
700+ }}},
701+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
702+ "a" : "foo" ,
703+ "b" : "bar" ,
704+ "c" : "foo" ,
705+ "d" : "bar" ,
706+ }}},
707+ },
708+ },
709+ {
710+ name : "equal mixed" ,
711+ args : args {
712+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
713+ "kind" : "Thing" ,
714+ "metadata" : map [string ]interface {}{
715+ "labels" : map [string ]interface {}{
716+ "a" : "foo" ,
717+ },
718+ },
719+ }},
720+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
721+ "a" : "foo" ,
722+ }}},
723+ },
724+ want : want {
725+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
726+ "kind" : "Thing" ,
727+ "metadata" : map [string ]interface {}{
728+ "labels" : map [string ]interface {}{
729+ "a" : "foo" ,
730+ },
731+ },
732+ }},
733+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
734+ "a" : "foo" ,
735+ }}},
736+ },
737+ },
738+ {
739+ name : "overlapping mixed" ,
740+ args : args {
741+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
742+ "kind" : "Thing" ,
743+ "metadata" : map [string ]interface {}{
744+ "labels" : map [string ]interface {}{
745+ "a" : "foo" ,
746+ "b" : "foo" ,
747+ "c" : "foo" ,
748+ },
749+ },
750+ }},
751+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
752+ "a" : "foo" ,
753+ "b" : "bar" ,
754+ "d" : "bar" ,
755+ }}},
756+ },
757+ want : want {
758+ current : & unstructured.Unstructured {Object : map [string ]interface {}{
759+ "kind" : "Thing" ,
760+ "metadata" : map [string ]interface {}{
761+ "labels" : map [string ]interface {}{
762+ "a" : "foo" ,
763+ "b" : "foo" ,
764+ "c" : "foo" ,
765+ },
766+ },
767+ }},
768+ desired : & corev1.ConfigMap {ObjectMeta : metav1.ObjectMeta {Labels : map [string ]string {
769+ "a" : "foo" ,
770+ "b" : "bar" ,
771+ "c" : "foo" ,
772+ "d" : "bar" ,
773+ }}},
774+ },
775+ },
776+ }
777+ for _ , tt := range tests {
778+ t .Run (tt .name , func (t * testing.T ) {
779+ err := AdditiveMergePatchApplyOption (context .Background (), tt .args .current , tt .args .desired )
780+ if diff := cmp .Diff (tt .want .err , err , test .EquateErrors ()); diff != "" {
781+ t .Errorf ("AdditiveMergePatchApplyOption() error = %v, wantErr %v" , err , tt .want .err )
782+ }
783+ if diff := cmp .Diff (tt .want .current , tt .args .current ); diff != "" {
784+ t .Errorf ("AdditiveMergePatchApplyOption()\n current = %v\n want = %v\n diff = %s" , tt .args .current , tt .want .current , diff )
785+ }
786+ if diff := cmp .Diff (tt .want .desired , tt .args .desired ); diff != "" {
787+ t .Errorf ("AdditiveMergePatchApplyOption()\n current = %v\n want = %v\n diff = %s" , tt .args .desired , tt .want .desired , diff )
788+ }
789+ })
790+ }
791+ }
0 commit comments