Implement Target interface and reconciler (#103)
Signed-off-by: hasheddan <georgedanielmangum@gmail.com>
This commit is contained in:
parent
d5c9dedd2a
commit
5ece4af54b
|
@ -36,7 +36,11 @@ const (
|
|||
TypeSynced ConditionType = "Synced"
|
||||
|
||||
// TypeReferencesResolved resources' references are resolved
|
||||
TypeReferencesResolved = "ReferencesResolved"
|
||||
TypeReferencesResolved ConditionType = "ReferencesResolved"
|
||||
|
||||
// TypeSecretPropagated resources have had connection information
|
||||
// propagated to their secret reference.
|
||||
TypeSecretPropagated ConditionType = "ConnectionSecretPropagated"
|
||||
)
|
||||
|
||||
// A ConditionReason represents the reason a resource is in a condition.
|
||||
|
@ -56,12 +60,18 @@ const (
|
|||
ReasonReconcileError ConditionReason = "Encountered an error during resource reconciliation"
|
||||
)
|
||||
|
||||
// Reason references for a resource are or are not resolved
|
||||
// Reason references for a resource are or are not resolved.
|
||||
const (
|
||||
ReasonReferenceResolveSuccess ConditionReason = "Successfully resolved resource references to other resources"
|
||||
ReasonResolveReferencesBlocked ConditionReason = "One or more referenced resources do not exist, or are not yet Ready"
|
||||
)
|
||||
|
||||
// Reason a referenced secret has or has not been propagated to.
|
||||
const (
|
||||
ReasonSecretPropagationSuccess ConditionReason = "Successfully propagated connection data to referenced secret"
|
||||
ReasonSecretPropagationError ConditionReason = "Unable to propagate connection data to referenced secret"
|
||||
)
|
||||
|
||||
// A Condition that may apply to a resource.
|
||||
type Condition struct {
|
||||
// Type of this condition. At most one of each condition type may apply to
|
||||
|
@ -284,3 +294,28 @@ func ReferenceResolutionBlocked(err error) Condition {
|
|||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// SecretPropagationSuccess returns a condition indicating that Crossplane
|
||||
// successfully propagated connection data to the referenced secret.
|
||||
func SecretPropagationSuccess() Condition {
|
||||
return Condition{
|
||||
Type: TypeSecretPropagated,
|
||||
Status: corev1.ConditionTrue,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: ReasonSecretPropagationSuccess,
|
||||
}
|
||||
}
|
||||
|
||||
// SecretPropagationError returns a condition indicating that Crossplane was
|
||||
// unable to propagate connection data to the referenced secret. This could be
|
||||
// because it was unable to find the managed resource that owns the secret to be
|
||||
// propagated.
|
||||
func SecretPropagationError(err error) Condition {
|
||||
return Condition{
|
||||
Type: TypeSecretPropagated,
|
||||
Status: corev1.ConditionFalse,
|
||||
LastTransitionTime: metav1.Now(),
|
||||
Reason: ReasonSecretPropagationError,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,3 +208,26 @@ type ProviderSpec struct {
|
|||
// the credentials that are used to connect to the provider.
|
||||
CredentialsSecretRef SecretKeySelector `json:"credentialsSecretRef"`
|
||||
}
|
||||
|
||||
// A TargetSpec defines the common fields of objects used for exposing
|
||||
// infrastructure to workloads that can be scheduled to.
|
||||
type TargetSpec struct {
|
||||
// WriteConnectionSecretToReference specifies the name of a Secret, in the
|
||||
// same namespace as this target, to which any connection details for this
|
||||
// target should be written or already exist. Connection secrets referenced
|
||||
// by a target should contain information for connecting to a resource that
|
||||
// allows for scheduling of workloads.
|
||||
// +optional
|
||||
WriteConnectionSecretToReference *LocalSecretReference `json:"connectionSecretRef,omitempty"`
|
||||
|
||||
// A ResourceReference specifies an existing managed resource, in any
|
||||
// namespace, which this target should attempt to propagate a connection
|
||||
// secret from.
|
||||
// +optional
|
||||
ResourceReference *corev1.ObjectReference `json:"clusterRef,omitempty"`
|
||||
}
|
||||
|
||||
// A TargetStatus defines the observed status a target.
|
||||
type TargetStatus struct {
|
||||
ConditionedStatus `json:",inline"`
|
||||
}
|
||||
|
|
|
@ -263,3 +263,44 @@ func (in *SecretReference) DeepCopy() *SecretReference {
|
|||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TargetSpec) DeepCopyInto(out *TargetSpec) {
|
||||
*out = *in
|
||||
if in.WriteConnectionSecretToReference != nil {
|
||||
in, out := &in.WriteConnectionSecretToReference, &out.WriteConnectionSecretToReference
|
||||
*out = new(LocalSecretReference)
|
||||
**out = **in
|
||||
}
|
||||
if in.ResourceReference != nil {
|
||||
in, out := &in.ResourceReference, &out.ResourceReference
|
||||
*out = new(corev1.ObjectReference)
|
||||
**out = **in
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetSpec.
|
||||
func (in *TargetSpec) DeepCopy() *TargetSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TargetSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TargetStatus) DeepCopyInto(out *TargetStatus) {
|
||||
*out = *in
|
||||
in.ConditionedStatus.DeepCopyInto(&out.ConditionedStatus)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TargetStatus.
|
||||
func (in *TargetStatus) DeepCopy() *TargetStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TargetStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -91,10 +91,10 @@ func NewAPIManagedConnectionPropagator(c client.Client, t runtime.ObjectTyper) *
|
|||
}
|
||||
|
||||
// PropagateConnection details from the supplied resource to the supplied claim.
|
||||
func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context, cm Claim, mg Managed) error {
|
||||
func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context, tr Target, mg Managed) error {
|
||||
// Either this resource does not expose a connection secret, or this claim
|
||||
// does not want one.
|
||||
if mg.GetWriteConnectionSecretToReference() == nil || cm.GetWriteConnectionSecretToReference() == nil {
|
||||
if mg.GetWriteConnectionSecretToReference() == nil || tr.GetWriteConnectionSecretToReference() == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -115,12 +115,12 @@ func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context
|
|||
return errors.New(errSecretConflict)
|
||||
}
|
||||
|
||||
cmcs := LocalConnectionSecretFor(cm, MustGetKind(cm, a.typer))
|
||||
cmcs := LocalConnectionSecretFor(tr, MustGetKind(tr, a.typer))
|
||||
if _, err := util.CreateOrUpdate(ctx, a.client, cmcs, func() error {
|
||||
// Inside this anonymous function cmcs could either be unchanged (if
|
||||
// it does not exist in the API server) or updated to reflect its
|
||||
// current state according to the API server.
|
||||
if c := metav1.GetControllerOf(cmcs); c == nil || c.UID != cm.GetUID() {
|
||||
if c := metav1.GetControllerOf(cmcs); c == nil || c.UID != tr.GetUID() {
|
||||
return errors.New(errSecretConflict)
|
||||
}
|
||||
cmcs.Data = mgcs.Data
|
||||
|
|
|
@ -107,16 +107,16 @@ func (fn ManagedCreatorFn) Create(ctx context.Context, cm Claim, cs Class, mg Ma
|
|||
// required to connect to a managed resource (for example the connection secret)
|
||||
// from the managed resource to its resource claim.
|
||||
type ManagedConnectionPropagator interface {
|
||||
PropagateConnection(ctx context.Context, cm Claim, mg Managed) error
|
||||
PropagateConnection(ctx context.Context, cm Target, mg Managed) error
|
||||
}
|
||||
|
||||
// A ManagedConnectionPropagatorFn is a function that satisfies the
|
||||
// ManagedConnectionPropagator interface.
|
||||
type ManagedConnectionPropagatorFn func(ctx context.Context, cm Claim, mg Managed) error
|
||||
type ManagedConnectionPropagatorFn func(ctx context.Context, cm Target, mg Managed) error
|
||||
|
||||
// PropagateConnection information from the supplied managed resource to the
|
||||
// supplied resource claim.
|
||||
func (fn ManagedConnectionPropagatorFn) PropagateConnection(ctx context.Context, cm Claim, mg Managed) error {
|
||||
func (fn ManagedConnectionPropagatorFn) PropagateConnection(ctx context.Context, cm Target, mg Managed) error {
|
||||
return fn(ctx, cm, mg)
|
||||
}
|
||||
|
||||
|
|
|
@ -550,7 +550,7 @@ func TestClaimReconciler(t *testing.T) {
|
|||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
||||
func(_ context.Context, _ Claim, _ Managed) error { return errBoom },
|
||||
func(_ context.Context, _ Target, _ Managed) error { return errBoom },
|
||||
)),
|
||||
},
|
||||
},
|
||||
|
@ -594,7 +594,7 @@ func TestClaimReconciler(t *testing.T) {
|
|||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
||||
func(_ context.Context, _ Claim, _ Managed) error { return nil },
|
||||
func(_ context.Context, _ Target, _ Managed) error { return nil },
|
||||
)),
|
||||
WithClaimFinalizer(ClaimFinalizerFns{
|
||||
AddFinalizerFn: func(_ context.Context, _ Claim) error { return errBoom }},
|
||||
|
@ -641,7 +641,7 @@ func TestClaimReconciler(t *testing.T) {
|
|||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
o: []ClaimReconcilerOption{
|
||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
||||
func(_ context.Context, _ Claim, _ Managed) error { return nil },
|
||||
func(_ context.Context, _ Target, _ Managed) error { return nil },
|
||||
)),
|
||||
WithClaimFinalizer(ClaimFinalizerFns{
|
||||
AddFinalizerFn: func(_ context.Context, _ Claim) error { return nil }},
|
||||
|
|
|
@ -133,15 +133,15 @@ func (m *Reclaimer) GetReclaimPolicy() v1alpha1.ReclaimPolicy { return m.Policy
|
|||
|
||||
// CredentialsSecretReferencer is a mock that satisfies CredentialsSecretReferencer
|
||||
// interface.
|
||||
type CredentialsSecretReferencer struct{ Ref *v1alpha1.SecretKeySelector }
|
||||
type CredentialsSecretReferencer struct{ Ref v1alpha1.SecretKeySelector }
|
||||
|
||||
// SetCredentialsSecretReference sets CredentialsSecretReference.
|
||||
func (m *CredentialsSecretReferencer) SetCredentialsSecretReference(r *v1alpha1.SecretKeySelector) {
|
||||
func (m *CredentialsSecretReferencer) SetCredentialsSecretReference(r v1alpha1.SecretKeySelector) {
|
||||
m.Ref = r
|
||||
}
|
||||
|
||||
// GetCredentialsSecretReference gets CredentialsSecretReference.
|
||||
func (m *CredentialsSecretReferencer) GetCredentialsSecretReference() *v1alpha1.SecretKeySelector {
|
||||
func (m *CredentialsSecretReferencer) GetCredentialsSecretReference() v1alpha1.SecretKeySelector {
|
||||
return m.Ref
|
||||
}
|
||||
|
||||
|
@ -243,6 +243,30 @@ func (m *Provider) DeepCopyObject() runtime.Object {
|
|||
return out
|
||||
}
|
||||
|
||||
// Target is a mock that implements Target interface.
|
||||
type Target struct {
|
||||
metav1.ObjectMeta
|
||||
ManagedResourceReferencer
|
||||
LocalConnectionSecretWriterTo
|
||||
v1alpha1.ConditionedStatus
|
||||
}
|
||||
|
||||
// GetObjectKind returns schema.ObjectKind.
|
||||
func (m *Target) GetObjectKind() schema.ObjectKind {
|
||||
return schema.EmptyObjectKind
|
||||
}
|
||||
|
||||
// DeepCopyObject returns a deep copy of Target as runtime.Object.
|
||||
func (m *Target) DeepCopyObject() runtime.Object {
|
||||
out := &Target{}
|
||||
j, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_ = json.Unmarshal(j, out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Manager is a mock object that satisfies manager.Manager interface.
|
||||
type Manager struct {
|
||||
manager.Manager
|
||||
|
|
|
@ -137,3 +137,15 @@ type Provider interface {
|
|||
|
||||
CredentialsSecretReferencer
|
||||
}
|
||||
|
||||
// A Target is a Kubernetes object that refers to credentials to connect
|
||||
// to a deployment target. Target is a subset of the Claim interface.
|
||||
type Target interface {
|
||||
runtime.Object
|
||||
metav1.Object
|
||||
|
||||
LocalConnectionSecretWriterTo
|
||||
ManagedResourceReferencer
|
||||
|
||||
Conditioned
|
||||
}
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
Copyright 2019 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"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/logging"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/meta"
|
||||
)
|
||||
|
||||
const (
|
||||
targetControllerName = "kubernetestarget.crossplane.io"
|
||||
targetReconcileTimeout = 1 * time.Minute
|
||||
|
||||
errGetTarget = "unable to get Target"
|
||||
errManagedResourceIsNotBound = "managed resource in Target clusterRef is unbound"
|
||||
errUpdateTarget = "unable to update Target"
|
||||
)
|
||||
|
||||
// A TargetKind contains the type metadata for a kind of target resource.
|
||||
type TargetKind schema.GroupVersionKind
|
||||
|
||||
// A TargetReconciler reconciles targets by propagating the secret of the
|
||||
// referenced managed resource.
|
||||
type TargetReconciler struct {
|
||||
client client.Client
|
||||
newTarget func() Target
|
||||
newManaged func() Managed
|
||||
|
||||
propagator ManagedConnectionPropagator
|
||||
}
|
||||
|
||||
// NewTargetReconciler returns a Reconciler that reconciles KubernetesTargets by
|
||||
// propagating the referenced Kubernetes cluster's connection Secret to the
|
||||
// namespace of the KubernetesTarget.
|
||||
func NewTargetReconciler(m manager.Manager, of TargetKind, with ManagedKind) *TargetReconciler {
|
||||
nt := func() Target { return MustCreateObject(schema.GroupVersionKind(of), m.GetScheme()).(Target) }
|
||||
nr := func() Managed { return MustCreateObject(schema.GroupVersionKind(with), m.GetScheme()).(Managed) }
|
||||
|
||||
// Panic early if we've been asked to reconcile a target or resource kind
|
||||
// that has not been registered with our controller manager's scheme.
|
||||
_, _ = nt(), nr()
|
||||
|
||||
r := &TargetReconciler{
|
||||
client: m.GetClient(),
|
||||
newTarget: nt,
|
||||
newManaged: nr,
|
||||
propagator: NewAPIManagedConnectionPropagator(m.GetClient(), m.GetScheme()),
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// Reconcile a target with a concrete managed resource.
|
||||
func (r *TargetReconciler) Reconcile(req reconcile.Request) (reconcile.Result, error) {
|
||||
log.V(logging.Debug).Info("Reconciling", "controller", targetControllerName, "request", req)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), targetReconcileTimeout)
|
||||
defer cancel()
|
||||
|
||||
target := r.newTarget()
|
||||
if err := r.client.Get(ctx, req.NamespacedName, target); err != nil {
|
||||
// There's no need to requeue if we no longer exist. Otherwise we'll be
|
||||
// requeued implicitly because we return an error.
|
||||
return reconcile.Result{}, errors.Wrap(IgnoreNotFound(err), errGetTarget)
|
||||
}
|
||||
|
||||
if target.GetWriteConnectionSecretToReference() == nil {
|
||||
// If the ConnectionSecretRef is not set on this Target, we generate a
|
||||
// Secret name that matches the UID of the Target. We are implicitly
|
||||
// requeued because of the Target update.
|
||||
target.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{Name: string(target.GetUID())})
|
||||
return reconcile.Result{}, errors.Wrap(r.client.Update(ctx, target), errUpdateTarget)
|
||||
}
|
||||
|
||||
if meta.WasDeleted(target) {
|
||||
// If the Target was deleted, there is nothing left for us to do.
|
||||
return reconcile.Result{Requeue: false}, nil
|
||||
}
|
||||
|
||||
managed := r.newManaged()
|
||||
if err := r.client.Get(ctx, meta.NamespacedNameOf(target.GetResourceReference()), managed); err != nil {
|
||||
target.SetConditions(v1alpha1.SecretPropagationError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, target), errUpdateTarget)
|
||||
}
|
||||
|
||||
if !IsBound(managed) {
|
||||
target.SetConditions(v1alpha1.SecretPropagationError(errors.New(errManagedResourceIsNotBound)))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, target), errUpdateTarget)
|
||||
}
|
||||
|
||||
if err := r.propagator.PropagateConnection(ctx, target, managed); err != nil {
|
||||
// If we fail to propagate the connection secret of a bound managed resource, we try again after a short wait.
|
||||
target.SetConditions(v1alpha1.SecretPropagationError(err))
|
||||
return reconcile.Result{RequeueAfter: aShortWait}, errors.Wrap(r.client.Status().Update(ctx, target), errUpdateTarget)
|
||||
}
|
||||
|
||||
// No need to requeue.
|
||||
return reconcile.Result{Requeue: false}, nil
|
||||
}
|
|
@ -0,0 +1,540 @@
|
|||
/*
|
||||
Copyright 2019 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"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
|
||||
"github.com/crossplaneio/crossplane-runtime/apis/core/v1alpha1"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/resource/fake"
|
||||
"github.com/crossplaneio/crossplane-runtime/pkg/test"
|
||||
)
|
||||
|
||||
func TestTargetReconciler(t *testing.T) {
|
||||
type args struct {
|
||||
m manager.Manager
|
||||
of TargetKind
|
||||
with ManagedKind
|
||||
}
|
||||
|
||||
type want struct {
|
||||
result reconcile.Result
|
||||
err error
|
||||
}
|
||||
|
||||
now := metav1.Now()
|
||||
ns := "namespace"
|
||||
tgname := "cooltarget"
|
||||
mgname := "coolmanaged"
|
||||
tguid := types.UID("tg-uuid")
|
||||
mguid := types.UID("mg-uuid")
|
||||
tgcsuid := types.UID("tgcs-uuid")
|
||||
mgcsuid := types.UID("mgcs-uuid")
|
||||
tgcsname := "cooltargetsecret"
|
||||
mgcsname := "coolmanagedsecret"
|
||||
mgcsnamespace := "coolns"
|
||||
mgcsdata := map[string][]byte{"cool": []byte("data")}
|
||||
controller := true
|
||||
|
||||
errBoom := errors.New("boom")
|
||||
errUnexpectedSecret := errors.New("unexpected secret name")
|
||||
errUnexpected := errors.New("unexpected object type")
|
||||
|
||||
cases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"ErrorGetTarget": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
*o = fake.Target{}
|
||||
return errBoom
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{},
|
||||
err: errors.Wrap(errBoom, errGetTarget),
|
||||
},
|
||||
},
|
||||
"SuccessTargetHasNoSecretRef": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
tg := &fake.Target{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: tguid,
|
||||
Name: tgname,
|
||||
Namespace: ns,
|
||||
}}
|
||||
tg.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
*o = *tg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Target{}
|
||||
want.SetName(tgname)
|
||||
want.SetNamespace(ns)
|
||||
want.SetUID(tguid)
|
||||
want.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
want.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{
|
||||
Name: string(tguid),
|
||||
})
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{},
|
||||
},
|
||||
},
|
||||
"TargetWasDeleted": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
tg := &fake.Target{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: tguid,
|
||||
Name: tgname,
|
||||
Namespace: ns,
|
||||
}}
|
||||
tg.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
tg.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{
|
||||
Name: tgcsname,
|
||||
})
|
||||
tg.SetDeletionTimestamp(&now)
|
||||
*o = *tg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{Requeue: false},
|
||||
},
|
||||
},
|
||||
"TargetNotFound": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
*o = fake.Target{}
|
||||
return kerrors.NewNotFound(schema.GroupResource{}, "")
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{},
|
||||
},
|
||||
},
|
||||
"ErrorGetManaged": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
tg := &fake.Target{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: tguid,
|
||||
Name: tgname,
|
||||
Namespace: ns,
|
||||
}}
|
||||
tg.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
tg.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{
|
||||
Name: tgcsname,
|
||||
})
|
||||
*o = *tg
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
*o = fake.Managed{}
|
||||
return errBoom
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Target{}
|
||||
want.SetName(tgname)
|
||||
want.SetNamespace(ns)
|
||||
want.SetUID(tguid)
|
||||
want.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
want.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{Name: tgcsname})
|
||||
want.SetConditions(v1alpha1.SecretPropagationError(errBoom))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{RequeueAfter: aShortWait},
|
||||
},
|
||||
},
|
||||
"ErrorManagedNotBound": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
tg := &fake.Target{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: tguid,
|
||||
Name: tgname,
|
||||
Namespace: ns,
|
||||
}}
|
||||
tg.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
tg.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{
|
||||
Name: tgcsname,
|
||||
})
|
||||
*o = *tg
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: mguid,
|
||||
Name: mgname,
|
||||
}}
|
||||
mg.SetWriteConnectionSecretToReference(&v1alpha1.SecretReference{
|
||||
Name: mgcsname,
|
||||
Namespace: mgcsnamespace,
|
||||
})
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseUnbound)
|
||||
*o = *mg
|
||||
return nil
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Target{}
|
||||
want.SetName(tgname)
|
||||
want.SetNamespace(ns)
|
||||
want.SetUID(tguid)
|
||||
want.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
want.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{Name: tgcsname})
|
||||
want.SetConditions(v1alpha1.SecretPropagationError(errors.New(errManagedResourceIsNotBound)))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{RequeueAfter: aShortWait},
|
||||
},
|
||||
},
|
||||
"ErrorSecretPropagationFailed": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
tg := &fake.Target{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: tguid,
|
||||
Name: tgname,
|
||||
Namespace: ns,
|
||||
}}
|
||||
tg.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
tg.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{
|
||||
Name: tgcsname,
|
||||
})
|
||||
*o = *tg
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: mguid,
|
||||
Name: mgname,
|
||||
}}
|
||||
mg.SetWriteConnectionSecretToReference(&v1alpha1.SecretReference{
|
||||
Name: mgcsname,
|
||||
Namespace: mgcsnamespace,
|
||||
})
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
*o = *mg
|
||||
return nil
|
||||
case *corev1.Secret:
|
||||
switch n.Name {
|
||||
case tgcsname:
|
||||
sc := &corev1.Secret{}
|
||||
sc.SetName(tgcsname)
|
||||
sc.SetNamespace(ns)
|
||||
sc.SetUID(tgcsuid)
|
||||
sc.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
UID: tguid,
|
||||
Controller: &controller,
|
||||
}})
|
||||
*o = *sc
|
||||
return nil
|
||||
case mgcsname:
|
||||
sc := &corev1.Secret{}
|
||||
sc.SetName(mgcsname)
|
||||
sc.SetNamespace(mgcsnamespace)
|
||||
sc.SetUID(mgcsuid)
|
||||
sc.Data = mgcsdata
|
||||
*o = *sc
|
||||
return nil
|
||||
default:
|
||||
return errUnexpectedSecret
|
||||
}
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Target{}
|
||||
want.SetName(tgname)
|
||||
want.SetNamespace(ns)
|
||||
want.SetUID(tguid)
|
||||
want.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
want.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{Name: tgcsname})
|
||||
want.SetConditions(v1alpha1.SecretPropagationError(errors.New(errSecretConflict)))
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{RequeueAfter: aShortWait},
|
||||
},
|
||||
},
|
||||
"Successful": {
|
||||
args: args{
|
||||
m: &fake.Manager{
|
||||
Client: &test.MockClient{
|
||||
MockGet: func(_ context.Context, n types.NamespacedName, o runtime.Object) error {
|
||||
switch o := o.(type) {
|
||||
case *fake.Target:
|
||||
tg := &fake.Target{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: tguid,
|
||||
Name: tgname,
|
||||
Namespace: ns,
|
||||
}}
|
||||
tg.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
tg.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{
|
||||
Name: tgcsname,
|
||||
})
|
||||
*o = *tg
|
||||
return nil
|
||||
case *fake.Managed:
|
||||
mg := &fake.Managed{ObjectMeta: metav1.ObjectMeta{
|
||||
UID: mguid,
|
||||
Name: mgname,
|
||||
}}
|
||||
mg.SetWriteConnectionSecretToReference(&v1alpha1.SecretReference{
|
||||
Name: mgcsname,
|
||||
Namespace: mgcsnamespace,
|
||||
})
|
||||
mg.SetBindingPhase(v1alpha1.BindingPhaseBound)
|
||||
*o = *mg
|
||||
return nil
|
||||
case *corev1.Secret:
|
||||
switch n.Name {
|
||||
case tgcsname:
|
||||
sc := &corev1.Secret{}
|
||||
sc.SetName(tgcsname)
|
||||
sc.SetNamespace(ns)
|
||||
sc.SetUID(tgcsuid)
|
||||
sc.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
UID: tguid,
|
||||
Controller: &controller,
|
||||
}})
|
||||
*o = *sc
|
||||
return nil
|
||||
case mgcsname:
|
||||
sc := &corev1.Secret{}
|
||||
sc.SetName(mgcsname)
|
||||
sc.SetNamespace(mgcsnamespace)
|
||||
sc.SetUID(mgcsuid)
|
||||
sc.SetOwnerReferences([]metav1.OwnerReference{{
|
||||
UID: mguid,
|
||||
Controller: &controller,
|
||||
}})
|
||||
sc.Data = mgcsdata
|
||||
*o = *sc
|
||||
return nil
|
||||
default:
|
||||
return errUnexpectedSecret
|
||||
}
|
||||
default:
|
||||
return errUnexpected
|
||||
}
|
||||
},
|
||||
MockUpdate: test.NewMockUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &corev1.Secret{}
|
||||
want.Data = mgcsdata
|
||||
|
||||
switch got.(metav1.Object).GetName() {
|
||||
case tgcsname:
|
||||
want.SetName(tgcsname)
|
||||
want.SetNamespace(ns)
|
||||
want.SetUID(tgcsuid)
|
||||
want.SetOwnerReferences([]metav1.OwnerReference{{UID: tguid, Controller: &controller}})
|
||||
want.SetAnnotations(map[string]string{
|
||||
AnnotationKeyPropagateFromNamespace: mgcsnamespace,
|
||||
AnnotationKeyPropagateFromName: mgcsname,
|
||||
AnnotationKeyPropagateFromUID: string(mgcsuid),
|
||||
})
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
case mgcsname:
|
||||
want.SetName(mgcsname)
|
||||
want.SetNamespace(mgcsnamespace)
|
||||
want.SetUID(mgcsuid)
|
||||
want.SetOwnerReferences([]metav1.OwnerReference{{UID: mguid, Controller: &controller}})
|
||||
want.SetAnnotations(map[string]string{
|
||||
strings.Join([]string{AnnotationKeyPropagateToPrefix, string(tgcsuid)}, SlashDelimeter): strings.Join([]string{ns, tgcsname}, SlashDelimeter),
|
||||
})
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
default:
|
||||
return errUnexpectedSecret
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
MockStatusUpdate: test.NewMockStatusUpdateFn(nil, func(got runtime.Object) error {
|
||||
want := &fake.Target{}
|
||||
want.SetName(tgname)
|
||||
want.SetNamespace(ns)
|
||||
want.SetUID(tguid)
|
||||
want.SetResourceReference(&corev1.ObjectReference{
|
||||
Name: mgname,
|
||||
})
|
||||
want.SetWriteConnectionSecretToReference(&v1alpha1.LocalSecretReference{Name: tgcsname})
|
||||
want.SetConditions(v1alpha1.SecretPropagationSuccess())
|
||||
if diff := cmp.Diff(want, got, test.EquateConditions()); diff != "" {
|
||||
t.Errorf("-want, +got:\n%s", diff)
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
Scheme: fake.SchemeWith(&fake.Target{}, &fake.Managed{}),
|
||||
},
|
||||
of: TargetKind(fake.GVK(&fake.Target{})),
|
||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||
},
|
||||
want: want{
|
||||
result: reconcile.Result{Requeue: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
r := NewTargetReconciler(tc.args.m, tc.args.of, tc.args.with)
|
||||
got, err := r.Reconcile(reconcile.Request{})
|
||||
|
||||
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want error, +got error:\n%s", diff)
|
||||
}
|
||||
|
||||
if diff := cmp.Diff(tc.want.result, got); diff != "" {
|
||||
t.Errorf("r.Reconcile(...): -want, +got:\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue