Add a utility to track ProviderConfig usages

Signed-off-by: Nic Cope <negz@rk0n.org>
This commit is contained in:
Nic Cope 2020-10-01 02:02:47 -07:00
parent fe70284573
commit be1324de71
8 changed files with 245 additions and 13 deletions

View File

@ -183,7 +183,7 @@ type ProviderConfigSpec struct {
// A ProviderConfigStatus defines the observed status of a ProviderConfig.
type ProviderConfigStatus struct {
// Users of this provider configuration.
Users *int64 `json:"users,omitempty"`
Users int64 `json:"users,omitempty"`
}
// A ProviderConfigUsage is a record that a particular managed resource is using

View File

@ -100,11 +100,6 @@ func (in *ProviderConfigSpec) DeepCopy() *ProviderConfigSpec {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ProviderConfigStatus) DeepCopyInto(out *ProviderConfigStatus) {
*out = *in
if in.Users != nil {
in, out := &in.Users, &out.Users
*out = new(int64)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ProviderConfigStatus.

View File

@ -172,7 +172,7 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error)
r.record.Event(pc, event.Warning(reasonAccount, errors.New("Blocking deletion while usages still exist")))
// We're watching our usages, so we'll be requeued when they go.
pc.SetUsers(&users)
pc.SetUsers(users)
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, pc), errUpdateStatus)
}
@ -193,6 +193,6 @@ func (r *Reconciler) Reconcile(req reconcile.Request) (reconcile.Result, error)
}
// There's no need to requeue explicitly - we're watching all PCs.
pc.SetUsers(&users)
pc.SetUsers(users)
return reconcile.Result{Requeue: false}, errors.Wrap(r.client.Status().Update(ctx, pc), errUpdateStatus)
}

View File

@ -94,6 +94,20 @@ func (m *RequiredProviderConfigReferencer) GetProviderConfigReference() v1alpha1
return m.Ref
}
// RequiredTypedResourceReferencer is a mock that implements the
// RequiredTypedResourceReferencer interface.
type RequiredTypedResourceReferencer struct{ Ref v1alpha1.TypedReference }
// SetResourceReference sets the ResourceReference.
func (m *RequiredTypedResourceReferencer) SetResourceReference(p v1alpha1.TypedReference) {
m.Ref = p
}
// GetResourceReference gets the ResourceReference.
func (m *RequiredTypedResourceReferencer) GetResourceReference() v1alpha1.TypedReference {
return m.Ref
}
// LocalConnectionSecretWriterTo is a mock that implements LocalConnectionSecretWriterTo interface.
type LocalConnectionSecretWriterTo struct {
Ref *v1alpha1.LocalSecretReference
@ -183,15 +197,15 @@ func (m *ComposedResourcesReferencer) GetResourceReferences() []corev1.ObjectRef
// UserCounter is a mock that satisfies UserCounter
// interface.
type UserCounter struct{ Users *int64 }
type UserCounter struct{ Users int64 }
// SetUsers sets the count of users.
func (m *UserCounter) SetUsers(i *int64) {
func (m *UserCounter) SetUsers(i int64) {
m.Users = i
}
// GetUsers gets the count of users.
func (m *UserCounter) GetUsers() *int64 {
func (m *UserCounter) GetUsers() int64 {
return m.Users
}
@ -488,6 +502,7 @@ type ProviderConfigUsage struct {
metav1.ObjectMeta
RequiredProviderConfigReferencer
RequiredTypedResourceReferencer
}
// GetObjectKind returns schema.ObjectKind.

View File

@ -91,6 +91,12 @@ type RequiredProviderConfigReferencer interface {
SetProviderConfigReference(p v1alpha1.Reference)
}
// A RequiredTypedResourceReferencer can reference a resource.
type RequiredTypedResourceReferencer interface {
SetResourceReference(r v1alpha1.TypedReference)
GetResourceReference() v1alpha1.TypedReference
}
// A Finalizer manages the finalizers on the resource.
type Finalizer interface {
AddFinalizer(ctx context.Context, obj Object) error
@ -123,8 +129,8 @@ type CompositeResourceReferencer interface {
// A UserCounter can count how many users it has.
type UserCounter interface {
SetUsers(i *int64)
GetUsers() *int64
SetUsers(i int64)
GetUsers() int64
}
// An Object is a Kubernetes object.
@ -168,6 +174,7 @@ type ProviderConfigUsage interface {
Object
RequiredProviderConfigReferencer
RequiredTypedResourceReferencer
}
// A ProviderConfigUsageList is a list of provider config usages.

View File

@ -0,0 +1,91 @@
/*
Copyright 2020 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resource
import (
"context"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/meta"
)
const (
errMissingPCRef = "managed resource does not reference a ProviderConfig"
errApplyPCU = "cannot apply ProviderConfigUsage"
)
type errMissingRef struct{ error }
func (m errMissingRef) MissingReference() bool { return true }
// IsMissingReference returns true if an error indicates that a managed
// resource is missing a required reference..
func IsMissingReference(err error) bool {
_, ok := err.(interface {
MissingReference() bool
})
return ok
}
// A UsageTracker tracks usages of a ProviderConfig by creating or updating the
// appropriate ProviderConfigUsage.
type UsageTracker struct {
c Applicator
of ProviderConfigUsage
}
// NewUsageTracker creates a UsageTracker.
func NewUsageTracker(c client.Client, of ProviderConfigUsage) *UsageTracker {
return &UsageTracker{c: NewAPIUpdatingApplicator(c), of: of}
}
// Track that the supplied Managed resource is using the ProviderConfig it
// references by creating or updating a ProviderConfigUsage. Track should be
// called _before_ attempting to use the ProviderConfig. This ensures the
// managed resource's usage is updated if the managed resource is updated to
// reference a misconfigured ProviderConfig.
func (u *UsageTracker) Track(ctx context.Context, mg Managed) error {
pcu := u.of.DeepCopyObject().(ProviderConfigUsage)
gvk := mg.GetObjectKind().GroupVersionKind()
ref := mg.GetProviderConfigReference()
if ref == nil {
return errMissingRef{errors.New(errMissingPCRef)}
}
pcu.SetName(string(mg.GetUID()))
pcu.SetLabels(map[string]string{v1alpha1.LabelKeyProviderName: ref.Name})
pcu.SetOwnerReferences([]metav1.OwnerReference{meta.AsController(meta.TypedReferenceTo(mg, gvk))})
pcu.SetProviderConfigReference(v1alpha1.Reference{Name: ref.Name})
pcu.SetResourceReference(v1alpha1.TypedReference{
APIVersion: gvk.GroupVersion().String(),
Kind: gvk.Kind,
Name: mg.GetName(),
})
err := u.c.Apply(ctx, pcu,
MustBeControllableBy(mg.GetUID()),
AllowUpdateIf(func(current, _ runtime.Object) bool {
return current.(ProviderConfigUsage).GetProviderConfigReference() != pcu.GetProviderConfigReference()
}),
)
return errors.Wrap(Ignore(IsNotAllowed, err), errApplyPCU)
}

View File

@ -0,0 +1,123 @@
/*
Copyright 2020 The Crossplane Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package resource
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/runtime"
"github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/resource/fake"
"github.com/crossplane/crossplane-runtime/pkg/test"
)
func TestTrack(t *testing.T) {
errBoom := errors.New("boom")
name := "provisional"
type fields struct {
c Applicator
of ProviderConfigUsage
}
type args struct {
ctx context.Context
mg Managed
}
cases := map[string]struct {
reason string
fields fields
args args
want error
}{
"MissingRef": {
reason: "An error that satisfies IsMissingReference should be returned if the managed resource has no provider config reference",
fields: fields{
of: &fake.ProviderConfigUsage{},
},
args: args{
mg: &fake.Managed{},
},
want: errMissingRef{errors.New(errMissingPCRef)},
},
"NopUpdate": {
reason: "No error should be returned if the apply fails because it would be a no-op",
fields: fields{
c: ApplyFn(func(c context.Context, r runtime.Object, ao ...ApplyOption) error {
for _, fn := range ao {
// Exercise the MustBeControllableBy and AllowUpdateIf
// ApplyOptions. The former should pass because the
// current object has no controller ref. The latter
// should return an error that satisfies IsNotAllowed
// because the current object has the same PC ref as the
// new one we would apply.
current := &fake.ProviderConfigUsage{
RequiredProviderConfigReferencer: fake.RequiredProviderConfigReferencer{
Ref: v1alpha1.Reference{Name: name},
},
}
if err := fn(context.TODO(), current, nil); err != nil {
return err
}
}
return errBoom
}),
of: &fake.ProviderConfigUsage{},
},
args: args{
mg: &fake.Managed{
ProviderConfigReferencer: fake.ProviderConfigReferencer{
Ref: &v1alpha1.Reference{Name: name},
},
},
},
want: nil,
},
"ApplyError": {
reason: "Errors applying the ProviderConfigUsage should be returned",
fields: fields{
c: ApplyFn(func(c context.Context, r runtime.Object, ao ...ApplyOption) error {
return errBoom
}),
of: &fake.ProviderConfigUsage{},
},
args: args{
mg: &fake.Managed{
ProviderConfigReferencer: fake.ProviderConfigReferencer{
Ref: &v1alpha1.Reference{Name: name},
},
},
},
want: errors.Wrap(errBoom, errApplyPCU),
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
ut := &UsageTracker{c: tc.fields.c, of: tc.fields.of}
got := ut.Track(tc.args.ctx, tc.args.mg)
if diff := cmp.Diff(tc.want, got, test.EquateErrors()); diff != "" {
t.Errorf("\n%s\nut.Track(...): -want error, +got error:\n%s\n", tc.reason, diff)
}
})
}
}

View File

@ -60,6 +60,7 @@ type CompositeClaimKind schema.GroupVersionKind
// ProviderConfigKinds contains the type metadata for a kind of provider config.
type ProviderConfigKinds struct {
Config schema.GroupVersionKind
Usage schema.GroupVersionKind
UsageList schema.GroupVersionKind
}