From f4fc673e45015843fe1c1ad5d2e682482338a255 Mon Sep 17 00:00:00 2001 From: Vadym Popov Date: Wed, 18 Sep 2024 22:44:02 -0400 Subject: [PATCH] Add AutoUpdate Client/Cache implementation (#46661) * 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 | 67 ++++++++++++ lib/cache/cache_test.go | 107 ++++++++++++++++++++ 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, 452 insertions(+), 61 deletions(-) diff --git a/api/client/client.go b/api/client/client.go index 00d068eb37b3c..e5eedbdd9d536 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -66,6 +66,7 @@ import ( accesslistv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accesslist/v1" accessmonitoringrulev1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/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" crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" @@ -2863,6 +2864,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 1317b923ced96..73766c3f64240 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -19,6 +19,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" accessmonitoringrulesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" @@ -99,6 +100,14 @@ func EventToGRPC(in types.Event) (*proto.Event, error) { out.Resource = &proto.Event_StaticHostUserV2{ StaticHostUserV2: r, } + case *autoupdate.AutoUpdateConfig: + out.Resource = &proto.Event_AutoUpdateConfig{ + AutoUpdateConfig: r, + } + case *autoupdate.AutoUpdateVersion: + out.Resource = &proto.Event_AutoUpdateVersion{ + AutoUpdateVersion: r, + } default: return nil, trace.BadParameter("resource type %T is not supported", r) } @@ -542,6 +551,12 @@ func EventFromGRPC(in *proto.Event) (*types.Event, error) { } else if r := in.GetStaticHostUserV2(); 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 4ebf29a536841..2ee33dc5bf2b3 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 5f6729ec42f5b..5fe4f167a037e 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 9fadf17ee8467..f65ce7ffba7dc 100644 --- a/lib/auth/accesspoint/accesspoint.go +++ b/lib/auth/accesspoint/accesspoint.go @@ -103,6 +103,7 @@ type Config struct { WebSession types.WebSessionInterface WebToken types.WebTokenInterface WindowsDesktops services.WindowsDesktops + AutoUpdateService services.AutoUpdateServiceGetter } func (c *Config) CheckAndSetDefaults() error { @@ -167,6 +168,7 @@ func NewCache(cfg Config) (*cache.Cache, error) { AppSession: cfg.AppSession, Apps: cfg.Apps, ClusterConfig: cfg.ClusterConfig, + AutoUpdateService: cfg.AutoUpdateService, CrownJewels: cfg.CrownJewels, DatabaseObjects: cfg.DatabaseObjects, DatabaseServices: cfg.DatabaseServices, diff --git a/lib/auth/authclient/api.go b/lib/auth/authclient/api.go index 83c7f1c9c8c89..edce17d68ccea 100644 --- a/lib/auth/authclient/api.go +++ b/lib/auth/authclient/api.go @@ -28,6 +28,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" accessmonitoringrules "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" @@ -306,6 +307,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. @@ -1176,6 +1183,12 @@ type Cache interface { // DatabaseObjectsGetter defines methods for fetching database objects. services.DatabaseObjectsGetter + // 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) + // GetAccessGraphSettings returns the access graph settings. GetAccessGraphSettings(context.Context) (*clusterconfigpb.AccessGraphSettings, error) diff --git a/lib/auth/authclient/clt.go b/lib/auth/authclient/clt.go index 3e1d4081cf7ad..b7e4f03deaad0 100644 --- a/lib/auth/authclient/clt.go +++ b/lib/auth/authclient/clt.go @@ -1570,6 +1570,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 555a03506d552..b14edeb13d6f9 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 { @@ -113,7 +113,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 { @@ -132,7 +132,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 { @@ -151,7 +151,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 { @@ -172,7 +172,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 { @@ -191,7 +191,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 { @@ -206,11 +206,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 { @@ -225,11 +225,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 { @@ -244,11 +244,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 25feaab22dfb9..c1394e9c4d6dd 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -5429,7 +5429,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 006136e458ee8..83d11f2feed3e 100644 --- a/lib/auth/helpers.go +++ b/lib/auth/helpers.go @@ -324,6 +324,7 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) { AppSession: svces.Identity, Apps: svces.Apps, ClusterConfig: svces.ClusterConfiguration, + AutoUpdateService: svces.AutoUpdateService, CrownJewels: svces.CrownJewels, DatabaseObjects: svces.DatabaseObjects, DatabaseServices: svces.DatabaseServices, diff --git a/lib/authz/permissions.go b/lib/authz/permissions.go index a235e11b8edb1..de3c1c070c1b0 100644 --- a/lib/authz/permissions.go +++ b/lib/authz/permissions.go @@ -913,6 +913,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 9a73ca730b4f7..3690d3e2fd762 100644 --- a/lib/cache/cache.go +++ b/lib/cache/cache.go @@ -38,6 +38,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" accessmonitoringrulesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" @@ -185,6 +186,8 @@ func ForAuth(cfg Config) Config { {Kind: types.KindAccessGraphSettings}, {Kind: types.KindSPIFFEFederation}, {Kind: types.KindStaticHostUser}, + {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 @@ -237,6 +240,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 @@ -491,6 +496,7 @@ type Cache struct { trustCache services.Trust clusterConfigCache services.ClusterConfiguration + autoUpdateCache *local.AutoUpdateService provisionerCache services.Provisioner usersCache services.UsersService accessCache services.Access @@ -639,6 +645,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 @@ -918,6 +926,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++ { @@ -956,6 +970,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), @@ -1881,6 +1896,58 @@ 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 4b1725c66eebf..0d2a43439dee3 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -43,6 +43,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" accessmonitoringrulesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" @@ -53,6 +54,7 @@ import ( userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2" "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/clusterconfig" "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/api/types/header" @@ -131,6 +133,7 @@ type testPack struct { databaseObjects services.DatabaseObjects spiffeFederations *local.SPIFFEFederationService staticHostUsers services.StaticHostUser + autoUpdateService services.AutoUpdateService } // testFuncs are functions to support testing an object in a cache. @@ -360,6 +363,11 @@ func newPackWithoutCache(dir string, opts ...packOption) (*testPack, error) { } p.staticHostUsers = staticHostUserService + p.autoUpdateService, err = local.NewAutoUpdateService(p.backend) + if err != nil { + return nil, trace.Wrap(err) + } + return p, nil } @@ -408,6 +416,7 @@ func newPack(dir string, setupConfig func(c Config) Config, opts ...packOption) SPIFFEFederations: p.spiffeFederations, DatabaseObjects: p.databaseObjects, StaticHostUsers: p.staticHostUsers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -814,6 +823,7 @@ func TestCompletenessInit(t *testing.T) { DatabaseObjects: p.databaseObjects, SPIFFEFederations: p.spiffeFederations, StaticHostUsers: p.staticHostUsers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -893,6 +903,7 @@ func TestCompletenessReset(t *testing.T) { DatabaseObjects: p.databaseObjects, SPIFFEFederations: p.spiffeFederations, StaticHostUsers: p.staticHostUsers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -1098,6 +1109,7 @@ func TestListResources_NodesTTLVariant(t *testing.T) { DatabaseObjects: p.databaseObjects, SPIFFEFederations: p.spiffeFederations, StaticHostUsers: p.staticHostUsers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, neverOK: true, // ensure reads are never healthy @@ -1188,6 +1200,7 @@ func initStrategy(t *testing.T) { DatabaseObjects: p.databaseObjects, SPIFFEFederations: p.spiffeFederations, StaticHostUsers: p.staticHostUsers, + AutoUpdateService: p.autoUpdateService, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -2661,6 +2674,78 @@ func TestDatabaseObjects(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)) + }, + }) +} + // TestGlobalNotifications tests that CRUD operations on global notification resources are // replicated from the backend to the cache. func TestGlobalNotifications(t *testing.T) { @@ -3297,6 +3382,8 @@ func TestCacheWatchKindExistsInEvents(t *testing.T) { types.KindAccessGraphSettings: types.Resource153ToLegacy(newAccessGraphSettings(t)), types.KindSPIFFEFederation: types.Resource153ToLegacy(newSPIFFEFederation("test")), types.KindStaticHostUser: types.Resource153ToLegacy(newStaticHostUser(t, "test")), + types.KindAutoUpdateConfig: types.Resource153ToLegacy(newAutoUpdateConfig(t)), + types.KindAutoUpdateVersion: types.Resource153ToLegacy(newAutoUpdateVersion(t)), } for name, cfg := range cases { @@ -3843,6 +3930,26 @@ func newStaticHostUser(t *testing.T, name string) *userprovisioningpb.StaticHost }) } +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 bb6f43178c3c8..e285f4170b2b6 100644 --- a/lib/cache/collections.go +++ b/lib/cache/collections.go @@ -29,6 +29,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" apidefaults "github.com/gravitational/teleport/api/defaults" accessmonitoringrulesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessmonitoringrules/v1" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" clusterconfigpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/clusterconfig/v1" crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" @@ -254,6 +255,8 @@ type cacheCollections struct { globalNotifications collectionReader[notificationGetter] accessMonitoringRules collectionReader[accessMonitoringRuleGetter] spiffeFederations collectionReader[SPIFFEFederationReader] + autoUpdateConfigs collectionReader[autoUpdateConfigGetter] + autoUpdateVersions collectionReader[autoUpdateVersionGetter] } // setupCollections returns a registry of collections. @@ -764,6 +767,24 @@ func setupCollections(c *Cache, watches []types.WatchKind) (*cacheCollections, e watch: watch, } collections.byKind[resourceKind] = collections.spiffeFederations + 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) } @@ -1269,7 +1290,75 @@ type clusterNameGetter interface { GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) } -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{} diff --git a/lib/service/service.go b/lib/service/service.go index 24c8761891e86..bc91001f6deeb 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -2530,6 +2530,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) } @@ -2573,6 +2574,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 3079f355d7ff8..5fa7a4eed4677 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 a1a04a38ea654..f6e6a23abd2b1 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 0a6f4e5bd2b86..77e13937ac47a 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) @@ -92,7 +92,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() @@ -103,11 +103,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) @@ -159,22 +159,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) @@ -196,7 +196,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) @@ -209,7 +209,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 7a358c3f1f41c..e16d1a4a70335 100644 --- a/lib/services/local/events.go +++ b/lib/services/local/events.go @@ -28,6 +28,7 @@ import ( "github.com/gravitational/teleport" apidefaults "github.com/gravitational/teleport/api/defaults" accessgraphsecretsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessgraph/v1" + "github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/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" @@ -93,6 +94,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: @@ -701,6 +706,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 != "" {