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"
|
TypeSynced ConditionType = "Synced"
|
||||||
|
|
||||||
// TypeReferencesResolved resources' references are resolved
|
// 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.
|
// A ConditionReason represents the reason a resource is in a condition.
|
||||||
|
@ -56,12 +60,18 @@ const (
|
||||||
ReasonReconcileError ConditionReason = "Encountered an error during resource reconciliation"
|
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 (
|
const (
|
||||||
ReasonReferenceResolveSuccess ConditionReason = "Successfully resolved resource references to other resources"
|
ReasonReferenceResolveSuccess ConditionReason = "Successfully resolved resource references to other resources"
|
||||||
ReasonResolveReferencesBlocked ConditionReason = "One or more referenced resources do not exist, or are not yet Ready"
|
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.
|
// A Condition that may apply to a resource.
|
||||||
type Condition struct {
|
type Condition struct {
|
||||||
// Type of this condition. At most one of each condition type may apply to
|
// 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(),
|
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.
|
// the credentials that are used to connect to the provider.
|
||||||
CredentialsSecretRef SecretKeySelector `json:"credentialsSecretRef"`
|
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)
|
in.DeepCopyInto(out)
|
||||||
return 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.
|
// 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
|
// Either this resource does not expose a connection secret, or this claim
|
||||||
// does not want one.
|
// does not want one.
|
||||||
if mg.GetWriteConnectionSecretToReference() == nil || cm.GetWriteConnectionSecretToReference() == nil {
|
if mg.GetWriteConnectionSecretToReference() == nil || tr.GetWriteConnectionSecretToReference() == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,12 +115,12 @@ func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context
|
||||||
return errors.New(errSecretConflict)
|
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 {
|
if _, err := util.CreateOrUpdate(ctx, a.client, cmcs, func() error {
|
||||||
// Inside this anonymous function cmcs could either be unchanged (if
|
// Inside this anonymous function cmcs could either be unchanged (if
|
||||||
// it does not exist in the API server) or updated to reflect its
|
// it does not exist in the API server) or updated to reflect its
|
||||||
// current state according to the API server.
|
// 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)
|
return errors.New(errSecretConflict)
|
||||||
}
|
}
|
||||||
cmcs.Data = mgcs.Data
|
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)
|
// required to connect to a managed resource (for example the connection secret)
|
||||||
// from the managed resource to its resource claim.
|
// from the managed resource to its resource claim.
|
||||||
type ManagedConnectionPropagator interface {
|
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
|
// A ManagedConnectionPropagatorFn is a function that satisfies the
|
||||||
// ManagedConnectionPropagator interface.
|
// 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
|
// PropagateConnection information from the supplied managed resource to the
|
||||||
// supplied resource claim.
|
// 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)
|
return fn(ctx, cm, mg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -550,7 +550,7 @@ func TestClaimReconciler(t *testing.T) {
|
||||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||||
o: []ClaimReconcilerOption{
|
o: []ClaimReconcilerOption{
|
||||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
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{})),
|
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||||
o: []ClaimReconcilerOption{
|
o: []ClaimReconcilerOption{
|
||||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
||||||
func(_ context.Context, _ Claim, _ Managed) error { return nil },
|
func(_ context.Context, _ Target, _ Managed) error { return nil },
|
||||||
)),
|
)),
|
||||||
WithClaimFinalizer(ClaimFinalizerFns{
|
WithClaimFinalizer(ClaimFinalizerFns{
|
||||||
AddFinalizerFn: func(_ context.Context, _ Claim) error { return errBoom }},
|
AddFinalizerFn: func(_ context.Context, _ Claim) error { return errBoom }},
|
||||||
|
@ -641,7 +641,7 @@ func TestClaimReconciler(t *testing.T) {
|
||||||
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
with: ManagedKind(fake.GVK(&fake.Managed{})),
|
||||||
o: []ClaimReconcilerOption{
|
o: []ClaimReconcilerOption{
|
||||||
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
WithManagedConnectionPropagator(ManagedConnectionPropagatorFn(
|
||||||
func(_ context.Context, _ Claim, _ Managed) error { return nil },
|
func(_ context.Context, _ Target, _ Managed) error { return nil },
|
||||||
)),
|
)),
|
||||||
WithClaimFinalizer(ClaimFinalizerFns{
|
WithClaimFinalizer(ClaimFinalizerFns{
|
||||||
AddFinalizerFn: func(_ context.Context, _ Claim) error { return nil }},
|
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
|
// CredentialsSecretReferencer is a mock that satisfies CredentialsSecretReferencer
|
||||||
// interface.
|
// interface.
|
||||||
type CredentialsSecretReferencer struct{ Ref *v1alpha1.SecretKeySelector }
|
type CredentialsSecretReferencer struct{ Ref v1alpha1.SecretKeySelector }
|
||||||
|
|
||||||
// SetCredentialsSecretReference sets CredentialsSecretReference.
|
// SetCredentialsSecretReference sets CredentialsSecretReference.
|
||||||
func (m *CredentialsSecretReferencer) SetCredentialsSecretReference(r *v1alpha1.SecretKeySelector) {
|
func (m *CredentialsSecretReferencer) SetCredentialsSecretReference(r v1alpha1.SecretKeySelector) {
|
||||||
m.Ref = r
|
m.Ref = r
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCredentialsSecretReference gets CredentialsSecretReference.
|
// GetCredentialsSecretReference gets CredentialsSecretReference.
|
||||||
func (m *CredentialsSecretReferencer) GetCredentialsSecretReference() *v1alpha1.SecretKeySelector {
|
func (m *CredentialsSecretReferencer) GetCredentialsSecretReference() v1alpha1.SecretKeySelector {
|
||||||
return m.Ref
|
return m.Ref
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,6 +243,30 @@ func (m *Provider) DeepCopyObject() runtime.Object {
|
||||||
return out
|
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.
|
// Manager is a mock object that satisfies manager.Manager interface.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
manager.Manager
|
manager.Manager
|
||||||
|
|
|
@ -137,3 +137,15 @@ type Provider interface {
|
||||||
|
|
||||||
CredentialsSecretReferencer
|
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