Add managed resource finalizer immediately before creating

This commit moves where we set the finalizer for managed resources to right
before creating them, not at the beginning of the reconcile. This means we'll be
less likely to encounter issues where we can't delete a managed resource because
we could never create it in the first place, but we added a finalizer.

* By the time we get here we know our Observe call worked. If (for example) our
  cloud provider credentials were completely wrong, we'd never proceed far
  enough to add the finalizer.
* If Observe works but Create fails (for example because we had RO cloud
  provider credentials) we would already have added the finalizer, but...
* When the managed resource was deleted we'd be able to Observe that the
  external resource does not exist (because we were never able to Create it) and
  thus would not call Delete on the external resource and go straight to
  unpublishing credentials and removing the finalizer.

This commit also renames and refactors a bunch of our interfaces to use less
obtuse names. Previously sometimes a "finalize" method unbound a managed
resource, while at other times it removed the finalizer. Similarly, finalizers
were added in "initialize". We now have a 'Binder' interface with bind and
unbind methods, and two 'Finalizer' interfaces (one for Claim, and one for
Managed) that add and remove finalizers, as you would expect.

Signed-off-by: Nic Cope <negz@rk0n.org>
This commit is contained in:
Nic Cope 2019-10-31 19:32:26 -07:00
parent e834b8ab98
commit a3a5f918ac
7 changed files with 357 additions and 212 deletions

View File

@ -72,7 +72,6 @@ func (a *APIManagedCreator) Create(ctx context.Context, cm Claim, cs Class, mg M
// can generate a complete reference only after the creation.
mgr := meta.ReferenceTo(mg, MustGetKind(mg, a.typer))
cm.SetResourceReference(mgr)
meta.AddFinalizer(cm, claimFinalizerName)
return errors.Wrap(a.client.Update(ctx, cm), errUpdateClaim)
}
@ -142,21 +141,21 @@ func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context
return errors.Wrap(a.client.Update(ctx, mgcs), errUpdateSecret)
}
// An APIManagedBinder binds resources to claims by updating them in a
// Kubernetes API server. Note that APIManagedBinder does not support objects
// using the status subresource; such objects should use APIManagedStatusBinder.
type APIManagedBinder struct {
// An APIBinder binds resources to claims by updating them in a Kubernetes API
// server. Note that APIBinder does not support objects using the status
// subresource; such objects should use APIStatusBinder.
type APIBinder struct {
client client.Client
typer runtime.ObjectTyper
}
// NewAPIManagedBinder returns a new APIManagedBinder.
func NewAPIManagedBinder(c client.Client, t runtime.ObjectTyper) *APIManagedBinder {
return &APIManagedBinder{client: c, typer: t}
// NewAPIBinder returns a new APIBinder.
func NewAPIBinder(c client.Client, t runtime.ObjectTyper) *APIBinder {
return &APIBinder{client: c, typer: t}
}
// Bind the supplied resource to the supplied claim.
func (a *APIManagedBinder) Bind(ctx context.Context, cm Claim, mg Managed) error {
func (a *APIBinder) Bind(ctx context.Context, cm Claim, mg Managed) error {
cm.SetBindingPhase(v1alpha1.BindingPhaseBound)
// This claim reference will already be set for dynamically provisioned
@ -178,22 +177,32 @@ func (a *APIManagedBinder) Bind(ctx context.Context, cm Claim, mg Managed) error
return errors.Wrap(a.client.Update(ctx, cm), errUpdateClaim)
}
// An APIManagedStatusBinder binds resources to claims by updating them in a
// Kubernetes API server. Note that APIManagedStatusBinder does not support
// Unbind the supplied Claim from the supplied Managed resource.
func (a *APIBinder) Unbind(ctx context.Context, _ Claim, mg Managed) error {
// TODO(negz): We probably want to delete the managed resource here if its
// reclaim policy is delete, rather than relying on garbage collection, per
// https://github.com/crossplaneio/crossplane/issues/550
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
mg.SetClaimReference(nil)
return errors.Wrap(IgnoreNotFound(a.client.Update(ctx, mg)), errUpdateManaged)
}
// An APIStatusBinder binds resources to claims by updating them in a
// Kubernetes API server. Note that APIStatusBinder does not support
// objects that do not use the status subresource; such objects should use
// APIManagedBinder.
type APIManagedStatusBinder struct {
// APIBinder.
type APIStatusBinder struct {
client client.Client
typer runtime.ObjectTyper
}
// NewAPIManagedStatusBinder returns a new APIManagedStatusBinder.
func NewAPIManagedStatusBinder(c client.Client, t runtime.ObjectTyper) *APIManagedStatusBinder {
return &APIManagedStatusBinder{client: c, typer: t}
// NewAPIStatusBinder returns a new APIStatusBinder.
func NewAPIStatusBinder(c client.Client, t runtime.ObjectTyper) *APIStatusBinder {
return &APIStatusBinder{client: c, typer: t}
}
// Bind the supplied resource to the supplied claim.
func (a *APIManagedStatusBinder) Bind(ctx context.Context, cm Claim, mg Managed) error {
func (a *APIStatusBinder) Bind(ctx context.Context, cm Claim, mg Managed) error {
cm.SetBindingPhase(v1alpha1.BindingPhaseBound)
// This claim reference will already be set for dynamically provisioned
@ -219,40 +228,8 @@ func (a *APIManagedStatusBinder) Bind(ctx context.Context, cm Claim, mg Managed)
return errors.Wrap(a.client.Update(ctx, cm), errUpdateClaim)
}
// An APIManagedUnbinder finalizes the deletion of a managed resource by
// unbinding it, then updating it in the API server.
type APIManagedUnbinder struct {
client client.Client
}
// NewAPIManagedUnbinder returns a new APIManagedUnbinder.
func NewAPIManagedUnbinder(c client.Client) *APIManagedUnbinder {
return &APIManagedUnbinder{client: c}
}
// Finalize the supplied managed rersource.
func (a *APIManagedUnbinder) Finalize(ctx context.Context, mg Managed) error {
// TODO(negz): We probably want to delete the managed resource here if its
// reclaim policy is delete, rather than relying on garbage collection, per
// https://github.com/crossplaneio/crossplane/issues/550
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
mg.SetClaimReference(nil)
return errors.Wrap(IgnoreNotFound(a.client.Update(ctx, mg)), errUpdateManaged)
}
// An APIManagedStatusUnbinder finalizes the deletion of a managed resource by
// unbinding it, then updating it and its status in the API server.
type APIManagedStatusUnbinder struct {
client client.Client
}
// NewAPIManagedStatusUnbinder returns a new APIStatusManagedFinalizer.
func NewAPIManagedStatusUnbinder(c client.Client) *APIManagedStatusUnbinder {
return &APIManagedStatusUnbinder{client: c}
}
// Finalize the supplied resource claim.
func (a *APIManagedStatusUnbinder) Finalize(ctx context.Context, mg Managed) error {
// Unbind the supplied Claim from the supplied Managed resource.
func (a *APIStatusBinder) Unbind(ctx context.Context, _ Claim, mg Managed) error {
// TODO(negz): We probably want to delete the managed resource here if its
// reclaim policy is delete, rather than relying on garbage collection, per
// https://github.com/crossplaneio/crossplane/issues/550
@ -266,56 +243,58 @@ func (a *APIManagedStatusUnbinder) Finalize(ctx context.Context, mg Managed) err
return errors.Wrap(IgnoreNotFound(a.client.Status().Update(ctx, mg)), errUpdateManagedStatus)
}
// An APIClaimFinalizerRemover finalizes the deletion of a resource claim by
// removing its finalizer and updating it in the API server.
type APIClaimFinalizerRemover struct {
client client.Client
// An APIClaimFinalizer adds and removes finalizers to and from a claim.
type APIClaimFinalizer struct {
client client.Client
finalizer string
}
// NewAPIClaimFinalizerRemover returns a new APIClaimFinalizerRemover.
func NewAPIClaimFinalizerRemover(c client.Client) *APIClaimFinalizerRemover {
return &APIClaimFinalizerRemover{client: c}
// NewAPIClaimFinalizer returns a new APIClaimFinalizer.
func NewAPIClaimFinalizer(c client.Client, finalizer string) *APIClaimFinalizer {
return &APIClaimFinalizer{client: c, finalizer: finalizer}
}
// Finalize the supplied resource claim.
func (a *APIClaimFinalizerRemover) Finalize(ctx context.Context, cm Claim) error {
meta.RemoveFinalizer(cm, claimFinalizerName)
// AddFinalizer to the supplied Claim.
func (a *APIClaimFinalizer) AddFinalizer(ctx context.Context, cm Claim) error {
if meta.FinalizerExists(cm, a.finalizer) {
return nil
}
meta.AddFinalizer(cm, a.finalizer)
return errors.Wrap(a.client.Update(ctx, cm), errUpdateClaim)
}
// RemoveFinalizer from the supplied Claim.
func (a *APIClaimFinalizer) RemoveFinalizer(ctx context.Context, cm Claim) error {
meta.RemoveFinalizer(cm, a.finalizer)
return errors.Wrap(IgnoreNotFound(a.client.Update(ctx, cm)), errUpdateClaim)
}
// An APIManagedFinalizerRemover finalizes the deletion of a Managed resource by
// removing its finalizer and updating it in the API server.
type APIManagedFinalizerRemover struct{ client client.Client }
// NewAPIManagedFinalizerRemover returns a new APIManagedFinalizerRemover.
func NewAPIManagedFinalizerRemover(c client.Client) *APIManagedFinalizerRemover {
return &APIManagedFinalizerRemover{client: c}
// An APIManagedFinalizer adds and removes finalizers to and from a resource.
type APIManagedFinalizer struct {
client client.Client
finalizer string
}
// Finalize the deletion of the supplied Managed resource.
func (a *APIManagedFinalizerRemover) Finalize(ctx context.Context, mg Managed) error {
meta.RemoveFinalizer(mg, managedFinalizerName)
return errors.Wrap(a.client.Update(ctx, mg), errUpdateManaged)
// NewAPIManagedFinalizer returns a new APIManagedFinalizer.
func NewAPIManagedFinalizer(c client.Client, finalizer string) *APIManagedFinalizer {
return &APIManagedFinalizer{client: c, finalizer: finalizer}
}
// An APIManagedFinalizerAdder establishes ownership of a managed resource by
// adding a finalizer and updating it in the API server.
type APIManagedFinalizerAdder struct{ client client.Client }
// NewAPIManagedFinalizerAdder returns a new APIManagedFinalizerAdder.
func NewAPIManagedFinalizerAdder(c client.Client) *APIManagedFinalizerAdder {
return &APIManagedFinalizerAdder{client: c}
}
// Initialize ownership of the supplied Managed resource.
func (a *APIManagedFinalizerAdder) Initialize(ctx context.Context, mg Managed) error {
if meta.FinalizerExists(mg, managedFinalizerName) {
// AddFinalizer to the supplied Managed resource.
func (a *APIManagedFinalizer) AddFinalizer(ctx context.Context, mg Managed) error {
if meta.FinalizerExists(mg, a.finalizer) {
return nil
}
meta.AddFinalizer(mg, managedFinalizerName)
meta.AddFinalizer(mg, a.finalizer)
return errors.Wrap(a.client.Update(ctx, mg), errUpdateManaged)
}
// RemoveFinalizer from the supplied Managed resource.
func (a *APIManagedFinalizer) RemoveFinalizer(ctx context.Context, mg Managed) error {
meta.RemoveFinalizer(mg, a.finalizer)
return errors.Wrap(IgnoreNotFound(a.client.Update(ctx, mg)), errUpdateManaged)
}
// ManagedNameAsExternalName writes the name of the managed resource to
// the external name annotation field in order to be used as name of
// the external resource in provider.

View File

@ -36,12 +36,11 @@ import (
var (
_ ManagedCreator = &APIManagedCreator{}
_ ManagedConnectionPropagator = &APIManagedConnectionPropagator{}
_ ManagedBinder = &APIManagedBinder{}
_ ManagedBinder = &APIManagedStatusBinder{}
_ ClaimFinalizer = &APIClaimFinalizerRemover{}
_ ManagedInitializer = &APIManagedFinalizerAdder{}
_ Binder = &APIBinder{}
_ Binder = &APIStatusBinder{}
_ ClaimFinalizer = &APIClaimFinalizer{}
_ ManagedFinalizer = &APIManagedFinalizer{}
_ ManagedInitializer = &ManagedNameAsExternalName{}
_ ManagedFinalizer = &APIManagedFinalizerRemover{}
)
func TestCreate(t *testing.T) {
@ -122,7 +121,6 @@ func TestCreate(t *testing.T) {
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
want := &MockClaim{}
want.SetName(cmname)
meta.AddFinalizer(want, claimFinalizerName)
want.SetResourceReference(&corev1.ObjectReference{
Name: mgname,
APIVersion: MockGVK(&MockManaged{}).GroupVersion().String(),
@ -571,7 +569,7 @@ func TestBind(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIManagedBinder(tc.client, tc.typer)
api := NewAPIBinder(tc.client, tc.typer)
err := api.Bind(tc.args.ctx, tc.args.cm, tc.args.mg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Bind(...): -want error, +got error:\n%s", diff)
@ -736,7 +734,7 @@ func TestStatusBind(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIManagedStatusBinder(tc.client, tc.typer)
api := NewAPIStatusBinder(tc.client, tc.typer)
err := api.Bind(tc.args.ctx, tc.args.cm, tc.args.mg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Bind(...): -want error, +got error:\n%s", diff)
@ -751,9 +749,10 @@ func TestStatusBind(t *testing.T) {
}
}
func TestFinalizeResource(t *testing.T) {
func TestUnbind(t *testing.T) {
type args struct {
ctx context.Context
cm Claim
mg Managed
}
@ -766,6 +765,7 @@ func TestFinalizeResource(t *testing.T) {
cases := map[string]struct {
client client.Client
typer runtime.ObjectTyper
args args
want want
}{
@ -815,13 +815,13 @@ func TestFinalizeResource(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIManagedUnbinder(tc.client)
err := api.Finalize(tc.args.ctx, tc.args.mg)
api := NewAPIBinder(tc.client, tc.typer)
err := api.Unbind(tc.args.ctx, tc.args.cm, tc.args.mg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Finalize(...): -want error, +got error:\n%s", diff)
t.Errorf("api.Unbind(...): -want error, +got error:\n%s", diff)
}
if diff := cmp.Diff(tc.want.mg, tc.args.mg, test.EquateConditions()); diff != "" {
t.Errorf("api.Finalize(...) Managed: -want, +got:\n%s", diff)
t.Errorf("api.Unbind(...) Managed: -want, +got:\n%s", diff)
}
})
}
@ -829,6 +829,7 @@ func TestFinalizeResource(t *testing.T) {
func TestStatusFinalizeResource(t *testing.T) {
type args struct {
ctx context.Context
cm Claim
mg Managed
}
@ -841,6 +842,7 @@ func TestStatusFinalizeResource(t *testing.T) {
cases := map[string]struct {
client client.Client
typer runtime.ObjectTyper
args args
want want
}{
@ -909,19 +911,21 @@ func TestStatusFinalizeResource(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIManagedStatusUnbinder(tc.client)
err := api.Finalize(tc.args.ctx, tc.args.mg)
api := NewAPIStatusBinder(tc.client, tc.typer)
err := api.Unbind(tc.args.ctx, tc.args.cm, tc.args.mg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Finalize(...): -want error, +got error:\n%s", diff)
t.Errorf("api.Unbind(...): -want error, +got error:\n%s", diff)
}
if diff := cmp.Diff(tc.want.mg, tc.args.mg, test.EquateConditions()); diff != "" {
t.Errorf("api.Finalize(...) Managed: -want, +got:\n%s", diff)
t.Errorf("api.Unbind(...) Managed: -want, +got:\n%s", diff)
}
})
}
}
func TestFinalizeClaim(t *testing.T) {
func TestClaimRemoveFinalizer(t *testing.T) {
finalizer := "veryfinal"
type args struct {
ctx context.Context
cm Claim
@ -943,7 +947,7 @@ func TestFinalizeClaim(t *testing.T) {
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(errBoom)},
args: args{
ctx: context.Background(),
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{claimFinalizerName}}},
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
want: want{
err: errors.Wrap(errBoom, errUpdateClaim),
@ -954,7 +958,7 @@ func TestFinalizeClaim(t *testing.T) {
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil)},
args: args{
ctx: context.Background(),
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{claimFinalizerName}}},
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
want: want{
err: nil,
@ -965,8 +969,8 @@ func TestFinalizeClaim(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIClaimFinalizerRemover(tc.client)
err := api.Finalize(tc.args.ctx, tc.args.cm)
api := NewAPIClaimFinalizer(tc.client, finalizer)
err := api.RemoveFinalizer(tc.args.ctx, tc.args.cm)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Finalize(...): -want error, +got error:\n%s", diff)
}
@ -977,7 +981,9 @@ func TestFinalizeClaim(t *testing.T) {
}
}
func TestFinalizeManaged(t *testing.T) {
func TestManagedRemoveFinalizer(t *testing.T) {
finalizer := "veryfinal"
type args struct {
ctx context.Context
mg Managed
@ -999,7 +1005,7 @@ func TestFinalizeManaged(t *testing.T) {
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(errBoom)},
args: args{
ctx: context.Background(),
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{managedFinalizerName}}},
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
want: want{
err: errors.Wrap(errBoom, errUpdateManaged),
@ -1010,7 +1016,7 @@ func TestFinalizeManaged(t *testing.T) {
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil)},
args: args{
ctx: context.Background(),
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{managedFinalizerName}}},
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
want: want{
err: nil,
@ -1021,19 +1027,79 @@ func TestFinalizeManaged(t *testing.T) {
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIManagedFinalizerRemover(tc.client)
err := api.Finalize(tc.args.ctx, tc.args.mg)
api := NewAPIManagedFinalizer(tc.client, finalizer)
err := api.RemoveFinalizer(tc.args.ctx, tc.args.mg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Finalize(...): -want error, +got error:\n%s", diff)
t.Errorf("api.RemoveFinalizer(...): -want error, +got error:\n%s", diff)
}
if diff := cmp.Diff(tc.want.mg, tc.args.mg, test.EquateConditions()); diff != "" {
t.Errorf("api.Finalize(...) Managed: -want, +got:\n%s", diff)
t.Errorf("api.RemoveFinalizer(...) Managed: -want, +got:\n%s", diff)
}
})
}
}
func TestAPIClaimFinalizerAdder(t *testing.T) {
finalizer := "veryfinal"
type args struct {
ctx context.Context
cm Claim
}
type want struct {
err error
cm Claim
}
errBoom := errors.New("boom")
cases := map[string]struct {
client client.Client
args args
want want
}{
"UpdateClaimError": {
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(errBoom)},
args: args{
ctx: context.Background(),
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}},
},
want: want{
err: errors.Wrap(errBoom, errUpdateClaim),
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
},
"Successful": {
client: &test.MockClient{MockUpdate: test.NewMockUpdateFn(nil)},
args: args{
ctx: context.Background(),
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{}}},
},
want: want{
err: nil,
cm: &MockClaim{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIClaimFinalizer(tc.client, finalizer)
err := api.AddFinalizer(tc.args.ctx, tc.args.cm)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Initialize(...): -want error, +got error:\n%s", diff)
}
if diff := cmp.Diff(tc.want.cm, tc.args.cm, test.EquateConditions()); diff != "" {
t.Errorf("api.Initialize(...) Claim: -want, +got:\n%s", diff)
}
})
}
}
func TestAPIManagedFinalizerAdder(t *testing.T) {
finalizer := "veryfinal"
type args struct {
ctx context.Context
mg Managed
@ -1059,7 +1125,7 @@ func TestAPIManagedFinalizerAdder(t *testing.T) {
},
want: want{
err: errors.Wrap(errBoom, errUpdateManaged),
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{managedFinalizerName}}},
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
},
"Successful": {
@ -1070,15 +1136,15 @@ func TestAPIManagedFinalizerAdder(t *testing.T) {
},
want: want{
err: nil,
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{managedFinalizerName}}},
mg: &MockManaged{ObjectMeta: metav1.ObjectMeta{Finalizers: []string{finalizer}}},
},
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
api := NewAPIManagedFinalizerAdder(tc.client)
err := api.Initialize(tc.args.ctx, tc.args.mg)
api := NewAPIManagedFinalizer(tc.client, finalizer)
err := api.AddFinalizer(tc.args.ctx, tc.args.mg)
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
t.Errorf("api.Initialize(...): -want error, +got error:\n%s", diff)
}

View File

@ -120,57 +120,54 @@ func (fn ManagedConnectionPropagatorFn) PropagateConnection(ctx context.Context,
return fn(ctx, cm, mg)
}
// A ManagedBinder binds a resource claim to a managed resource.
type ManagedBinder interface {
// A Binder binds a resource claim to a managed resource.
type Binder interface {
// Bind the supplied Claim to the supplied Managed resource.
Bind(ctx context.Context, cm Claim, mg Managed) error
// Unbind the supplied Claim from the supplied Managed resource.
Unbind(ctx context.Context, cm Claim, mg Managed) error
}
// A ManagedBinderFn is a function that satisfies the ManagedBinder interface.
type ManagedBinderFn func(ctx context.Context, cm Claim, mg Managed) error
// Bind the supplied resource claim to the supplied managed resource.
func (fn ManagedBinderFn) Bind(ctx context.Context, cm Claim, mg Managed) error {
return fn(ctx, cm, mg)
// BinderFns satisfy the Binder interface.
type BinderFns struct {
BindFn func(ctx context.Context, cm Claim, mg Managed) error
UnbindFn func(ctx context.Context, cm Claim, mg Managed) error
}
// A ManagedFinalizer finalizes the deletion of a resource claim.
type ManagedFinalizer interface {
Finalize(ctx context.Context, cm Managed) error
// Bind the supplied Claim to the supplied Managed resource.
func (b BinderFns) Bind(ctx context.Context, cm Claim, mg Managed) error {
return b.BindFn(ctx, cm, mg)
}
// A FinalizerChain chains multiple managed finalizers.
type FinalizerChain []ManagedFinalizer
// Finalize calls each ManagedFinalizer serially. It returns the first
// error it encounters, if any.
func (cc FinalizerChain) Finalize(ctx context.Context, mg Managed) error {
for _, c := range cc {
if err := c.Finalize(ctx, mg); err != nil {
return err
}
}
return nil
}
// A ManagedFinalizerFn is a function that satisfies the ManagedFinalizer interface.
type ManagedFinalizerFn func(ctx context.Context, cm Managed) error
// Finalize the supplied managed resource.
func (fn ManagedFinalizerFn) Finalize(ctx context.Context, cm Managed) error {
return fn(ctx, cm)
// Unbind the supplied Claim from the supplied Managed resource.
func (b BinderFns) Unbind(ctx context.Context, cm Claim, mg Managed) error {
return b.UnbindFn(ctx, cm, mg)
}
// A ClaimFinalizer finalizes the deletion of a resource claim.
type ClaimFinalizer interface {
Finalize(ctx context.Context, cm Claim) error
// AddFinalizer to the supplied Claim.
AddFinalizer(ctx context.Context, cm Claim) error
// RemoveFinalizer from the supplied Claim.
RemoveFinalizer(ctx context.Context, cm Claim) error
}
// A ClaimFinalizerFn is a function that satisfies the ClaimFinalizer interface.
type ClaimFinalizerFn func(ctx context.Context, cm Claim) error
// A ClaimFinalizerFns satisfy the ClaimFinalizer interface.
type ClaimFinalizerFns struct {
AddFinalizerFn func(ctx context.Context, cm Claim) error
RemoveFinalizerFn func(ctx context.Context, cm Claim) error
}
// Finalize the supplied managed resource.
func (fn ClaimFinalizerFn) Finalize(ctx context.Context, cm Claim) error {
return fn(ctx, cm)
// AddFinalizer to the supplied Claim.
func (f ClaimFinalizerFns) AddFinalizer(ctx context.Context, mg Claim) error {
return f.AddFinalizerFn(ctx, mg)
}
// RemoveFinalizer from the supplied Claim.
func (f ClaimFinalizerFns) RemoveFinalizer(ctx context.Context, mg Claim) error {
return f.RemoveFinalizerFn(ctx, mg)
}
// A ClaimReconciler reconciles resource claims by creating exactly one kind of
@ -196,7 +193,7 @@ type crManaged struct {
ManagedConfigurator
ManagedCreator
ManagedConnectionPropagator
ManagedBinder
Binder
ManagedFinalizer
}
@ -205,8 +202,7 @@ func defaultCRManaged(m manager.Manager) crManaged {
ManagedConfigurator: NewObjectMetaConfigurator(m.GetScheme()),
ManagedCreator: NewAPIManagedCreator(m.GetClient(), m.GetScheme()),
ManagedConnectionPropagator: NewAPIManagedConnectionPropagator(m.GetClient(), m.GetScheme()),
ManagedBinder: NewAPIManagedBinder(m.GetClient(), m.GetScheme()),
ManagedFinalizer: NewAPIManagedUnbinder(m.GetClient()),
Binder: NewAPIBinder(m.GetClient(), m.GetScheme()),
}
}
@ -215,7 +211,7 @@ type crClaim struct {
}
func defaultCRClaim(m manager.Manager) crClaim {
return crClaim{ClaimFinalizer: NewAPIClaimFinalizerRemover(m.GetClient())}
return crClaim{ClaimFinalizer: NewAPIClaimFinalizer(m.GetClient(), claimFinalizerName)}
}
// A ClaimReconcilerOption configures a ClaimReconciler.
@ -246,19 +242,11 @@ func WithManagedConnectionPropagator(p ManagedConnectionPropagator) ClaimReconci
}
}
// WithManagedBinder specifies which ManagedBinder should be used to bind
// WithBinder specifies which Binder should be used to bind
// resources to their claim.
func WithManagedBinder(b ManagedBinder) ClaimReconcilerOption {
func WithBinder(b Binder) ClaimReconcilerOption {
return func(r *ClaimReconciler) {
r.managed.ManagedBinder = b
}
}
// WithManagedFinalizer specifies which ManagedFinalizer should be used to
// finalize managed resources when their claims are deleted.
func WithManagedFinalizer(f ManagedFinalizer) ClaimReconcilerOption {
return func(r *ClaimReconciler) {
r.managed.ManagedFinalizer = f
r.managed.Binder = b
}
}
@ -341,7 +329,7 @@ func (r *ClaimReconciler) Reconcile(req reconcile.Request) (reconcile.Result, er
}
if meta.WasDeleted(claim) {
if err := r.managed.Finalize(ctx, managed); err != nil {
if err := r.managed.Unbind(ctx, claim, managed); err != nil {
// If we didn't hit this error last time we'll be requeued
// implicitly due to the status update. Otherwise we want to retry
// after a brief wait, in case this was a transient error.
@ -349,7 +337,7 @@ func (r *ClaimReconciler) Reconcile(req reconcile.Request) (reconcile.Result, er
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
}
if err := r.claim.Finalize(ctx, claim); err != nil {
if err := r.claim.RemoveFinalizer(ctx, claim); err != nil {
// If we didn't hit this error last time we'll be requeued
// implicitly due to the status update. Otherwise we want to retry
// after a brief wait, in case this was a transient error.
@ -394,6 +382,14 @@ func (r *ClaimReconciler) Reconcile(req reconcile.Request) (reconcile.Result, er
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
}
if err := r.claim.AddFinalizer(ctx, claim); err != nil {
// If we didn't hit this error last time we'll be requeued
// implicitly due to the status update. Otherwise we want to retry
// after a brief wait, in case this was a transient error.
claim.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(err))
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, claim), errUpdateClaimStatus)
}
if err := r.managed.Create(ctx, claim, class, managed); err != nil {
// If we didn't hit this error last time we'll be requeued
// implicitly due to the status update. Otherwise we want to retry

View File

@ -164,7 +164,7 @@ func TestClaimReconciler(t *testing.T) {
},
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
},
"FinalizeManagedError": {
"UnbindError": {
args: args{
m: &MockManager{
c: &test.MockClient{
@ -195,12 +195,12 @@ func TestClaimReconciler(t *testing.T) {
use: ClassKind(MockGVK(&MockClass{})),
with: ManagedKind(MockGVK(&MockManaged{})),
o: []ClaimReconcilerOption{
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return errBoom })),
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ Claim, _ Managed) error { return errBoom }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
},
"FinalizeManagedSuccess": {
"UnbindSuccess": {
args: args{
m: &MockManager{
c: &test.MockClient{
@ -231,13 +231,13 @@ func TestClaimReconciler(t *testing.T) {
use: ClassKind(MockGVK(&MockClass{})),
with: ManagedKind(MockGVK(&MockManaged{})),
o: []ClaimReconcilerOption{
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return nil })),
WithClaimFinalizer(ClaimFinalizerFn(func(_ context.Context, _ Claim) error { return nil })),
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ Claim, _ Managed) error { return nil }}),
WithClaimFinalizer(ClaimFinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ Claim) error { return nil }}),
},
},
want: want{result: reconcile.Result{Requeue: false}},
},
"FinalizeClaimError": {
"RemoveClaimFinalizerError": {
args: args{
m: &MockManager{
c: &test.MockClient{
@ -268,13 +268,13 @@ func TestClaimReconciler(t *testing.T) {
use: ClassKind(MockGVK(&MockClass{})),
with: ManagedKind(MockGVK(&MockManaged{})),
o: []ClaimReconcilerOption{
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return nil })),
WithClaimFinalizer(ClaimFinalizerFn(func(_ context.Context, _ Claim) error { return errBoom })),
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ Claim, _ Managed) error { return nil }}),
WithClaimFinalizer(ClaimFinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ Claim) error { return errBoom }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
},
"FinalizeClaimSuccess": {
"SuccessfulDelete": {
args: args{
m: &MockManager{
c: &test.MockClient{
@ -305,8 +305,8 @@ func TestClaimReconciler(t *testing.T) {
use: ClassKind(MockGVK(&MockClass{})),
with: ManagedKind(MockGVK(&MockManaged{})),
o: []ClaimReconcilerOption{
WithManagedFinalizer(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return nil })),
WithClaimFinalizer(ClaimFinalizerFn(func(_ context.Context, _ Claim) error { return nil })),
WithBinder(BinderFns{UnbindFn: func(_ context.Context, _ Claim, _ Managed) error { return nil }}),
WithClaimFinalizer(ClaimFinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ Claim) error { return nil }}),
},
},
want: want{result: reconcile.Result{Requeue: false}},
@ -416,6 +416,49 @@ func TestClaimReconciler(t *testing.T) {
},
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
},
"AddFinalizerError": {
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil, func(o runtime.Object) error {
switch o := o.(type) {
case *MockClaim:
cm := &MockClaim{}
cm.SetClassReference(&corev1.ObjectReference{})
*o = *cm
return nil
case *MockClass:
return nil
default:
return errUnexpected
}
}),
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
want := &MockClaim{}
want.SetClassReference(&corev1.ObjectReference{})
want.SetConditions(v1alpha1.Creating(), v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
t.Errorf("-want, +got:\n%s", diff)
}
return nil
}),
},
s: MockSchemeWith(&MockClaim{}, &MockClass{}, &MockManaged{}),
},
of: ClaimKind(MockGVK(&MockClaim{})),
use: ClassKind(MockGVK(&MockClass{})),
with: ManagedKind(MockGVK(&MockManaged{})),
o: []ClaimReconcilerOption{
WithManagedConfigurators(ManagedConfiguratorFn(
func(_ context.Context, _ Claim, _ Class, _ Managed) error { return nil },
)),
WithClaimFinalizer(ClaimFinalizerFns{
AddFinalizerFn: func(_ context.Context, _ Claim) error { return errBoom }},
),
},
},
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},
},
"CreateManagedError": {
args: args{
m: &MockManager{
@ -452,6 +495,9 @@ func TestClaimReconciler(t *testing.T) {
WithManagedConfigurators(ManagedConfiguratorFn(
func(_ context.Context, _ Claim, _ Class, _ Managed) error { return nil },
)),
WithClaimFinalizer(ClaimFinalizerFns{
AddFinalizerFn: func(_ context.Context, _ Claim) error { return nil }},
),
WithManagedCreator(ManagedCreatorFn(
func(_ context.Context, _ Claim, _ Class, _ Managed) error { return errBoom },
)),
@ -625,9 +671,9 @@ func TestClaimReconciler(t *testing.T) {
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
func(_ context.Context, _ Claim, _ Managed) error { return nil },
)),
WithManagedBinder(ManagedBinderFn(
func(_ context.Context, _ Claim, _ Managed) error { return errBoom },
)),
WithBinder(BinderFns{
BindFn: func(_ context.Context, _ Claim, _ Managed) error { return errBoom },
}),
},
},
want: want{result: reconcile.Result{RequeueAfter: aShortWait}},

View File

@ -100,6 +100,31 @@ func (cc InitializerChain) Initialize(ctx context.Context, mg Managed) error {
return nil
}
// A ManagedFinalizer finalizes the deletion of a resource claim.
type ManagedFinalizer interface {
// AddFinalizer to the supplied Managed resource.
AddFinalizer(ctx context.Context, cm Managed) error
// RemoveFinalizer from the supplied Managed resource.
RemoveFinalizer(ctx context.Context, cm Managed) error
}
// A ManagedFinalizerFns satisfy the ManagedFinalizer interface.
type ManagedFinalizerFns struct {
AddFinalizerFn func(ctx context.Context, cm Managed) error
RemoveFinalizerFn func(ctx context.Context, cm Managed) error
}
// AddFinalizer to the supplied Managed resource.
func (f ManagedFinalizerFns) AddFinalizer(ctx context.Context, mg Managed) error {
return f.AddFinalizerFn(ctx, mg)
}
// RemoveFinalizer from the supplied Managed resource.
func (f ManagedFinalizerFns) RemoveFinalizer(ctx context.Context, mg Managed) error {
return f.RemoveFinalizerFn(ctx, mg)
}
// A ManagedInitializerFn is a function that satisfies the ManagedInitializer
// interface.
type ManagedInitializerFn func(ctx context.Context, mg Managed) error
@ -119,7 +144,7 @@ type ManagedReferenceResolver interface {
ResolveReferences(ctx context.Context, res CanReference) error
}
// a ManagedReferenceResolverFn is a function that satisfies the
// A ManagedReferenceResolverFn is a function that satisfies the
// ManagedReferenceResolver interface.
type ManagedReferenceResolverFn func(context.Context, CanReference) error
@ -282,12 +307,9 @@ type mrManaged struct {
func defaultMRManaged(m manager.Manager) mrManaged {
return mrManaged{
ManagedConnectionPublisher: NewAPISecretPublisher(m.GetClient(), m.GetScheme()),
ManagedFinalizer: NewAPIManagedFinalizerRemover(m.GetClient()),
ManagedInitializer: InitializerChain{
NewManagedNameAsExternalName(m.GetClient()),
NewAPIManagedFinalizerAdder(m.GetClient()),
},
ManagedReferenceResolver: NewAPIManagedReferenceResolver(m.GetClient()),
ManagedFinalizer: NewAPIManagedFinalizer(m.GetClient(), managedFinalizerName),
ManagedInitializer: NewManagedNameAsExternalName(m.GetClient()),
ManagedReferenceResolver: NewAPIManagedReferenceResolver(m.GetClient()),
}
}
@ -349,11 +371,11 @@ func WithManagedInitializers(i ...ManagedInitializer) ManagedReconcilerOption {
}
}
// WithManagedFinalizers specifies how the Reconciler should finalize a
// managed resource after it has been deleted.
func WithManagedFinalizers(f ...ManagedFinalizer) ManagedReconcilerOption {
// WithManagedFinalizer specifies how the Reconciler should add and remove
// finalizers to and from the managed resource.
func WithManagedFinalizer(f ManagedFinalizer) ManagedReconcilerOption {
return func(r *ManagedReconciler) {
r.managed.ManagedFinalizer = FinalizerChain(f)
r.managed.ManagedFinalizer = f
}
}
@ -491,7 +513,7 @@ func (r *ManagedReconciler) Reconcile(req reconcile.Request) (reconcile.Result,
managed.SetConditions(v1alpha1.ReconcileError(err))
return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(IgnoreNotFound(r.client.Status().Update(ctx, managed)), errUpdateManagedStatus)
}
if err := r.managed.Finalize(ctx, managed); err != nil {
if err := r.managed.RemoveFinalizer(ctx, managed); err != nil {
// If this is the first time we encounter this issue we'll be
// requeued implicitly when we update our status with the new error
// condition. If not, we want to try again after a short wait.
@ -514,6 +536,14 @@ func (r *ManagedReconciler) Reconcile(req reconcile.Request) (reconcile.Result,
return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(r.client.Status().Update(ctx, managed), errUpdateManagedStatus)
}
if err := r.managed.AddFinalizer(ctx, managed); err != nil {
// If this is the first time we encounter this issue we'll be
// requeued implicitly when we update our status with the new error
// condition. If not, we want to try again after a short wait.
managed.SetConditions(v1alpha1.ReconcileError(err))
return reconcile.Result{RequeueAfter: r.shortWait}, errors.Wrap(IgnoreNotFound(r.client.Status().Update(ctx, managed)), errUpdateManagedStatus)
}
if !observation.ResourceExists {
creation, err := external.Create(ctx, managed)
if err != nil {

View File

@ -361,8 +361,8 @@ func TestManagedReconciler(t *testing.T) {
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"FinalizeDeletionError": {
reason: "Errors finalizing managed resource deletion should trigger a requeue after a short wait.",
"RemoveFinalizerError": {
reason: "Errors removing the managed resource finalizer should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
@ -377,7 +377,7 @@ func TestManagedReconciler(t *testing.T) {
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors finalizing managed resource deletion should be reported as a conditioned status."
reason := "Errors removing the managed resource finalizer should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
@ -398,7 +398,7 @@ func TestManagedReconciler(t *testing.T) {
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizers(ManagedFinalizerFn(func(_ context.Context, _ Managed) error { return errBoom })),
WithManagedFinalizer(ManagedFinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ Managed) error { return errBoom }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
@ -429,7 +429,7 @@ func TestManagedReconciler(t *testing.T) {
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizers(),
WithManagedFinalizer(ManagedFinalizerFns{RemoveFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{Requeue: false}},
@ -465,6 +465,36 @@ func TestManagedReconciler(t *testing.T) {
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"AddFinalizerError": {
reason: "Errors adding a finalizer should trigger a requeue after a short wait.",
args: args{
m: &MockManager{
c: &test.MockClient{
MockGet: test.NewMockGetFn(nil),
MockStatusUpdate: test.MockStatusUpdateFn(func(_ context.Context, obj runtime.Object, _ ...client.UpdateOption) error {
want := &MockManaged{}
want.SetConditions(v1alpha1.ReferenceResolutionSuccess())
want.SetConditions(v1alpha1.ReconcileError(errBoom))
if diff := cmp.Diff(want, obj, test.EquateConditions()); diff != "" {
reason := "Errors adding a finalizer should be reported as a conditioned status."
t.Errorf("\nReason: %s\n-want, +got:\n%s", reason, diff)
}
return nil
}),
},
s: MockSchemeWith(&MockManaged{}),
},
mg: ManagedKind(MockGVK(&MockManaged{})),
o: []ManagedReconcilerOption{
WithManagedInitializers(),
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return errBoom }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
},
"CreateExternalError": {
reason: "Errors while creating an external resource should trigger a requeue after a short wait.",
args: args{
@ -500,6 +530,7 @@ func TestManagedReconciler(t *testing.T) {
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
@ -550,6 +581,7 @@ func TestManagedReconciler(t *testing.T) {
return nil
},
}),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
@ -579,6 +611,7 @@ func TestManagedReconciler(t *testing.T) {
WithManagedReferenceResolver(ManagedReferenceResolverFn(func(_ context.Context, _ CanReference) error { return nil })),
WithExternalConnecter(&NopConnecter{}),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
@ -615,6 +648,7 @@ func TestManagedReconciler(t *testing.T) {
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedLongWait}},
@ -654,6 +688,7 @@ func TestManagedReconciler(t *testing.T) {
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
@ -704,6 +739,7 @@ func TestManagedReconciler(t *testing.T) {
return nil
},
}),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedShortWait}},
@ -743,6 +779,7 @@ func TestManagedReconciler(t *testing.T) {
return c, nil
})),
WithManagedConnectionPublishers(),
WithManagedFinalizer(ManagedFinalizerFns{AddFinalizerFn: func(_ context.Context, _ Managed) error { return nil }}),
},
},
want: want{result: reconcile.Result{RequeueAfter: defaultManagedLongWait}},

View File

@ -118,15 +118,6 @@ type AttributeReferencer interface {
Assign(res CanReference, value string) error
}
// A ManagedReferenceResolver resolves the references to other managed
// resources, by looking them up in the Kubernetes API server. The references
// are the fields in the managed resource that implement AttributeReferencer
// interface and have
// `attributeReferencerTagName:"managedResourceStructTagPackageName"` tag
type ManagedReferenceResolver interface {
ResolveReferences(context.Context, CanReference) error
}
// An AttributeReferencerFinder returns all types within the supplied object
// that satisfy AttributeReferencer.
type AttributeReferencerFinder interface {