Use server-side apply patch to update the resolved cross-resource references
in managed.APISimpleReferenceResolver. Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
This commit is contained in:
parent
53f2f2d472
commit
f1be5e62dc
1
go.mod
1
go.mod
|
|
@ -5,6 +5,7 @@ go 1.20
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.0
|
dario.cat/mergo v1.0.0
|
||||||
github.com/bufbuild/buf v1.28.1
|
github.com/bufbuild/buf v1.28.1
|
||||||
|
github.com/evanphx/json-patch v5.6.0+incompatible
|
||||||
github.com/go-logr/logr v1.3.0
|
github.com/go-logr/logr v1.3.0
|
||||||
github.com/google/go-cmp v0.6.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/spf13/afero v1.11.0
|
github.com/spf13/afero v1.11.0
|
||||||
|
|
|
||||||
1
go.sum
1
go.sum
|
|
@ -55,6 +55,7 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
|
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
|
||||||
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U=
|
||||||
|
github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww=
|
||||||
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4=
|
||||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,15 @@ package managed
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||||
|
|
@ -33,10 +36,20 @@ import (
|
||||||
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
"github.com/crossplane/crossplane-runtime/pkg/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// fieldOwnerAPISimpleRefResolver owns the reference fields
|
||||||
|
// the managed reconciler resolves.
|
||||||
|
fieldOwnerAPISimpleRefResolver = "managed.crossplane.io/api-simple-reference-resolver"
|
||||||
|
)
|
||||||
|
|
||||||
// Error strings.
|
// Error strings.
|
||||||
const (
|
const (
|
||||||
errCreateOrUpdateSecret = "cannot create or update connection secret"
|
errCreateOrUpdateSecret = "cannot create or update connection secret"
|
||||||
errUpdateManaged = "cannot update managed resource"
|
errUpdateManaged = "cannot update managed resource"
|
||||||
|
errPatchManaged = "cannot patch the managed resource via server-side apply"
|
||||||
|
errMarshalExisting = "cannot marshal the existing object into JSON"
|
||||||
|
errMarshalResolved = "cannot marshal the object with the resolved references into JSON"
|
||||||
|
errPreparePatch = "cannot prepare the JSON merge patch for the resolved object"
|
||||||
errUpdateManagedStatus = "cannot update managed resource status"
|
errUpdateManagedStatus = "cannot update managed resource status"
|
||||||
errResolveReferences = "cannot resolve references"
|
errResolveReferences = "cannot resolve references"
|
||||||
errUpdateCriticalAnnotations = "cannot update critical annotations"
|
errUpdateCriticalAnnotations = "cannot update critical annotations"
|
||||||
|
|
@ -130,6 +143,27 @@ func NewAPISimpleReferenceResolver(c client.Client) *APISimpleReferenceResolver
|
||||||
return &APISimpleReferenceResolver{client: c}
|
return &APISimpleReferenceResolver{client: c}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareJSONMerge(existing, resolved runtime.Object) ([]byte, error) {
|
||||||
|
// restore the to be replaced GVK so that the existing object is
|
||||||
|
// not modified by this function.
|
||||||
|
defer existing.GetObjectKind().SetGroupVersionKind(existing.GetObjectKind().GroupVersionKind())
|
||||||
|
// we need the apiVersion and kind in the patch document so we set them
|
||||||
|
// to their zero values and make them available in the calculated patch
|
||||||
|
// in the first place, instead of an unmarshal/marshal from the prepared
|
||||||
|
// patch []byte later.
|
||||||
|
existing.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
|
||||||
|
eBuff, err := json.Marshal(existing)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMarshalExisting)
|
||||||
|
}
|
||||||
|
rBuff, err := json.Marshal(resolved)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, errMarshalResolved)
|
||||||
|
}
|
||||||
|
patch, err := jsonpatch.CreateMergePatch(eBuff, rBuff)
|
||||||
|
return patch, errors.Wrap(err, errPreparePatch)
|
||||||
|
}
|
||||||
|
|
||||||
// ResolveReferences of the supplied managed resource by calling its
|
// ResolveReferences of the supplied managed resource by calling its
|
||||||
// ResolveReferences method, if any.
|
// ResolveReferences method, if any.
|
||||||
func (a *APISimpleReferenceResolver) ResolveReferences(ctx context.Context, mg resource.Managed) error {
|
func (a *APISimpleReferenceResolver) ResolveReferences(ctx context.Context, mg resource.Managed) error {
|
||||||
|
|
@ -151,7 +185,11 @@ func (a *APISimpleReferenceResolver) ResolveReferences(ctx context.Context, mg r
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.Wrap(a.client.Update(ctx, mg), errUpdateManaged)
|
patch, err := prepareJSONMerge(existing, mg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return errors.Wrap(a.client.Patch(ctx, mg, client.RawPatch(types.ApplyPatchType, patch), client.FieldOwner(fieldOwnerAPISimpleRefResolver), client.ForceOwnership), errPatchManaged)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A RetryingCriticalAnnotationUpdater is a CriticalAnnotationUpdater that
|
// A RetryingCriticalAnnotationUpdater is a CriticalAnnotationUpdater that
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ func TestAPISecretPublisher(t *testing.T) {
|
||||||
type mockSimpleReferencer struct {
|
type mockSimpleReferencer struct {
|
||||||
resource.Managed
|
resource.Managed
|
||||||
|
|
||||||
MockResolveReferences func(context.Context, client.Reader) error
|
MockResolveReferences func(context.Context, client.Reader) error `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *mockSimpleReferencer) ResolveReferences(ctx context.Context, c client.Reader) error {
|
func (r *mockSimpleReferencer) ResolveReferences(ctx context.Context, c client.Reader) error {
|
||||||
|
|
@ -313,7 +313,7 @@ func TestResolveReferences(t *testing.T) {
|
||||||
"SuccessfulUpdate": {
|
"SuccessfulUpdate": {
|
||||||
reason: "Should return without error when a value is successfully resolved.",
|
reason: "Should return without error when a value is successfully resolved.",
|
||||||
c: &test.MockClient{
|
c: &test.MockClient{
|
||||||
MockUpdate: test.NewMockUpdateFn(nil),
|
MockPatch: test.NewMockPatchFn(nil),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
|
|
@ -327,10 +327,10 @@ func TestResolveReferences(t *testing.T) {
|
||||||
},
|
},
|
||||||
want: nil,
|
want: nil,
|
||||||
},
|
},
|
||||||
"UpdateError": {
|
"PatchError": {
|
||||||
reason: "Should return an error when the managed resource cannot be updated.",
|
reason: "Should return an error when the managed resource cannot be updated.",
|
||||||
c: &test.MockClient{
|
c: &test.MockClient{
|
||||||
MockUpdate: test.NewMockUpdateFn(errBoom),
|
MockPatch: test.NewMockPatchFn(errBoom),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
|
|
@ -342,7 +342,7 @@ func TestResolveReferences(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: errors.Wrap(errBoom, errUpdateManaged),
|
want: errors.Wrap(errBoom, errPatchManaged),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -357,6 +357,49 @@ func TestResolveReferences(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrepareJSONMerge(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
existing runtime.Object
|
||||||
|
resolved runtime.Object
|
||||||
|
}
|
||||||
|
type want struct {
|
||||||
|
patch string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := map[string]struct {
|
||||||
|
reason string
|
||||||
|
args args
|
||||||
|
want want
|
||||||
|
}{
|
||||||
|
"SuccessfulPatch": {
|
||||||
|
reason: "Should successfully compute the JSON merge patch document.",
|
||||||
|
args: args{
|
||||||
|
existing: &fake.Managed{},
|
||||||
|
resolved: &fake.Managed{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "resolved",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
patch: `{"name":"resolved"}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range cases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
patch, err := prepareJSONMerge(tc.args.existing, tc.args.resolved)
|
||||||
|
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||||
|
t.Errorf("\n%s\nprepareJSONMerge(...): -wantErr, +gotErr:\n%s", tc.reason, diff)
|
||||||
|
}
|
||||||
|
if diff := cmp.Diff(tc.want.patch, string(patch)); diff != "" {
|
||||||
|
t.Errorf("\n%s\nprepareJSONMerge(...): -want, +got:\n%s", tc.reason, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRetryingCriticalAnnotationUpdater(t *testing.T) {
|
func TestRetryingCriticalAnnotationUpdater(t *testing.T) {
|
||||||
errBoom := errors.New("boom")
|
errBoom := errors.New("boom")
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue