Skip to content

Commit 9a0fe90

Browse files
committed
pkg/resource: add AdditiveMergePatchApplyOption
Signed-off-by: Dr. Stefan Schimanski <[email protected]>
1 parent 36c1fe2 commit 9a0fe90

File tree

4 files changed

+381
-1
lines changed

4 files changed

+381
-1
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.20
55
require (
66
dario.cat/mergo v1.0.0
77
github.com/bufbuild/buf v1.26.1
8+
github.com/evanphx/json-patch v5.6.0+incompatible
89
github.com/go-logr/logr v1.2.4
910
github.com/google/go-cmp v0.5.9
1011
github.com/spf13/afero v1.9.5

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
9696
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
9797
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
9898
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
99+
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
99100
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
100101
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
101102
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=

pkg/resource/api.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ limitations under the License.
1717
package resource
1818

1919
import (
20+
"bytes"
2021
"context"
2122

23+
jsonpatch "github.com/evanphx/json-patch"
2224
kerrors "k8s.io/apimachinery/pkg/api/errors"
25+
"k8s.io/apimachinery/pkg/runtime"
2326
"k8s.io/apimachinery/pkg/runtime/schema"
27+
"k8s.io/apimachinery/pkg/runtime/serializer/json"
2428
"k8s.io/apimachinery/pkg/types"
2529
"sigs.k8s.io/controller-runtime/pkg/client"
2630

@@ -54,7 +58,7 @@ func NewAPIPatchingApplicator(c client.Client) *APIPatchingApplicator {
5458
// Apply changes to the supplied object. The object will be created if it does
5559
// not exist, or patched if it does. If the object does exist, it will only be
5660
// patched if the passed object has the same or an empty resource version.
57-
func (a *APIPatchingApplicator) Apply(ctx context.Context, obj client.Object, ao ...ApplyOption) error { //nolint:gocyclo // the logic here is crucial and deserves to stay in one method
61+
func (a *APIPatchingApplicator) Apply(ctx context.Context, obj client.Object, ao ...ApplyOption) error {
5862
if obj.GetName() == "" && obj.GetGenerateName() != "" {
5963
return a.client.Create(ctx, obj)
6064
}
@@ -102,6 +106,50 @@ func groupResource(c client.Client, o client.Object) (schema.GroupResource, erro
102106
return m.Resource.GroupResource(), nil
103107
}
104108

109+
var emptyScheme = runtime.NewScheme() // no need to recognize any types
110+
var jsonSerializer = json.NewSerializerWithOptions(json.DefaultMetaFactory, emptyScheme, emptyScheme, json.SerializerOptions{})
111+
112+
// AdditiveMergePatchApplyOption returns an ApplyOption that makes
113+
// the Apply additive in the sense of a merge patch without null values. This is
114+
// the old behavior of the APIPatchingApplicator.
115+
//
116+
// This only works with a desired object of type *unstructured.Unstructured.
117+
//
118+
// Deprecated: replace with Server Side Apply.
119+
func AdditiveMergePatchApplyOption(_ context.Context, current, desired runtime.Object) error {
120+
// set GVK uniformly to the desired object to make serializer happy
121+
currentGVK, desiredGVK := current.GetObjectKind().GroupVersionKind(), desired.GetObjectKind().GroupVersionKind()
122+
if !desiredGVK.Empty() && currentGVK != desiredGVK {
123+
return errors.Errorf("cannot apply %v to %v", desired.GetObjectKind().GroupVersionKind(), current.GetObjectKind().GroupVersionKind())
124+
}
125+
desired.GetObjectKind().SetGroupVersionKind(currentGVK)
126+
127+
// merge `desired` additively with `current`
128+
var currentBytes, desiredBytes bytes.Buffer
129+
if err := jsonSerializer.Encode(current, &currentBytes); err != nil {
130+
return errors.Wrapf(err, "cannot marshal current %s", HumanReadableReference(nil, current))
131+
}
132+
if err := jsonSerializer.Encode(desired, &desiredBytes); err != nil {
133+
return errors.Wrapf(err, "cannot marshal desired %s", HumanReadableReference(nil, desired))
134+
}
135+
mergedBytes, err := jsonpatch.MergePatch(currentBytes.Bytes(), desiredBytes.Bytes())
136+
if err != nil {
137+
return errors.Wrapf(err, "cannot merge patch to %s", HumanReadableReference(nil, desired))
138+
}
139+
140+
// write merged object back to `desired`
141+
if _, _, err := jsonSerializer.Decode(mergedBytes, nil, desired); err != nil {
142+
return errors.Wrapf(err, "cannot unmarshal merged patch to %s", HumanReadableReference(nil, desired))
143+
}
144+
145+
// restore empty GVK for typed objects
146+
if _, isUnstructured := desired.(runtime.Unstructured); !isUnstructured {
147+
desired.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
148+
}
149+
150+
return nil
151+
}
152+
105153
// An APIUpdatingApplicator applies changes to an object by either creating or
106154
// updating it in a Kubernetes API server.
107155
type APIUpdatingApplicator struct {

0 commit comments

Comments
 (0)