Add a utility to track ProviderConfig usages
Signed-off-by: Nic Cope <negz@rk0n.org>
This commit is contained in:
parent
fe70284573
commit
be1324de71
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue