From cf744159e5f3a7f08fc325e4844f45f02430d4cd Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 5 Nov 2020 04:12:20 +0000 Subject: [PATCH 1/2] Allow XRs and XRCs to record when they last published connection details This information is useful both for debugging XRs and XRCs, and for controllers to watch for updates to connection details (i.e. if those details were stored in an external system rather than in Secrets, which can themselves be watched). Signed-off-by: Nic Cope --- pkg/resource/fake/mocks.go | 18 +++++++++ pkg/resource/interfaces.go | 9 +++++ pkg/resource/unstructured/claim/claim.go | 14 +++++++ pkg/resource/unstructured/claim/claim_test.go | 37 +++++++++++++++++++ .../unstructured/composite/composite.go | 14 +++++++ .../unstructured/composite/composite_test.go | 37 +++++++++++++++++++ 6 files changed, 129 insertions(+) diff --git a/pkg/resource/fake/mocks.go b/pkg/resource/fake/mocks.go index e853fb007..da84cb904 100644 --- a/pkg/resource/fake/mocks.go +++ b/pkg/resource/fake/mocks.go @@ -181,6 +181,20 @@ func (m *ComposedResourcesReferencer) SetResourceReferences(r []corev1.ObjectRef // GetResourceReferences gets the composed references. func (m *ComposedResourcesReferencer) GetResourceReferences() []corev1.ObjectReference { return m.Refs } +// ConnectionDetailsLastPublishedTimer is a mock that implements the +// ConnectionDetailsLastPublishedTimer interface. +type ConnectionDetailsLastPublishedTimer struct{ Time *metav1.Time } + +// SetConnectionDetailsLastPublishedTime sets the published time. +func (c *ConnectionDetailsLastPublishedTimer) SetConnectionDetailsLastPublishedTime(t *metav1.Time) { + c.Time = t +} + +// GetConnectionDetailsLastPublishedTime gets the published time. +func (c *ConnectionDetailsLastPublishedTimer) GetConnectionDetailsLastPublishedTime() *metav1.Time { + return c.Time +} + // UserCounter is a mock that satisfies UserCounter // interface. type UserCounter struct{ Users int64 } @@ -251,7 +265,9 @@ type Composite struct { ComposedResourcesReferencer ClaimReferencer ConnectionSecretWriterTo + v1alpha1.ConditionedStatus + ConnectionDetailsLastPublishedTimer } // GetObjectKind returns schema.ObjectKind. @@ -300,7 +316,9 @@ type CompositeClaim struct { CompositionReferencer CompositeResourceReferencer LocalConnectionSecretWriterTo + v1alpha1.ConditionedStatus + ConnectionDetailsLastPublishedTimer } // GetObjectKind returns schema.ObjectKind. diff --git a/pkg/resource/interfaces.go b/pkg/resource/interfaces.go index 71846ad54..169d33d93 100644 --- a/pkg/resource/interfaces.go +++ b/pkg/resource/interfaces.go @@ -126,6 +126,13 @@ type UserCounter interface { GetUsers() int64 } +// A ConnectionDetailsPublishedTimer can record the last time its connection +// details were published. +type ConnectionDetailsPublishedTimer interface { + SetConnectionDetailsLastPublishedTime(t *metav1.Time) + GetConnectionDetailsLastPublishedTime() *metav1.Time +} + // An Object is a Kubernetes object. type Object interface { metav1.Object @@ -188,6 +195,7 @@ type Composite interface { ConnectionSecretWriterTo Conditioned + ConnectionDetailsPublishedTimer } // Composed resources can be a composed into a Composite resource. @@ -208,4 +216,5 @@ type CompositeClaim interface { LocalConnectionSecretWriterTo Conditioned + ConnectionDetailsPublishedTimer } diff --git a/pkg/resource/unstructured/claim/claim.go b/pkg/resource/unstructured/claim/claim.go index 80fa74078..8fc8df983 100644 --- a/pkg/resource/unstructured/claim/claim.go +++ b/pkg/resource/unstructured/claim/claim.go @@ -139,3 +139,17 @@ func (c *Unstructured) SetConditions(conditions ...v1alpha1.Condition) { conditioned.SetConditions(conditions...) _ = fieldpath.Pave(c.Object).SetValue("status.conditions", conditioned.Conditions) } + +// GetConnectionDetailsLastPublishedTime of this composite resource claim. +func (c *Unstructured) GetConnectionDetailsLastPublishedTime() *metav1.Time { + out := &metav1.Time{} + if err := fieldpath.Pave(c.Object).GetValueInto("status.connectionDetails.lastPublishedTime", out); err != nil { + return nil + } + return out +} + +// SetConnectionDetailsLastPublishedTime of this composite resource claim. +func (c *Unstructured) SetConnectionDetailsLastPublishedTime(t *metav1.Time) { + _ = fieldpath.Pave(c.Object).SetValue("status.connectionDetails.lastPublishedTime", t) +} diff --git a/pkg/resource/unstructured/claim/claim_test.go b/pkg/resource/unstructured/claim/claim_test.go index d80138102..2db0a56fc 100644 --- a/pkg/resource/unstructured/claim/claim_test.go +++ b/pkg/resource/unstructured/claim/claim_test.go @@ -17,7 +17,9 @@ limitations under the License. package claim import ( + "encoding/json" "testing" + "time" "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" @@ -213,3 +215,38 @@ func TestWriteConnectionSecretToReference(t *testing.T) { }) } } + +func TestConnectionDetailsLastPublishedTime(t *testing.T) { + now := &metav1.Time{Time: time.Now()} + + // The timestamp loses a little resolution when round-tripped through JSON + // encoding. + lores := func(t *metav1.Time) *metav1.Time { + out := &metav1.Time{} + j, _ := json.Marshal(t) + _ = json.Unmarshal(j, out) + return out + } + + cases := map[string]struct { + u *Unstructured + set *metav1.Time + want *metav1.Time + }{ + "NewTime": { + u: New(), + set: now, + want: lores(now), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + tc.u.SetConnectionDetailsLastPublishedTime(tc.set) + got := tc.u.GetConnectionDetailsLastPublishedTime() + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("\nu.GetConnectionDetailsLastPublishedTime(): -want, +got:\n%s", diff) + } + }) + } +} diff --git a/pkg/resource/unstructured/composite/composite.go b/pkg/resource/unstructured/composite/composite.go index 3172fd6b7..bb1b0e0a1 100644 --- a/pkg/resource/unstructured/composite/composite.go +++ b/pkg/resource/unstructured/composite/composite.go @@ -161,3 +161,17 @@ func (c *Unstructured) SetConditions(conditions ...v1alpha1.Condition) { conditioned.SetConditions(conditions...) _ = fieldpath.Pave(c.Object).SetValue("status.conditions", conditioned.Conditions) } + +// GetConnectionDetailsLastPublishedTime of this Composite resource. +func (c *Unstructured) GetConnectionDetailsLastPublishedTime() *metav1.Time { + out := &metav1.Time{} + if err := fieldpath.Pave(c.Object).GetValueInto("status.connectionDetails.lastPublishedTime", out); err != nil { + return nil + } + return out +} + +// SetConnectionDetailsLastPublishedTime of this Composite resource. +func (c *Unstructured) SetConnectionDetailsLastPublishedTime(t *metav1.Time) { + _ = fieldpath.Pave(c.Object).SetValue("status.connectionDetails.lastPublishedTime", t) +} diff --git a/pkg/resource/unstructured/composite/composite_test.go b/pkg/resource/unstructured/composite/composite_test.go index 7771da4e7..cec197dc0 100644 --- a/pkg/resource/unstructured/composite/composite_test.go +++ b/pkg/resource/unstructured/composite/composite_test.go @@ -17,7 +17,9 @@ limitations under the License. package composite import ( + "encoding/json" "testing" + "time" "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" @@ -238,3 +240,38 @@ func TestWriteConnectionSecretToReference(t *testing.T) { }) } } + +func TestConnectionDetailsLastPublishedTime(t *testing.T) { + now := &metav1.Time{Time: time.Now()} + + // The timestamp loses a little resolution when round-tripped through JSON + // encoding. + lores := func(t *metav1.Time) *metav1.Time { + out := &metav1.Time{} + j, _ := json.Marshal(t) + _ = json.Unmarshal(j, out) + return out + } + + cases := map[string]struct { + u *Unstructured + set *metav1.Time + want *metav1.Time + }{ + "NewTime": { + u: New(), + set: now, + want: lores(now), + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + tc.u.SetConnectionDetailsLastPublishedTime(tc.set) + got := tc.u.GetConnectionDetailsLastPublishedTime() + if diff := cmp.Diff(tc.want, got); diff != "" { + t.Errorf("\nu.GetConnectionDetailsLastPublishedTime(): -want, +got:\n%s", diff) + } + }) + } +} From 3058c2f2c83a9f8510a7a728acd614f2d585a3d4 Mon Sep 17 00:00:00 2001 From: Nic Cope Date: Thu, 5 Nov 2020 04:48:25 +0000 Subject: [PATCH 2/2] Mark connection secret propagation machinery deprecated The claim controller in Crossplane core is the only place this machinery is still used, so it will be migrated into that controller. Signed-off-by: Nic Cope --- pkg/meta/meta.go | 2 ++ pkg/resource/api.go | 3 +++ pkg/resource/resource.go | 2 ++ 3 files changed, 7 insertions(+) diff --git a/pkg/meta/meta.go b/pkg/meta/meta.go index de183a6b9..d77fa6c8d 100644 --- a/pkg/meta/meta.go +++ b/pkg/meta/meta.go @@ -41,6 +41,7 @@ const AnnotationKeyExternalName = "crossplane.io/external-name" const ( AnnotationKeyPropagateToPrefix = "to.propagate.crossplane.io/" + // Deprecated: This functionality will be removed soon. AnnotationKeyPropagateFromNamespace = "from.propagate.crossplane.io/namespace" AnnotationKeyPropagateFromName = "from.propagate.crossplane.io/name" ) @@ -246,6 +247,7 @@ func SetExternalName(o metav1.Object, name string) { // AllowPropagation from one object to another by adding consenting annotations // to both. +// Deprecated: This functionality will be removed soon. func AllowPropagation(from, to metav1.Object) { AddAnnotations(to, map[string]string{ AnnotationKeyPropagateFromNamespace: from.GetNamespace(), diff --git a/pkg/resource/api.go b/pkg/resource/api.go index 102731a45..b5a5309d6 100644 --- a/pkg/resource/api.go +++ b/pkg/resource/api.go @@ -43,6 +43,7 @@ const ( // An APIManagedConnectionPropagator propagates connection details by reading // them from and writing them to a Kubernetes API server. +// Deprecated: This functionality will be removed soon. type APIManagedConnectionPropagator struct { Propagator ConnectionPropagator } @@ -54,12 +55,14 @@ func (a *APIManagedConnectionPropagator) PropagateConnection(ctx context.Context // An APIConnectionPropagator propagates connection details by reading // them from and writing them to a Kubernetes API server. +// Deprecated: This functionality will be removed soon. type APIConnectionPropagator struct { client ClientApplicator typer runtime.ObjectTyper } // NewAPIConnectionPropagator returns a new APIConnectionPropagator. +// Deprecated: This functionality will be removed soon. func NewAPIConnectionPropagator(c client.Client, t runtime.ObjectTyper) *APIConnectionPropagator { return &APIConnectionPropagator{ client: ClientApplicator{Client: c, Applicator: NewAPIUpdatingApplicator(c)}, diff --git a/pkg/resource/resource.go b/pkg/resource/resource.go index 5c9e86821..0fdfdbf4e 100644 --- a/pkg/resource/resource.go +++ b/pkg/resource/resource.go @@ -72,6 +72,7 @@ type LocalConnectionSecretOwner interface { // A ConnectionPropagator is responsible for propagating information required to // connect to a resource. +// Deprecated: This functionality will be removed soon. type ConnectionPropagator interface { PropagateConnection(ctx context.Context, to LocalConnectionSecretOwner, from ConnectionSecretOwner) error } @@ -83,6 +84,7 @@ type ConnectionPropagatorFn func(ctx context.Context, to LocalConnectionSecretOw // A ManagedConnectionPropagator is responsible for propagating information // required to connect to a managed resource (for example the connection secret) // from the managed resource to a target. +// Deprecated: This functionality will be removed soon. type ManagedConnectionPropagator interface { PropagateConnection(ctx context.Context, o LocalConnectionSecretOwner, mg Managed) error }