From 95bf3ce555ee2c4bfb64b7872d1d27cfab18a227 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Tue, 1 Oct 2024 18:50:30 -0400 Subject: [PATCH] Add AutoUpdate Client/Cache implementation (#46661) (#46750) * Add AutoUpdate Client/Cache implementation * CR changes * Add permission for proxy to access resources * Rename all occurrences auto update to camelcase * Remove auto update client wrapper * Drop AutoUpdateServiceClient helper Rename comments for consistency --- api/client/client.go | 21 +++ api/client/events.go | 15 ++ api/types/autoupdate/config_test.go | 2 +- api/types/autoupdate/version_test.go | 2 +- lib/auth/accesspoint/accesspoint.go | 2 + lib/auth/authclient/api.go | 13 ++ lib/auth/authclient/clt.go | 1 + lib/auth/autoupdate/autoupdatev1/service.go | 46 ++--- lib/auth/grpcserver.go | 2 +- lib/auth/helpers.go | 1 + lib/authz/permissions.go | 2 + lib/cache/cache.go | 69 +++++++ lib/cache/cache_test.go | 195 ++++++++++++++++++++ lib/cache/collections.go | 91 +++++++++ lib/service/service.go | 2 + lib/services/autoupdates.go | 20 +- lib/services/local/autoupdate.go | 22 +-- lib/services/local/autoupdate_test.go | 26 +-- lib/services/local/events.go | 71 +++++++ 19 files changed, 543 insertions(+), 60 deletions(-) diff --git a/api/client/client.go b/api/client/client.go index 3ccc0f6a723c..dc6478040776 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -59,6 +59,7 @@ import ( "github.com/gravitational/teleport/api/gen/proto/go/assist/v1" accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" auditlogpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/auditlog/v1" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" discoveryconfigv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/discoveryconfig/v1" @@ -2718,6 +2719,26 @@ func (c *Client) GetClusterAuditConfig(ctx context.Context) (types.ClusterAuditC return resp, nil } +// GetAutoUpdateConfig gets AutoUpdateConfig resource. +func (c *Client) GetAutoUpdateConfig(ctx context.Context) (*autoupdatev1pb.AutoUpdateConfig, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.GetAutoUpdateConfig(ctx, &autoupdatev1pb.GetAutoUpdateConfigRequest{}) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + +// GetAutoUpdateVersion gets AutoUpdateVersion resource. +func (c *Client) GetAutoUpdateVersion(ctx context.Context) (*autoupdatev1pb.AutoUpdateVersion, error) { + client := autoupdatev1pb.NewAutoUpdateServiceClient(c.conn) + resp, err := client.GetAutoUpdateVersion(ctx, &autoupdatev1pb.GetAutoUpdateVersionRequest{}) + if err != nil { + return nil, trace.Wrap(err) + } + return resp, nil +} + // GetClusterAccessGraphConfig retrieves the Cluster Access Graph configuration from Auth server. func (c *Client) GetClusterAccessGraphConfig(ctx context.Context) (*clusterconfigpb.AccessGraphConfig, error) { rsp, err := c.ClusterConfigClient().GetClusterAccessGraphConfig(ctx, &clusterconfigpb.GetClusterAccessGraphConfigRequest{}) diff --git a/api/client/events.go b/api/client/events.go index b2bd379b0d3c..98fa50defe09 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -18,6 +18,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" @@ -56,6 +57,14 @@ func EventToGRPC(in types.Event) (*proto.Event, error) { out.Resource = &proto.Event_KubernetesWaitingContainer{ KubernetesWaitingContainer: r, } + case *autoupdate.AutoUpdateConfig: + out.Resource = &proto.Event_AutoUpdateConfig{ + AutoUpdateConfig: r, + } + case *autoupdate.AutoUpdateVersion: + out.Resource = &proto.Event_AutoUpdateVersion{ + AutoUpdateVersion: r, + } } case *types.ResourceHeader: out.Resource = &proto.Event_ResourceHeader{ @@ -470,6 +479,12 @@ func EventFromGRPC(in *proto.Event) (*types.Event, error) { } else if r := in.GetKubernetesWaitingContainer(); r != nil { out.Resource = types.Resource153ToLegacy(r) return &out, nil + } else if r := in.GetAutoUpdateConfig(); r != nil { + out.Resource = types.Resource153ToLegacy(r) + return &out, nil + } else if r := in.GetAutoUpdateVersion(); r != nil { + out.Resource = types.Resource153ToLegacy(r) + return &out, nil } else { return nil, trace.BadParameter("received unsupported resource %T", in.Resource) } diff --git a/api/types/autoupdate/config_test.go b/api/types/autoupdate/config_test.go index 4ebf29a53684..2ee33dc5bf2b 100644 --- a/api/types/autoupdate/config_test.go +++ b/api/types/autoupdate/config_test.go @@ -30,7 +30,7 @@ import ( "github.com/gravitational/teleport/api/types" ) -// TestNewAutoUpdateConfig verifies validation for auto update config resource. +// TestNewAutoUpdateConfig verifies validation for AutoUpdateConfig resource. func TestNewAutoUpdateConfig(t *testing.T) { tests := []struct { name string diff --git a/api/types/autoupdate/version_test.go b/api/types/autoupdate/version_test.go index 5f6729ec42f5..5fe4f167a037 100644 --- a/api/types/autoupdate/version_test.go +++ b/api/types/autoupdate/version_test.go @@ -30,7 +30,7 @@ import ( "github.com/gravitational/teleport/api/types" ) -// TestNewAutoUpdateVersion verifies validation for auto update version resource. +// TestNewAutoUpdateVersion verifies validation for AutoUpdateVersion resource. func TestNewAutoUpdateVersion(t *testing.T) { tests := []struct { name string diff --git a/lib/auth/accesspoint/accesspoint.go b/lib/auth/accesspoint/accesspoint.go index 8a2c2ecf90e4..2514a3764ebf 100644 --- a/lib/auth/accesspoint/accesspoint.go +++ b/lib/auth/accesspoint/accesspoint.go @@ -95,6 +95,7 @@ type Config struct { WebSession types.WebSessionInterface WebToken types.WebTokenInterface WindowsDesktops services.WindowsDesktops + AutoUpdateService services.AutoUpdateServiceGetter } func (c *Config) CheckAndSetDefaults() error { @@ -158,6 +159,7 @@ func NewCache(cfg Config) (*cache.Cache, error) { AppSession: cfg.AppSession, Apps: cfg.Apps, ClusterConfig: cfg.ClusterConfig, + AutoUpdateService: cfg.AutoUpdateService, DatabaseServices: cfg.DatabaseServices, Databases: cfg.Databases, DiscoveryConfigs: cfg.DiscoveryConfigs, diff --git a/lib/auth/authclient/api.go b/lib/auth/authclient/api.go index d7fc446acc72..3949bce0edc0 100644 --- a/lib/auth/authclient/api.go +++ b/lib/auth/authclient/api.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" @@ -298,6 +299,12 @@ type ReadProxyAccessPoint interface { // GetUserGroup returns the specified user group resources. GetUserGroup(ctx context.Context, name string) (types.UserGroup, error) + + // GetAutoUpdateConfig gets the AutoUpdateConfig from the backend. + GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) + + // GetAutoUpdateVersion gets the AutoUpdateVersion from the backend. + GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) } // SnowflakeSessionWatcher is watcher interface used by Snowflake web session watcher. @@ -1138,6 +1145,12 @@ type Cache interface { // IntegrationsGetter defines read/list methods for integrations. services.IntegrationsGetter + + // GetAutoUpdateConfig gets the AutoUpdateConfig from the backend. + GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) + + // GetAutoUpdateVersion gets the AutoUpdateVersion from the backend. + GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) } type NodeWrapper struct { diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go index b5e179ffbd95..6381dd4f1b18 100644 --- a/lib/auth/authclient/clt.go +++ b/lib/auth/authclient/clt.go @@ -1303,6 +1303,7 @@ type ClientI interface { WebService services.Status services.ClusterConfiguration + services.AutoUpdateServiceGetter services.SessionTrackerService services.ConnectionsDiagnostic services.SAMLIdPSession diff --git a/lib/auth/autoupdate/autoupdatev1/service.go b/lib/auth/autoupdate/autoupdatev1/service.go index 728482701305..1c6faf8c04b5 100644 --- a/lib/auth/autoupdate/autoupdatev1/service.go +++ b/lib/auth/autoupdate/autoupdatev1/service.go @@ -32,24 +32,24 @@ import ( // Cache defines only read-only service methods. type Cache interface { - // GetAutoUpdateConfig gets the autoupdate configuration from the backend. + // GetAutoUpdateConfig gets the AutoUpdateConfig from the backend. GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) - // GetAutoUpdateVersion gets the autoupdate version from the backend. + // GetAutoUpdateVersion gets the AutoUpdateVersion from the backend. GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) } -// ServiceConfig holds configuration options for the autoupdate gRPC service. +// ServiceConfig holds configuration options for the auto update gRPC service. type ServiceConfig struct { // Authorizer is the authorizer used to check access to resources. Authorizer authz.Authorizer - // Backend is the backend used to store autoupdate resources. + // Backend is the backend used to store AutoUpdate resources. Backend services.AutoUpdateService - // Cache is the cache used to store autoupdate resources. + // Cache is the cache used to store AutoUpdate resources. Cache Cache } -// Service implements the gRPC API layer for the Autoupdate. +// Service implements the gRPC API layer for the AutoUpdate. type Service struct { autoupdate.UnimplementedAutoUpdateServiceServer @@ -58,7 +58,7 @@ type Service struct { cache Cache } -// NewService returns a new Autoupdate API service using the given storage layer and authorizer. +// NewService returns a new AutoUpdate API service using the given storage layer and authorizer. func NewService(cfg ServiceConfig) (*Service, error) { switch { case cfg.Backend == nil: @@ -75,7 +75,7 @@ func NewService(cfg ServiceConfig) (*Service, error) { }, nil } -// GetAutoUpdateConfig gets the current autoupdate config singleton. +// GetAutoUpdateConfig gets the current AutoUpdateConfig singleton. func (s *Service) GetAutoUpdateConfig(ctx context.Context, req *autoupdate.GetAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -94,7 +94,7 @@ func (s *Service) GetAutoUpdateConfig(ctx context.Context, req *autoupdate.GetAu return config, nil } -// CreateAutoUpdateConfig creates autoupdate config singleton. +// CreateAutoUpdateConfig creates AutoUpdateConfig singleton. func (s *Service) CreateAutoUpdateConfig(ctx context.Context, req *autoupdate.CreateAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -109,7 +109,7 @@ func (s *Service) CreateAutoUpdateConfig(ctx context.Context, req *autoupdate.Cr return config, trace.Wrap(err) } -// UpdateAutoUpdateConfig updates autoupdate config singleton. +// UpdateAutoUpdateConfig updates AutoUpdateConfig singleton. func (s *Service) UpdateAutoUpdateConfig(ctx context.Context, req *autoupdate.UpdateAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -124,7 +124,7 @@ func (s *Service) UpdateAutoUpdateConfig(ctx context.Context, req *autoupdate.Up return config, trace.Wrap(err) } -// UpsertAutoUpdateConfig updates or creates autoupdate config singleton. +// UpsertAutoUpdateConfig updates or creates AutoUpdateConfig singleton. func (s *Service) UpsertAutoUpdateConfig(ctx context.Context, req *autoupdate.UpsertAutoUpdateConfigRequest) (*autoupdate.AutoUpdateConfig, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -139,7 +139,7 @@ func (s *Service) UpsertAutoUpdateConfig(ctx context.Context, req *autoupdate.Up return config, trace.Wrap(err) } -// DeleteAutoUpdateConfig deletes autoupdate config singleton. +// DeleteAutoUpdateConfig deletes AutoUpdateConfig singleton. func (s *Service) DeleteAutoUpdateConfig(ctx context.Context, req *autoupdate.DeleteAutoUpdateConfigRequest) (*emptypb.Empty, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -156,7 +156,7 @@ func (s *Service) DeleteAutoUpdateConfig(ctx context.Context, req *autoupdate.De return &emptypb.Empty{}, nil } -// GetAutoUpdateVersion gets the current autoupdate version singleton. +// GetAutoUpdateVersion gets the current AutoUpdateVersion singleton. func (s *Service) GetAutoUpdateVersion(ctx context.Context, req *autoupdate.GetAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -175,7 +175,7 @@ func (s *Service) GetAutoUpdateVersion(ctx context.Context, req *autoupdate.GetA return version, nil } -// CreateAutoUpdateVersion creates autoupdate version singleton. +// CreateAutoUpdateVersion creates AutoUpdateVersion singleton. func (s *Service) CreateAutoUpdateVersion(ctx context.Context, req *autoupdate.CreateAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -186,11 +186,11 @@ func (s *Service) CreateAutoUpdateVersion(ctx context.Context, req *autoupdate.C return nil, trace.Wrap(err) } - autoupdateVersion, err := s.backend.CreateAutoUpdateVersion(ctx, req.Version) - return autoupdateVersion, trace.Wrap(err) + autoUpdateVersion, err := s.backend.CreateAutoUpdateVersion(ctx, req.Version) + return autoUpdateVersion, trace.Wrap(err) } -// UpdateAutoUpdateVersion updates autoupdate version singleton. +// UpdateAutoUpdateVersion updates AutoUpdateVersion singleton. func (s *Service) UpdateAutoUpdateVersion(ctx context.Context, req *autoupdate.UpdateAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -201,11 +201,11 @@ func (s *Service) UpdateAutoUpdateVersion(ctx context.Context, req *autoupdate.U return nil, trace.Wrap(err) } - autoupdateVersion, err := s.backend.UpdateAutoUpdateVersion(ctx, req.Version) - return autoupdateVersion, trace.Wrap(err) + autoUpdateVersion, err := s.backend.UpdateAutoUpdateVersion(ctx, req.Version) + return autoUpdateVersion, trace.Wrap(err) } -// UpsertAutoUpdateVersion updates or creates autoupdate version singleton. +// UpsertAutoUpdateVersion updates or creates AutoUpdateVersion singleton. func (s *Service) UpsertAutoUpdateVersion(ctx context.Context, req *autoupdate.UpsertAutoUpdateVersionRequest) (*autoupdate.AutoUpdateVersion, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { @@ -216,11 +216,11 @@ func (s *Service) UpsertAutoUpdateVersion(ctx context.Context, req *autoupdate.U return nil, trace.Wrap(err) } - autoupdateVersion, err := s.backend.UpsertAutoUpdateVersion(ctx, req.Version) - return autoupdateVersion, trace.Wrap(err) + autoUpdateVersion, err := s.backend.UpsertAutoUpdateVersion(ctx, req.Version) + return autoUpdateVersion, trace.Wrap(err) } -// DeleteAutoUpdateVersion deletes autoupdate version singleton. +// DeleteAutoUpdateVersion deletes AutoUpdateVersion singleton. func (s *Service) DeleteAutoUpdateVersion(ctx context.Context, req *autoupdate.DeleteAutoUpdateVersionRequest) (*emptypb.Empty, error) { authCtx, err := s.authorizer.Authorize(ctx) if err != nil { diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index a2884398e62c..dac27471d4e1 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -5721,7 +5721,7 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { autoUpdateServiceServer, err := autoupdatev1.NewService(autoupdatev1.ServiceConfig{ Authorizer: cfg.Authorizer, Backend: cfg.AuthServer.Services, - Cache: cfg.AuthServer.Services, + Cache: cfg.AuthServer.Cache, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/auth/helpers.go b/lib/auth/helpers.go index 373b0442c2e4..fe9207458340 100644 --- a/lib/auth/helpers.go +++ b/lib/auth/helpers.go @@ -334,6 +334,7 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) { AppSession: svces.Identity, Apps: svces.Apps, ClusterConfig: svces.ClusterConfiguration, + AutoUpdateService: svces.AutoUpdateService, DatabaseServices: svces.DatabaseServices, Databases: svces.Databases, DiscoveryConfigs: svces.DiscoveryConfigs, diff --git a/lib/authz/permissions.go b/lib/authz/permissions.go index ee620b0a242c..717d3bc27bd8 100644 --- a/lib/authz/permissions.go +++ b/lib/authz/permissions.go @@ -688,6 +688,8 @@ func roleSpecForProxy(clusterName string) types.RoleSpecV6 { types.NewRule(types.KindSAMLIdPServiceProvider, services.RO()), types.NewRule(types.KindUserGroup, services.RO()), types.NewRule(types.KindClusterMaintenanceConfig, services.RO()), + types.NewRule(types.KindAutoUpdateConfig, services.RO()), + types.NewRule(types.KindAutoUpdateVersion, services.RO()), types.NewRule(types.KindIntegration, append(services.RO(), types.VerbUse)), types.NewRule(types.KindAuditQuery, services.RO()), types.NewRule(types.KindSecurityReport, services.RO()), diff --git a/lib/cache/cache.go b/lib/cache/cache.go index 2a03476b0724..63843d26f9ab 100644 --- a/lib/cache/cache.go +++ b/lib/cache/cache.go @@ -30,10 +30,12 @@ import ( "go.opentelemetry.io/otel/attribute" oteltrace "go.opentelemetry.io/otel/trace" "golang.org/x/sync/errgroup" + protobuf "google.golang.org/protobuf/proto" "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" "github.com/gravitational/teleport/api/internalutils/stream" apitracing "github.com/gravitational/teleport/api/observability/tracing" @@ -166,6 +168,8 @@ func ForAuth(cfg Config) Config { {Kind: types.KindAccessListMember}, {Kind: types.KindAccessListReview}, {Kind: types.KindKubeWaitingContainer}, + {Kind: types.KindAutoUpdateVersion}, + {Kind: types.KindAutoUpdateConfig}, } cfg.QueueSize = defaults.AuthQueueSize // We don't want to enable partial health for auth cache because auth uses an event stream @@ -218,6 +222,8 @@ func ForProxy(cfg Config) Config { {Kind: types.KindSecurityReport}, {Kind: types.KindSecurityReportState}, {Kind: types.KindKubeWaitingContainer}, + {Kind: types.KindAutoUpdateConfig}, + {Kind: types.KindAutoUpdateVersion}, } cfg.QueueSize = defaults.ProxyQueueSize return cfg @@ -513,6 +519,7 @@ type Cache struct { trustCache services.Trust clusterConfigCache services.ClusterConfiguration + autoUpdateCache *local.AutoUpdateService provisionerCache services.Provisioner usersCache services.UsersService accessCache services.Access @@ -655,6 +662,8 @@ type Config struct { Trust services.Trust // ClusterConfig is a cluster configuration service ClusterConfig services.ClusterConfiguration + // AutoUpdateService is an autoupdate service. + AutoUpdateService services.AutoUpdateServiceGetter // Provisioner is a provisioning service Provisioner services.Provisioner // Users is a users service @@ -904,6 +913,12 @@ func New(config Config) (*Cache, error) { return nil, trace.Wrap(err) } + autoUpdateCache, err := local.NewAutoUpdateService(config.Backend) + if err != nil { + cancel() + return nil, trace.Wrap(err) + } + fanout := services.NewFanoutV2(services.FanoutV2Config{}) lowVolumeFanouts := make([]*services.FanoutV2, 0, config.FanoutShards) for i := 0; i < config.FanoutShards; i++ { @@ -924,6 +939,7 @@ func New(config Config) (*Cache, error) { fnCache: fnCache, trustCache: local.NewCAService(config.Backend), clusterConfigCache: clusterConfigCache, + autoUpdateCache: autoUpdateCache, provisionerCache: local.NewProvisioningService(config.Backend), usersCache: local.NewIdentityService(config.Backend), accessCache: local.NewAccessService(config.Backend), @@ -1843,6 +1859,59 @@ func (c *Cache) GetClusterName(opts ...services.MarshalOption) (types.ClusterNam return rg.reader.GetClusterName(opts...) } +type autoUpdateCacheKey struct { + kind string +} + +var _ map[autoUpdateCacheKey]struct{} // compile-time hashability check + +// GetAutoUpdateConfig gets the AutoUpdateConfig from the backend. +func (c *Cache) GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) { + ctx, span := c.Tracer.Start(ctx, "cache/GetAutoUpdateConfig") + defer span.End() + + rg, err := readCollectionCache(c, c.collections.autoUpdateConfigs) + if err != nil { + return nil, trace.Wrap(err) + } + defer rg.Release() + if !rg.IsCacheRead() { + cachedConfig, err := utils.FnCacheGet(ctx, c.fnCache, autoUpdateCacheKey{"config"}, func(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) { + cfg, err := rg.reader.GetAutoUpdateConfig(ctx) + return cfg, err + }) + if err != nil { + return nil, trace.Wrap(err) + } + return protobuf.Clone(cachedConfig).(*autoupdate.AutoUpdateConfig), nil + } + return rg.reader.GetAutoUpdateConfig(ctx) +} + +// GetAutoUpdateVersion gets the AutoUpdateVersion from the backend. +func (c *Cache) GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) { + ctx, span := c.Tracer.Start(ctx, "cache/GetAutoUpdateVersion") + defer span.End() + + rg, err := readCollectionCache(c, c.collections.autoUpdateVersions) + if err != nil { + return nil, trace.Wrap(err) + } + defer rg.Release() + if !rg.IsCacheRead() { + cachedVersion, err := utils.FnCacheGet(ctx, c.fnCache, autoUpdateCacheKey{"version"}, func(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) { + version, err := rg.reader.GetAutoUpdateVersion(ctx) + return version, err + }) + if err != nil { + return nil, trace.Wrap(err) + } + + return protobuf.Clone(cachedVersion).(*autoupdate.AutoUpdateVersion), nil + } + return rg.reader.GetAutoUpdateVersion(ctx) +} + func (c *Cache) GetUIConfig(ctx context.Context) (types.UIConfig, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetUIConfig") defer span.End() diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go index 26ecba553b1e..57706bacca20 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -40,9 +40,12 @@ import ( "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" + update "github.com/gravitational/teleport/api/types/autoupdate" "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/api/types/header" "github.com/gravitational/teleport/api/types/kubewaitingcontainer" @@ -112,6 +115,7 @@ type testPack struct { secReports services.SecReports accessLists services.AccessLists kubeWaitingContainers services.KubeWaitingContainer + autoUpdateService services.AutoUpdateService } // testFuncs are functions to support testing an object in a cache. @@ -125,6 +129,17 @@ type testFuncs[T types.Resource] struct { deleteAll func(context.Context) error } +// testFuncs153 are functions to support testing an RFD153-style resource in a cache. +type testFuncs153[T types.Resource153] struct { + newResource func(string) (T, error) + create func(context.Context, T) error + list func(context.Context) ([]T, error) + cacheGet func(context.Context, string) (T, error) + cacheList func(context.Context) ([]T, error) + update func(context.Context, T) error + deleteAll func(context.Context) error +} + func (t *testPack) Close() { var errors []error if t.backend != nil { @@ -295,6 +310,11 @@ func newPackWithoutCache(dir string, opts ...packOption) (*testPack, error) { } p.kubeWaitingContainers = kubeWaitingContSvc + p.autoUpdateService, err = local.NewAutoUpdateService(p.backend) + if err != nil { + return nil, trace.Wrap(err) + } + return p, nil } @@ -337,6 +357,7 @@ func newPack(dir string, setupConfig func(c Config) Config, opts ...packOption) SecReports: p.secReports, AccessLists: p.accessLists, KubeWaitingContainers: p.kubeWaitingContainers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -736,6 +757,7 @@ func TestCompletenessInit(t *testing.T) { SecReports: p.secReports, AccessLists: p.accessLists, KubeWaitingContainers: p.kubeWaitingContainers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -809,6 +831,7 @@ func TestCompletenessReset(t *testing.T) { SecReports: p.secReports, AccessLists: p.accessLists, KubeWaitingContainers: p.kubeWaitingContainers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -1008,6 +1031,7 @@ func TestListResources_NodesTTLVariant(t *testing.T) { SecReports: p.secReports, AccessLists: p.accessLists, KubeWaitingContainers: p.kubeWaitingContainers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, neverOK: true, // ensure reads are never healthy @@ -1092,6 +1116,7 @@ func initStrategy(t *testing.T) { SecReports: p.secReports, AccessLists: p.accessLists, KubeWaitingContainers: p.kubeWaitingContainers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -2441,6 +2466,154 @@ func TestAccessListReviews(t *testing.T) { }) } +// TestAutoUpdateConfig tests that CRUD operations on AutoUpdateConfig resources are +// replicated from the backend to the cache. +func TestAutoUpdateConfig(t *testing.T) { + t.Parallel() + + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + testResources153(t, p, testFuncs153[*autoupdate.AutoUpdateConfig]{ + newResource: func(name string) (*autoupdate.AutoUpdateConfig, error) { + return newAutoUpdateConfig(t), nil + }, + create: func(ctx context.Context, item *autoupdate.AutoUpdateConfig) error { + _, err := p.autoUpdateService.UpsertAutoUpdateConfig(ctx, item) + return trace.Wrap(err) + }, + list: func(ctx context.Context) ([]*autoupdate.AutoUpdateConfig, error) { + item, err := p.autoUpdateService.GetAutoUpdateConfig(ctx) + if trace.IsNotFound(err) { + return []*autoupdate.AutoUpdateConfig{}, nil + } + return []*autoupdate.AutoUpdateConfig{item}, trace.Wrap(err) + }, + cacheList: func(ctx context.Context) ([]*autoupdate.AutoUpdateConfig, error) { + item, err := p.cache.GetAutoUpdateConfig(ctx) + if trace.IsNotFound(err) { + return []*autoupdate.AutoUpdateConfig{}, nil + } + return []*autoupdate.AutoUpdateConfig{item}, trace.Wrap(err) + }, + deleteAll: func(ctx context.Context) error { + return trace.Wrap(p.autoUpdateService.DeleteAutoUpdateConfig(ctx)) + }, + }) +} + +// TestAutoUpdateVersion tests that CRUD operations on AutoUpdateVersion resource are +// replicated from the backend to the cache. +func TestAutoUpdateVersion(t *testing.T) { + t.Parallel() + + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + testResources153(t, p, testFuncs153[*autoupdate.AutoUpdateVersion]{ + newResource: func(name string) (*autoupdate.AutoUpdateVersion, error) { + return newAutoUpdateVersion(t), nil + }, + create: func(ctx context.Context, item *autoupdate.AutoUpdateVersion) error { + _, err := p.autoUpdateService.UpsertAutoUpdateVersion(ctx, item) + return trace.Wrap(err) + }, + list: func(ctx context.Context) ([]*autoupdate.AutoUpdateVersion, error) { + item, err := p.autoUpdateService.GetAutoUpdateVersion(ctx) + if trace.IsNotFound(err) { + return []*autoupdate.AutoUpdateVersion{}, nil + } + return []*autoupdate.AutoUpdateVersion{item}, trace.Wrap(err) + }, + cacheList: func(ctx context.Context) ([]*autoupdate.AutoUpdateVersion, error) { + item, err := p.cache.GetAutoUpdateVersion(ctx) + if trace.IsNotFound(err) { + return []*autoupdate.AutoUpdateVersion{}, nil + } + return []*autoupdate.AutoUpdateVersion{item}, trace.Wrap(err) + }, + deleteAll: func(ctx context.Context) error { + return trace.Wrap(p.autoUpdateService.DeleteAutoUpdateVersion(ctx)) + }, + }) +} + +// testResources153 is a generic tester for RFD153-style resources. +func testResources153[T types.Resource153](t *testing.T, p *testPack, funcs testFuncs153[T]) { + ctx := context.Background() + + // Create a resource. + r, err := funcs.newResource("test-resource") + require.NoError(t, err) + // update is optional as not every resource implements it + if funcs.update != nil { + r.GetMetadata().Labels = map[string]string{"label": "value1"} + } + + err = funcs.create(ctx, r) + require.NoError(t, err) + + cmpOpts := []cmp.Option{ + protocmp.IgnoreFields(&headerv1.Metadata{}, "revision", "id"), + protocmp.Transform(), + } + + // Check that the resource is now in the backend. + out, err := funcs.list(ctx) + require.NoError(t, err) + require.Empty(t, cmp.Diff([]T{r}, out, cmpOpts...)) + + // Wait until the information has been replicated to the cache. + require.Eventually(t, func() bool { + // Make sure the cache has a single resource in it. + out, err = funcs.cacheList(ctx) + assert.NoError(t, err) + return len(cmp.Diff([]T{r}, out, cmpOpts...)) == 0 + }, time.Second*2, time.Millisecond*250) + + // cacheGet is optional as not every resource implements it + if funcs.cacheGet != nil { + // Make sure a single cache get works. + getR, err := funcs.cacheGet(ctx, r.GetMetadata().GetName()) + require.NoError(t, err) + require.Empty(t, cmp.Diff(r, getR, cmpOpts...)) + } + + // update is optional as not every resource implements it + if funcs.update != nil { + // Update the resource and upsert it into the backend again. + r.GetMetadata().Labels["label"] = "value2" + err = funcs.update(ctx, r) + require.NoError(t, err) + } + + // Check that the resource is in the backend and only one exists (so an + // update occurred). + out, err = funcs.list(ctx) + require.NoError(t, err) + require.Empty(t, cmp.Diff([]T{r}, out, cmpOpts...)) + + // Check that information has been replicated to the cache. + require.Eventually(t, func() bool { + // Make sure the cache has a single resource in it. + out, err = funcs.cacheList(ctx) + assert.NoError(t, err) + return len(cmp.Diff([]T{r}, out, cmpOpts...)) == 0 + }, time.Second*2, time.Millisecond*250) + + // Remove all resources from the backend. + err = funcs.deleteAll(ctx) + require.NoError(t, err) + + // Check that information has been replicated to the cache. + require.EventuallyWithT(t, func(t *assert.CollectT) { + // Check that the cache is now empty. + out, err = funcs.cacheList(ctx) + assert.NoError(t, err) + assert.Empty(t, out) + }, time.Second*2, time.Millisecond*250) +} + // testResources is a generic tester for resources. func testResources[T types.Resource](t *testing.T, p *testPack, funcs testFuncs[T]) { ctx := context.Background() @@ -2936,6 +3109,8 @@ func TestCacheWatchKindExistsInEvents(t *testing.T) { types.KindAccessListMember: newAccessListMember(t, "access-list", "member"), types.KindAccessListReview: newAccessListReview(t, "access-list", "review"), types.KindKubeWaitingContainer: newKubeWaitingContainer(t), + types.KindAutoUpdateConfig: types.Resource153ToLegacy(newAutoUpdateConfig(t)), + types.KindAutoUpdateVersion: types.Resource153ToLegacy(newAutoUpdateVersion(t)), } for name, cfg := range cases { @@ -3384,6 +3559,26 @@ func newKubeWaitingContainer(t *testing.T) types.Resource { return types.Resource153ToLegacy(waitingCont) } +func newAutoUpdateConfig(t *testing.T) *autoupdate.AutoUpdateConfig { + t.Helper() + + r, err := update.NewAutoUpdateConfig(&autoupdate.AutoUpdateConfigSpec{ + ToolsAutoupdate: true, + }) + require.NoError(t, err) + return r +} + +func newAutoUpdateVersion(t *testing.T) *autoupdate.AutoUpdateVersion { + t.Helper() + + r, err := update.NewAutoUpdateVersion(&autoupdate.AutoUpdateVersionSpec{ + ToolsVersion: "1.2.3", + }) + require.NoError(t, err) + return r +} + func withKeepalive[T any](fn func(context.Context, T) (*types.KeepAlive, error)) func(context.Context, T) error { return func(ctx context.Context, resource T) error { _, err := fn(ctx, resource) diff --git a/lib/cache/collections.go b/lib/cache/collections.go index dc2f07ee9cd6..64e99834ac63 100644 --- a/lib/cache/collections.go +++ b/lib/cache/collections.go @@ -26,6 +26,7 @@ import ( "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" @@ -231,6 +232,8 @@ type cacheCollections struct { webTokens collectionReader[webTokenGetter] windowsDesktops collectionReader[windowsDesktopsGetter] windowsDesktopServices collectionReader[windowsDesktopServiceGetter] + autoUpdateConfigs collectionReader[autoUpdateConfigGetter] + autoUpdateVersions collectionReader[autoUpdateVersionGetter] } // setupCollections returns a registry of collections. @@ -672,6 +675,24 @@ func setupCollections(c *Cache, watches []types.WatchKind) (*cacheCollections, e watch: watch, } collections.byKind[resourceKind] = collections.kubeWaitingContainers + case types.KindAutoUpdateConfig: + if c.AutoUpdateService == nil { + return nil, trace.BadParameter("missing parameter AutoUpdateService") + } + collections.autoUpdateConfigs = &genericCollection[*autoupdate.AutoUpdateConfig, autoUpdateConfigGetter, autoUpdateConfigExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.autoUpdateConfigs + case types.KindAutoUpdateVersion: + if c.AutoUpdateService == nil { + return nil, trace.BadParameter("missing parameter AutoUpdateService") + } + collections.autoUpdateVersions = &genericCollection[*autoupdate.AutoUpdateVersion, autoUpdateVersionGetter, autoUpdateVersionExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.autoUpdateVersions default: return nil, trace.BadParameter("resource %q is not supported", watch.Kind) } @@ -1177,6 +1198,76 @@ type clusterNameGetter interface { var _ executor[types.ClusterName, clusterNameGetter] = clusterNameExecutor{} +type autoUpdateConfigExecutor struct{} + +func (autoUpdateConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*autoupdate.AutoUpdateConfig, error) { + config, err := cache.AutoUpdateService.GetAutoUpdateConfig(ctx) + return []*autoupdate.AutoUpdateConfig{config}, trace.Wrap(err) +} + +func (autoUpdateConfigExecutor) upsert(ctx context.Context, cache *Cache, resource *autoupdate.AutoUpdateConfig) error { + _, err := cache.autoUpdateCache.UpsertAutoUpdateConfig(ctx, resource) + return trace.Wrap(err) +} + +func (autoUpdateConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.autoUpdateCache.DeleteAutoUpdateConfig(ctx) +} + +func (autoUpdateConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.autoUpdateCache.DeleteAutoUpdateConfig(ctx) +} + +func (autoUpdateConfigExecutor) isSingleton() bool { return true } + +func (autoUpdateConfigExecutor) getReader(cache *Cache, cacheOK bool) autoUpdateConfigGetter { + if cacheOK { + return cache.autoUpdateCache + } + return cache.Config.AutoUpdateService +} + +type autoUpdateConfigGetter interface { + GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) +} + +var _ executor[*autoupdate.AutoUpdateConfig, autoUpdateConfigGetter] = autoUpdateConfigExecutor{} + +type autoUpdateVersionExecutor struct{} + +func (autoUpdateVersionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*autoupdate.AutoUpdateVersion, error) { + version, err := cache.AutoUpdateService.GetAutoUpdateVersion(ctx) + return []*autoupdate.AutoUpdateVersion{version}, trace.Wrap(err) +} + +func (autoUpdateVersionExecutor) upsert(ctx context.Context, cache *Cache, resource *autoupdate.AutoUpdateVersion) error { + _, err := cache.autoUpdateCache.UpsertAutoUpdateVersion(ctx, resource) + return trace.Wrap(err) +} + +func (autoUpdateVersionExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.autoUpdateCache.DeleteAutoUpdateVersion(ctx) +} + +func (autoUpdateVersionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.autoUpdateCache.DeleteAutoUpdateVersion(ctx) +} + +func (autoUpdateVersionExecutor) isSingleton() bool { return true } + +func (autoUpdateVersionExecutor) getReader(cache *Cache, cacheOK bool) autoUpdateVersionGetter { + if cacheOK { + return cache.autoUpdateCache + } + return cache.Config.AutoUpdateService +} + +type autoUpdateVersionGetter interface { + GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) +} + +var _ executor[*autoupdate.AutoUpdateVersion, autoUpdateVersionGetter] = autoUpdateVersionExecutor{} + type userExecutor struct{} func (userExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.User, error) { diff --git a/lib/service/service.go b/lib/service/service.go index 5301b03537e8..4b819577b250 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -2389,6 +2389,7 @@ func (process *TeleportProcess) newAccessCacheForServices(cfg accesspoint.Config cfg.WebSession = services.Identity.WebSessions() cfg.WebToken = services.Identity.WebTokens() cfg.WindowsDesktops = services.WindowsDesktops + cfg.AutoUpdateService = services.AutoUpdateService return accesspoint.NewCache(cfg) } @@ -2427,6 +2428,7 @@ func (process *TeleportProcess) newAccessCacheForClient(cfg accesspoint.Config, cfg.WebSession = client.WebSessions() cfg.WebToken = client.WebTokens() cfg.WindowsDesktops = client + cfg.AutoUpdateService = client return accesspoint.NewCache(cfg) } diff --git a/lib/services/autoupdates.go b/lib/services/autoupdates.go index 3079f355d7ff..5fa7a4eed467 100644 --- a/lib/services/autoupdates.go +++ b/lib/services/autoupdates.go @@ -26,10 +26,10 @@ import ( // AutoUpdateServiceGetter defines only read-only service methods. type AutoUpdateServiceGetter interface { - // GetAutoUpdateConfig gets the autoupdate configuration from the backend. + // GetAutoUpdateConfig gets the AutoUpdateConfig singleton resource. GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) - // GetAutoUpdateVersion gets the autoupdate version from the backend. + // GetAutoUpdateVersion gets the AutoUpdateVersion singleton resource. GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) } @@ -37,27 +37,27 @@ type AutoUpdateServiceGetter interface { type AutoUpdateService interface { AutoUpdateServiceGetter - // CreateAutoUpdateConfig creates an auto update configuration. + // CreateAutoUpdateConfig creates the AutoUpdateConfig singleton resource. CreateAutoUpdateConfig(ctx context.Context, config *autoupdate.AutoUpdateConfig) (*autoupdate.AutoUpdateConfig, error) - // UpdateAutoUpdateConfig updates an auto update configuration. + // UpdateAutoUpdateConfig updates the AutoUpdateConfig singleton resource. UpdateAutoUpdateConfig(ctx context.Context, config *autoupdate.AutoUpdateConfig) (*autoupdate.AutoUpdateConfig, error) - // UpsertAutoUpdateConfig sets an auto update configuration. + // UpsertAutoUpdateConfig sets the AutoUpdateConfig singleton resource. UpsertAutoUpdateConfig(ctx context.Context, c *autoupdate.AutoUpdateConfig) (*autoupdate.AutoUpdateConfig, error) - // DeleteAutoUpdateConfig deletes the auto update configuration from the backend. + // DeleteAutoUpdateConfig deletes the AutoUpdateConfig singleton resource. DeleteAutoUpdateConfig(ctx context.Context) error - // CreateAutoUpdateVersion creates an auto update version. + // CreateAutoUpdateVersion creates the AutoUpdateVersion singleton resource. CreateAutoUpdateVersion(ctx context.Context, config *autoupdate.AutoUpdateVersion) (*autoupdate.AutoUpdateVersion, error) - // UpdateAutoUpdateVersion updates an auto update version. + // UpdateAutoUpdateVersion updates the AutoUpdateVersion singleton resource. UpdateAutoUpdateVersion(ctx context.Context, config *autoupdate.AutoUpdateVersion) (*autoupdate.AutoUpdateVersion, error) - // UpsertAutoUpdateVersion sets an auto update version. + // UpsertAutoUpdateVersion sets the AutoUpdateVersion singleton resource. UpsertAutoUpdateVersion(ctx context.Context, c *autoupdate.AutoUpdateVersion) (*autoupdate.AutoUpdateVersion, error) - // DeleteAutoUpdateVersion deletes the auto update version from the backend. + // DeleteAutoUpdateVersion deletes the AutoUpdateVersion singleton resource. DeleteAutoUpdateVersion(ctx context.Context) error } diff --git a/lib/services/local/autoupdate.go b/lib/services/local/autoupdate.go index a1a04a38ea65..f6e6a23abd2b 100644 --- a/lib/services/local/autoupdate.go +++ b/lib/services/local/autoupdate.go @@ -36,7 +36,7 @@ const ( autoUpdateVersionPrefix = "auto_update_version" ) -// AutoUpdateService is responsible for managing auto update configuration and version. +// AutoUpdateService is responsible for managing AutoUpdateConfig and AutoUpdateVersion singleton resources. type AutoUpdateService struct { config *generic.ServiceWrapper[*autoupdate.AutoUpdateConfig] version *generic.ServiceWrapper[*autoupdate.AutoUpdateVersion] @@ -81,7 +81,7 @@ func NewAutoUpdateService(backend backend.Backend) (*AutoUpdateService, error) { }, nil } -// CreateAutoUpdateConfig creates an auto update configuration singleton. +// CreateAutoUpdateConfig creates the AutoUpdateConfig singleton resource. func (s *AutoUpdateService) CreateAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, @@ -90,7 +90,7 @@ func (s *AutoUpdateService) CreateAutoUpdateConfig( return config, trace.Wrap(err) } -// UpdateAutoUpdateConfig updates an auto update configuration singleton. +// UpdateAutoUpdateConfig updates the AutoUpdateConfig singleton resource. func (s *AutoUpdateService) UpdateAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, @@ -99,7 +99,7 @@ func (s *AutoUpdateService) UpdateAutoUpdateConfig( return config, trace.Wrap(err) } -// UpsertAutoUpdateConfig sets an auto update configuration. +// UpsertAutoUpdateConfig sets the AutoUpdateConfig singleton resource. func (s *AutoUpdateService) UpsertAutoUpdateConfig( ctx context.Context, c *autoupdate.AutoUpdateConfig, @@ -108,18 +108,18 @@ func (s *AutoUpdateService) UpsertAutoUpdateConfig( return config, trace.Wrap(err) } -// GetAutoUpdateConfig gets the auto update configuration from the backend. +// GetAutoUpdateConfig gets the AutoUpdateConfig singleton resource. func (s *AutoUpdateService) GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdateConfig, error) { config, err := s.config.GetResource(ctx, types.MetaNameAutoUpdateConfig) return config, trace.Wrap(err) } -// DeleteAutoUpdateConfig deletes the auto update configuration from the backend. +// DeleteAutoUpdateConfig deletes the AutoUpdateConfig singleton resource. func (s *AutoUpdateService) DeleteAutoUpdateConfig(ctx context.Context) error { return trace.Wrap(s.config.DeleteResource(ctx, types.MetaNameAutoUpdateConfig)) } -// CreateAutoUpdateVersion creates an autoupdate version resource. +// CreateAutoUpdateVersion creates the AutoUpdateVersion singleton resource. func (s *AutoUpdateService) CreateAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, @@ -128,7 +128,7 @@ func (s *AutoUpdateService) CreateAutoUpdateVersion( return version, trace.Wrap(err) } -// UpdateAutoUpdateVersion updates an autoupdate version resource. +// UpdateAutoUpdateVersion updates the AutoUpdateVersion singleton resource. func (s *AutoUpdateService) UpdateAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, @@ -137,7 +137,7 @@ func (s *AutoUpdateService) UpdateAutoUpdateVersion( return version, trace.Wrap(err) } -// UpsertAutoUpdateVersion sets autoupdate version resource. +// UpsertAutoUpdateVersion sets the AutoUpdateVersion singleton resource. func (s *AutoUpdateService) UpsertAutoUpdateVersion( ctx context.Context, v *autoupdate.AutoUpdateVersion, @@ -146,13 +146,13 @@ func (s *AutoUpdateService) UpsertAutoUpdateVersion( return version, trace.Wrap(err) } -// GetAutoUpdateVersion gets the auto update version from the backend. +// GetAutoUpdateVersion gets the AutoUpdateVersion singleton resource. func (s *AutoUpdateService) GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdateVersion, error) { version, err := s.version.GetResource(ctx, types.MetaNameAutoUpdateVersion) return version, trace.Wrap(err) } -// DeleteAutoUpdateVersion deletes the auto update version from the backend. +// DeleteAutoUpdateVersion deletes the AutoUpdateVersion singleton resource. func (s *AutoUpdateService) DeleteAutoUpdateVersion(ctx context.Context) error { return trace.Wrap(s.version.DeleteResource(ctx, types.MetaNameAutoUpdateVersion)) } diff --git a/lib/services/local/autoupdate_test.go b/lib/services/local/autoupdate_test.go index 6e1a9cc42dd1..d46a40b75647 100644 --- a/lib/services/local/autoupdate_test.go +++ b/lib/services/local/autoupdate_test.go @@ -28,7 +28,7 @@ import ( "github.com/stretchr/testify/require" "google.golang.org/protobuf/testing/protocmp" - autoupdatepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" + autoupdatev1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/autoupdate" @@ -36,7 +36,7 @@ import ( ) // TestAutoUpdateServiceConfigCRUD verifies get/create/update/upsert/delete methods of the backend service -// for auto update config resource. +// for AutoUpdateConfig resource. func TestAutoUpdateServiceConfigCRUD(t *testing.T) { t.Parallel() @@ -47,11 +47,11 @@ func TestAutoUpdateServiceConfigCRUD(t *testing.T) { require.NoError(t, err) ctx := context.Background() - config := &autoupdatepb.AutoUpdateConfig{ + config := &autoupdatev1pb.AutoUpdateConfig{ Kind: types.KindAutoUpdateConfig, Version: types.V1, Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateConfig}, - Spec: &autoupdatepb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + Spec: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, } created, err := service.CreateAutoUpdateConfig(ctx, config) @@ -91,7 +91,7 @@ func TestAutoUpdateServiceConfigCRUD(t *testing.T) { } // TestAutoUpdateServiceVersionCRUD verifies get/create/update/upsert/delete methods of the backend service -// for auto update version resource. +// for AutoUpdateVersion resource. func TestAutoUpdateServiceVersionCRUD(t *testing.T) { t.Parallel() @@ -102,11 +102,11 @@ func TestAutoUpdateServiceVersionCRUD(t *testing.T) { require.NoError(t, err) ctx := context.Background() - version := &autoupdatepb.AutoUpdateVersion{ + version := &autoupdatev1pb.AutoUpdateVersion{ Kind: types.KindAutoUpdateVersion, Version: types.V1, Metadata: &headerv1.Metadata{Name: types.MetaNameAutoUpdateVersion}, - Spec: &autoupdatepb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + Spec: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, } created, err := service.CreateAutoUpdateVersion(ctx, version) @@ -157,22 +157,22 @@ func TestAutoUpdateServiceInvalidNameCreate(t *testing.T) { require.NoError(t, err) ctx := context.Background() - config := &autoupdatepb.AutoUpdateConfig{ + config := &autoupdatev1pb.AutoUpdateConfig{ Kind: types.KindAutoUpdateConfig, Version: types.V1, Metadata: &headerv1.Metadata{Name: "invalid-auto-update-config-name"}, - Spec: &autoupdatepb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, + Spec: &autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: true}, } createdConfig, err := service.CreateAutoUpdateConfig(ctx, config) require.Error(t, err) require.Nil(t, createdConfig) - version := &autoupdatepb.AutoUpdateVersion{ + version := &autoupdatev1pb.AutoUpdateVersion{ Kind: types.KindAutoUpdateVersion, Version: types.V1, Metadata: &headerv1.Metadata{Name: "invalid-auto-update-version-name"}, - Spec: &autoupdatepb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, + Spec: &autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}, } createdVersion, err := service.CreateAutoUpdateVersion(ctx, version) @@ -194,7 +194,7 @@ func TestAutoUpdateServiceInvalidNameUpdate(t *testing.T) { ctx := context.Background() // Validate the config update restriction. - config, err := autoupdate.NewAutoUpdateConfig(&autoupdatepb.AutoUpdateConfigSpec{ToolsAutoupdate: true}) + config, err := autoupdate.NewAutoUpdateConfig(&autoupdatev1pb.AutoUpdateConfigSpec{ToolsAutoupdate: true}) require.NoError(t, err) createdConfig, err := service.UpsertAutoUpdateConfig(ctx, config) @@ -207,7 +207,7 @@ func TestAutoUpdateServiceInvalidNameUpdate(t *testing.T) { require.Nil(t, createdConfig) // Validate the version update restriction. - version, err := autoupdate.NewAutoUpdateVersion(&autoupdatepb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}) + version, err := autoupdate.NewAutoUpdateVersion(&autoupdatev1pb.AutoUpdateVersionSpec{ToolsVersion: "1.2.3"}) require.NoError(t, err) createdVersion, err := service.UpsertAutoUpdateVersion(ctx, version) diff --git a/lib/services/local/events.go b/lib/services/local/events.go index c27a3086eb4b..ce90af3616e6 100644 --- a/lib/services/local/events.go +++ b/lib/services/local/events.go @@ -24,6 +24,7 @@ import ( "github.com/sirupsen/logrus" apidefaults "github.com/gravitational/teleport/api/defaults" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/kubewaitingcontainer" @@ -83,6 +84,10 @@ func (e *EventsService) NewWatcher(ctx context.Context, watch types.Watch) (type parser = newUIConfigParser() case types.KindClusterName: parser = newClusterNameParser() + case types.KindAutoUpdateConfig: + parser = newAutoUpdateConfigParser() + case types.KindAutoUpdateVersion: + parser = newAutoUpdateVersionParser() case types.KindNamespace: parser = newNamespaceParser(kind.Name) case types.KindRole: @@ -666,6 +671,72 @@ func (p *clusterNameParser) parse(event backend.Event) (types.Resource, error) { } } +func newAutoUpdateConfigParser() *autoUpdateConfigParser { + return &autoUpdateConfigParser{ + baseParser: newBaseParser(backend.NewKey(autoUpdateConfigPrefix)), + } +} + +type autoUpdateConfigParser struct { + baseParser +} + +func (p *autoUpdateConfigParser) parse(event backend.Event) (types.Resource, error) { + switch event.Type { + case types.OpDelete: + h, err := resourceHeader(event, types.KindAutoUpdateConfig, types.V1, 0) + if err != nil { + return nil, trace.Wrap(err) + } + h.SetName(types.MetaNameAutoUpdateConfig) + return h, nil + case types.OpPut: + autoUpdateConfig, err := services.UnmarshalProtoResource[*autoupdate.AutoUpdateConfig](event.Item.Value, + services.WithExpires(event.Item.Expires), + services.WithRevision(event.Item.Revision), + ) + if err != nil { + return nil, trace.Wrap(err) + } + return types.Resource153ToLegacy(autoUpdateConfig), nil + default: + return nil, trace.BadParameter("event %v is not supported", event.Type) + } +} + +func newAutoUpdateVersionParser() *autoUpdateVersionParser { + return &autoUpdateVersionParser{ + baseParser: newBaseParser(backend.NewKey(autoUpdateVersionPrefix)), + } +} + +type autoUpdateVersionParser struct { + baseParser +} + +func (p *autoUpdateVersionParser) parse(event backend.Event) (types.Resource, error) { + switch event.Type { + case types.OpDelete: + h, err := resourceHeader(event, types.KindAutoUpdateVersion, types.V1, 0) + if err != nil { + return nil, trace.Wrap(err) + } + h.SetName(types.MetaNameAutoUpdateVersion) + return h, nil + case types.OpPut: + autoUpdateVersion, err := services.UnmarshalProtoResource[*autoupdate.AutoUpdateVersion](event.Item.Value, + services.WithExpires(event.Item.Expires), + services.WithRevision(event.Item.Revision), + ) + if err != nil { + return nil, trace.Wrap(err) + } + return types.Resource153ToLegacy(autoUpdateVersion), nil + default: + return nil, trace.BadParameter("event %v is not supported", event.Type) + } +} + func newNamespaceParser(name string) *namespaceParser { prefix := backend.NewKey(namespacesPrefix) if name != "" {