Skip to content

Commit d637c06

Browse files
authored
support StrategicMergePatchType (#113)
Signed-off-by: Wei Liu <[email protected]>
1 parent a1677d7 commit d637c06

File tree

2 files changed

+72
-9
lines changed

2 files changed

+72
-9
lines changed

pkg/cloudevents/clients/utils/utils.go

+30-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"k8s.io/apimachinery/pkg/labels"
1717
"k8s.io/apimachinery/pkg/runtime"
1818
"k8s.io/apimachinery/pkg/types"
19+
"k8s.io/apimachinery/pkg/util/strategicpatch"
1920
"k8s.io/apimachinery/pkg/util/validation/field"
2021
"k8s.io/client-go/tools/cache"
2122
"k8s.io/klog/v2"
@@ -24,9 +25,25 @@ import (
2425
"open-cluster-management.io/sdk-go/pkg/cloudevents/generic"
2526
)
2627

27-
// Patch applies the patch to a resource with the patch type.
28-
func Patch[T generic.ResourceObject](patchType types.PatchType, work T, patchData []byte) (resource T, err error) {
29-
workData, err := json.Marshal(work)
28+
// Patch applies the given patch to a `generic.ResourceObject` using the specified patch type.
29+
//
30+
// Parameters:
31+
// - patchType: The type of patch to apply (JSONPatchType, MergePatchType and StrategicMergePatchType are supported).
32+
// - original: The resource object to be patched.
33+
// - patchData: The raw patch data.
34+
//
35+
// Returns:
36+
// - The patched resource object.
37+
// - An error if the patching fails at any step.
38+
//
39+
// Notes on StrategicMergePatch:
40+
// - Strategic Merge Patch (SMP) is a Kubernetes-specific patch type.
41+
// - It relies on **struct tags** (e.g., `patchStrategy` and `patchMergeKey`) defined in the Go types
42+
// of Kubernetes API objects to determine how to merge lists and maps (e.g., merge by key instead of replacing).
43+
// - SMP **only works** on known Kubernetes built-in API types (e.g., corev1.Pod) that have these metadata tags.
44+
// - It will **fail or behave incorrectly** if used on CRDs or custom types that don’t have the necessary tags.
45+
func Patch[T generic.ResourceObject](patchType types.PatchType, original T, patchData []byte) (resource T, err error) {
46+
originalData, err := json.Marshal(original)
3047
if err != nil {
3148
return resource, err
3249
}
@@ -39,26 +56,30 @@ func Patch[T generic.ResourceObject](patchType types.PatchType, work T, patchDat
3956
if err != nil {
4057
return resource, err
4158
}
42-
patchedData, err = patchObj.Apply(workData)
59+
patchedData, err = patchObj.Apply(originalData)
4360
if err != nil {
4461
return resource, err
4562
}
46-
4763
case types.MergePatchType:
48-
patchedData, err = jsonpatch.MergePatch(workData, patchData)
64+
patchedData, err = jsonpatch.MergePatch(originalData, patchData)
65+
if err != nil {
66+
return resource, err
67+
}
68+
case types.StrategicMergePatchType:
69+
patchedData, err = strategicpatch.StrategicMergePatch(originalData, patchData, original)
4970
if err != nil {
5071
return resource, err
5172
}
5273
default:
5374
return resource, fmt.Errorf("unsupported patch type: %s", patchType)
5475
}
5576

56-
patchedWork := new(T)
57-
if err := json.Unmarshal(patchedData, patchedWork); err != nil {
77+
patchedResource := new(T)
78+
if err := json.Unmarshal(patchedData, patchedResource); err != nil {
5879
return resource, err
5980
}
6081

61-
return *patchedWork, nil
82+
return *patchedResource, nil
6283
}
6384

6485
// ListResourcesWithOptions retrieves the resources from store which matches the options.

pkg/cloudevents/clients/utils/utils_test.go

+42
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
99
"k8s.io/apimachinery/pkg/types"
10+
"k8s.io/apimachinery/pkg/util/strategicpatch"
1011
"k8s.io/client-go/tools/cache"
1112

1213
workv1 "open-cluster-management.io/api/work/v1"
@@ -68,6 +69,47 @@ func TestPatch(t *testing.T) {
6869
}
6970
},
7071
},
72+
{
73+
name: "strategic merge patch",
74+
patchType: types.StrategicMergePatchType,
75+
work: &workv1.ManifestWork{
76+
ObjectMeta: metav1.ObjectMeta{
77+
Name: "test",
78+
},
79+
},
80+
patch: func() []byte {
81+
oldData, err := json.Marshal(&workv1.ManifestWork{
82+
ObjectMeta: metav1.ObjectMeta{
83+
Name: "test",
84+
},
85+
})
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
newData, err := json.Marshal(&workv1.ManifestWork{
90+
ObjectMeta: metav1.ObjectMeta{
91+
Name: "test",
92+
Namespace: "test",
93+
},
94+
})
95+
if err != nil {
96+
t.Fatal(err)
97+
}
98+
data, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, workv1.ManifestWork{})
99+
if err != nil {
100+
t.Fatal(err)
101+
}
102+
return data
103+
}(),
104+
validate: func(t *testing.T, work *workv1.ManifestWork) {
105+
if work.Name != "test" {
106+
t.Errorf("unexpected work %v", work)
107+
}
108+
if work.Namespace != "test" {
109+
t.Errorf("unexpected work %v", work)
110+
}
111+
},
112+
},
71113
}
72114

73115
for _, c := range cases {

0 commit comments

Comments
 (0)