From cfabeb41d3800cf4b3431ddd5fdc3bae81158102 Mon Sep 17 00:00:00 2001 From: Alan Parra Date: Mon, 11 Dec 2023 13:04:48 -0300 Subject: [PATCH] Adapt types.Resource to Resource153 (#35568) --- api/types/resource_153.go | 64 ++++++++++++++++++++++++++++++++++ api/types/resource_153_test.go | 36 +++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/api/types/resource_153.go b/api/types/resource_153.go index 96af3799c5266..ddb4ff23c9aba 100644 --- a/api/types/resource_153.go +++ b/api/types/resource_153.go @@ -23,6 +23,12 @@ import ( headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" ) +// ResourceMetadata is the smallest interface that defines a Teleport resource. +type ResourceMetadata interface { + // GetMetadata returns the generic resource metadata. + GetMetadata() *headerv1.Metadata +} + // Resource153 is a resource that follows RFD 153. // // It exists as a weak guideline for fields that resource protos must provide @@ -52,6 +58,64 @@ type Resource153 interface { GetMetadata() *headerv1.Metadata } +// LegacyToResource153 converts a legacy [Resource] into a [Resource153]. +// +// Useful to handle old and new resources uniformly. If you can, consider +// further "downgrading" the Resource153 interface into the smallest subset that +// works for you (for example, [ResourceMetadata]). +func LegacyToResource153(r Resource) Resource153 { + return &legacyToResource153Adapter{inner: r} +} + +type legacyToResource153Adapter struct { + inner Resource +} + +// Unwrap is an escape hatch for Resource instances that are piped down into the +// codebase as a legacy Resource. +// +// Ideally you shouldn't depend on this. +func (r *legacyToResource153Adapter) Unwrap() Resource { + return r.inner +} + +// MarshalJSON adds support for marshaling the wrapped resource (instead of +// marshaling the adapter itself). +func (r *legacyToResource153Adapter) MarshalJSON() ([]byte, error) { + return json.Marshal(r.inner) +} + +func (r *legacyToResource153Adapter) GetKind() string { + return r.inner.GetKind() +} + +func (r *legacyToResource153Adapter) GetMetadata() *headerv1.Metadata { + md := r.inner.GetMetadata() + + var expires *timestamppb.Timestamp + if md.Expires != nil { + expires = timestamppb.New(*md.Expires) + } + + return &headerv1.Metadata{ + Name: md.Name, + Namespace: md.Namespace, + Description: md.Description, + Labels: md.Labels, + Expires: expires, + Id: md.ID, + Revision: md.Revision, + } +} + +func (r *legacyToResource153Adapter) GetSubKind() string { + return r.inner.GetSubKind() +} + +func (r *legacyToResource153Adapter) GetVersion() string { + return r.inner.GetVersion() +} + // Resource153ToLegacy transforms an RFD 153 style resource into a legacy // [Resource] type. // diff --git a/api/types/resource_153_test.go b/api/types/resource_153_test.go index 9b5fd370a6c8c..7dc0ed5221ac6 100644 --- a/api/types/resource_153_test.go +++ b/api/types/resource_153_test.go @@ -27,6 +27,42 @@ import ( "github.com/gravitational/teleport/api/types" ) +func TestLegacyToResource153(t *testing.T) { + // user is an example of a legacy resource. + // Any other resource type would to. + user := &types.UserV2{ + Kind: "user", + Metadata: types.Metadata{ + Name: "llama", + }, + Spec: types.UserSpecV2{ + Roles: []string{"human", "camelidae"}, + }, + } + + resource := types.LegacyToResource153(user) + + // Unwrap gives the underlying resource back. + t.Run("unwrap", func(t *testing.T) { + unwrapped := resource.(interface{ Unwrap() types.Resource }).Unwrap() + if diff := cmp.Diff(user, unwrapped, protocmp.Transform()); diff != "" { + t.Errorf("Unwrap mismatch (-want +got)\n%s", diff) + } + }) + + // Marshaling as JSON marshals the underlying resource. + t.Run("marshal", func(t *testing.T) { + jsonBytes, err := json.Marshal(resource) + require.NoError(t, err, "Marshal") + + user2 := &types.UserV2{} + require.NoError(t, json.Unmarshal(jsonBytes, user2), "Unmarshal") + if diff := cmp.Diff(user, user2, protocmp.Transform()); diff != "" { + t.Errorf("Marshal/Unmarshal mismatch (-want +got)\n%s", diff) + } + }) +} + func TestResource153ToLegacy(t *testing.T) { // bot is an example of an RFD 153 "compliant" resource. // Any other resource type would do.