diff --git a/lib/cache/cache.go b/lib/cache/cache.go index ddfce8bc224a4..37520f6488b1b 100644 --- a/lib/cache/cache.go +++ b/lib/cache/cache.go @@ -525,8 +525,11 @@ type Cache struct { // cancel triggers exit context closure cancel context.CancelFunc - // collections is a registry of resource collections - collections *cacheCollections + // legacyCacheCollections is a registry of resource legacyCollections + legacyCacheCollections *legacyCollections + + // collections is a registry of resource collections. + collections *collections // confirmedKinds is a map of kinds confirmed by the server to be included in the current generation // by resource Kind/SubKind @@ -613,18 +616,9 @@ func (c *Cache) setReadStatus(ok bool, confirmedKinds map[resourceKind]types.Wat c.confirmedKinds = confirmedKinds } -// readCollectionCache acquires the cache read lock and uses getReader() to select the appropriate target for read -// operations on resources of the specified collection. The returned guard *must* be released to prevent deadlocks. -func readCollectionCache[R any](cache *Cache, collection collectionReader[R]) (rg readGuard[R], err error) { - if collection == nil { - return rg, trace.BadParameter("cannot read from an uninitialized cache collection") - } - return readCache(cache, collection.watchKind(), collection.getReader) -} - // readListResourcesCache acquires the cache read lock and uses getReader() to select the appropriate target // for listing resources of the specified resourceType. The returned guard *must* be released to prevent deadlocks. -func readListResourcesCache(cache *Cache, resourceType string) (readGuard[resourceGetter], error) { +func readListResourcesCache(cache *Cache, resourceType string) (legacyReadGuard[resourceGetter], error) { getResourceReader := func(cacheOK bool) resourceGetter { if cacheOK { return cache.presenceCache @@ -632,20 +626,53 @@ func readListResourcesCache(cache *Cache, resourceType string) (readGuard[resour return cache.Config.Presence } - return readCache(cache, types.WatchKind{Kind: resourceType}, getResourceReader) + return legacyReadCache(cache, types.WatchKind{Kind: resourceType}, getResourceReader) +} + +// readLegacyCollectionCache acquires the cache read lock and uses getReader() to select the appropriate target for read +// operations on resources of the specified collection. The returned guard *must* be released to prevent deadlocks. +func readLegacyCollectionCache[R any](cache *Cache, collection collectionReader[R]) (legacyReadGuard[R], error) { + if collection == nil { + return legacyReadGuard[R]{}, trace.BadParameter("cannot read from an uninitialized cache collection") + } + return legacyReadCache(cache, collection.watchKind(), collection.getReader) } -// readCache acquires the cache read lock and uses getReader() to select the appropriate target for read operations +// acquireReadGuard provides a readGuard that may be used to determine how +// a cache read should operate. The returned guard *must* be released to prevent deadlocks. +func acquireReadGuard[T any, I comparable](cache *Cache, c *collection[T, I]) (readGuard[T, I], error) { + if cache.closed.Load() { + return readGuard[T, I]{}, trace.Errorf("cache is closed") + } + cache.rw.RLock() + + if cache.ok { + if _, kindOK := cache.confirmedKinds[resourceKind{kind: c.watch.Kind, subkind: c.watch.SubKind}]; kindOK { + return readGuard[T, I]{ + cacheRead: true, + release: cache.rw.RUnlock, + store: c.store, + }, nil + } + } + + cache.rw.RUnlock() + return readGuard[T, I]{ + cacheRead: false, + }, nil +} + +// legacyReadCache acquires the cache read lock and uses getReader() to select the appropriate target for read operations // on resources of the specified kind. The returned guard *must* be released to prevent deadlocks. -func readCache[R any](cache *Cache, kind types.WatchKind, getReader func(cacheOK bool) R) (readGuard[R], error) { +func legacyReadCache[R any](cache *Cache, kind types.WatchKind, getReader func(cacheOK bool) R) (legacyReadGuard[R], error) { if cache.closed.Load() { - return readGuard[R]{}, trace.Errorf("cache is closed") + return legacyReadGuard[R]{}, trace.Errorf("cache is closed") } cache.rw.RLock() if cache.ok { if _, kindOK := cache.confirmedKinds[resourceKind{kind: kind.Kind, subkind: kind.SubKind}]; kindOK { - return readGuard[R]{ + return legacyReadGuard[R]{ reader: getReader(true), release: cache.rw.RUnlock, }, nil @@ -653,34 +680,60 @@ func readCache[R any](cache *Cache, kind types.WatchKind, getReader func(cacheOK } cache.rw.RUnlock() - return readGuard[R]{ + return legacyReadGuard[R]{ reader: getReader(false), release: nil, }, nil } -// readGuard holds a reference to a read-only "backend" R. If the referenced backed is the cache, then readGuard +// legacyReadGuard holds a reference to a read-only "backend" R. If the referenced backed is the cache, then legacyReadGuard // also holds the release function for the read lock, and ensures that it is not double-called. -type readGuard[R any] struct { - reader R - release func() - released bool +type legacyReadGuard[R any] struct { + reader R + once sync.Once + release func() } -// Release releases the read lock if it is held. This method -// can be called multiple times, but is not thread-safe. -func (r *readGuard[R]) Release() { - if r.release != nil && !r.released { +// Release releases the read lock if it is held. This method +// can be called multiple times. +func (r *legacyReadGuard[R]) Release() { + r.once.Do(func() { + if r.release == nil { + return + } + r.release() - r.released = true - } + }) } // IsCacheRead checks if this readGuard holds a cache reference. -func (r *readGuard[R]) IsCacheRead() bool { +func (r *legacyReadGuard[R]) IsCacheRead() bool { return r.release != nil } +type readGuard[T any, I comparable] struct { + cacheRead bool + store *store[T, I] + once sync.Once + release func() +} + +func (r *readGuard[T, I]) ReadCache() bool { + return r.cacheRead +} + +// Release releases the read lock if it is held. This method +// can be called multiple times. +func (r *readGuard[T, I]) Release() { + r.once.Do(func() { + if r.release == nil { + return + } + + r.release() + }) +} + // Config defines cache configuration parameters type Config struct { // target is an identifying string that allows errors to @@ -1143,7 +1196,15 @@ func New(config Config) (*Cache, error) { teleport.ComponentKey: config.Component, }), } - collections, err := setupCollections(cs, config.Watches) + + legacyCollections, err := setupLegacyCollections(cs, config.Watches) + if err != nil { + cs.Close() + return nil, trace.Wrap(err) + } + cs.legacyCacheCollections = legacyCollections + + collections, err := setupCollections(config, legacyCollections.byKind) if err != nil { cs.Close() return nil, trace.Wrap(err) @@ -1376,7 +1437,7 @@ func (c *Cache) notify(ctx context.Context, event Event) { // potentially lagging behind the state of the database. func (c *Cache) fetchAndWatch(ctx context.Context, retry retryutils.Retry, timer *time.Timer) error { cacheLastReset.WithLabelValues(c.target).SetToCurrentTime() - requestKinds := c.watchKinds() + requestKinds := c.Config.Watches watcher, err := c.Events.NewWatcher(c.ctx, types.Watch{ Name: c.Component, Kinds: requestKinds, @@ -1678,14 +1739,6 @@ func (c *Cache) performRelativeNodeExpiry(ctx context.Context) error { return nil } -func (c *Cache) watchKinds() []types.WatchKind { - out := make([]types.WatchKind, 0, len(c.collections.byKind)) - for _, collection := range c.collections.byKind { - out = append(out, collection.watchKind()) - } - return out -} - // isClosing checks if the cache has begun closing. func (c *Cache) isClosing() bool { if c.closed.Load() { @@ -1764,9 +1817,9 @@ func (c *Cache) fetch(ctx context.Context, confirmedKinds map[resourceKind]types g, ctx := errgroup.WithContext(ctx) g.SetLimit(fetchLimit(c.target)) - applyfns := make([]applyFn, len(c.collections.byKind)) + applyfns := make([]applyFn, len(c.legacyCacheCollections.byKind)+len(c.collections.byKind)) i := 0 - for kind, collection := range c.collections.byKind { + for kind, collection := range c.legacyCacheCollections.byKind { kind, collection := kind, collection ii := i i++ @@ -1792,6 +1845,31 @@ func (c *Cache) fetch(ctx context.Context, confirmedKinds map[resourceKind]types }) } + for kind, handler := range c.collections.byKind { + ii := i + i++ + + g.Go(func() (err error) { + ctx, span := c.Tracer.Start( + ctx, + fmt.Sprintf("cache/fetch/%s", kind.String()), + oteltrace.WithAttributes( + attribute.String("target", c.target), + ), + ) + defer func() { apitracing.EndSpan(span, err) }() + + _, cacheOK := confirmedKinds[resourceKind{kind: kind.kind, subkind: kind.subkind}] + applyfn, err := handler.fetch(ctx, cacheOK) + if err != nil { + return trace.Wrap(err, "failed to fetch resource: %q", kind) + } + + applyfns[ii] = tracedApplyFn(fetchSpan, c.Tracer, kind, applyfn) + return nil + }) + } + if err := g.Wait(); err != nil { return nil, trace.Wrap(err) } @@ -1811,13 +1889,31 @@ func (c *Cache) fetch(ctx context.Context, confirmedKinds map[resourceKind]types // the event will be emitted via the fanout. func (c *Cache) processEvent(ctx context.Context, event types.Event) error { resourceKind := resourceKindFromResource(event.Resource) - collection, ok := c.collections.byKind[resourceKind] - if !ok { - c.Logger.Warnf("Skipping unsupported event %v/%v", event.Resource.GetKind(), event.Resource.GetSubKind()) - return nil - } - if err := collection.processEvent(ctx, event); err != nil { - return trace.Wrap(err) + + legacyCollection, legacyFound := c.legacyCacheCollections.byKind[resourceKind] + handler, handlerFound := c.collections.byKind[resourceKind] + + switch { + case legacyFound: + if err := legacyCollection.processEvent(ctx, event); err != nil { + return trace.Wrap(err) + } + case handlerFound: + switch event.Type { + case types.OpDelete: + if err := handler.onDelete(event.Resource); err != nil { + if !trace.IsNotFound(err) { + c.Logger.Warnf("Failed to delete resource: %v", err) + return trace.Wrap(err) + } + } + case types.OpPut: + if err := handler.onPut(event.Resource); err != nil { + return trace.Wrap(err) + } + default: + c.Logger.Warnf("Skipping unsupported event type %T", event.Type) + } } c.eventsFanout.Emit(event) @@ -1840,7 +1936,7 @@ func (c *Cache) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadS ctx, span := c.Tracer.Start(ctx, "cache/GetCertAuthority") defer span.End() - rg, err := readCollectionCache(c, c.collections.certAuthorities) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.certAuthorities) if err != nil { return nil, trace.Wrap(err) } @@ -1880,7 +1976,7 @@ func (c *Cache) GetCertAuthorities(ctx context.Context, caType types.CertAuthTyp ctx, span := c.Tracer.Start(ctx, "cache/GetCertAuthorities") defer span.End() - rg, err := readCollectionCache(c, c.collections.certAuthorities) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.certAuthorities) if err != nil { return nil, trace.Wrap(err) } @@ -1907,7 +2003,7 @@ func (c *Cache) GetStaticTokens() (types.StaticTokens, error) { _, span := c.Tracer.Start(context.TODO(), "cache/GetStaticTokens") defer span.End() - rg, err := readCollectionCache(c, c.collections.staticTokens) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.staticTokens) if err != nil { return nil, trace.Wrap(err) } @@ -1920,7 +2016,7 @@ func (c *Cache) GetTokens(ctx context.Context) ([]types.ProvisionToken, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetTokens") defer span.End() - rg, err := readCollectionCache(c, c.collections.tokens) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.tokens) if err != nil { return nil, trace.Wrap(err) } @@ -1933,7 +2029,7 @@ func (c *Cache) GetToken(ctx context.Context, name string) (types.ProvisionToken ctx, span := c.Tracer.Start(ctx, "cache/GetToken") defer span.End() - rg, err := readCollectionCache(c, c.collections.tokens) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.tokens) if err != nil { return nil, trace.Wrap(err) } @@ -1961,7 +2057,7 @@ func (c *Cache) GetClusterAuditConfig(ctx context.Context) (types.ClusterAuditCo ctx, span := c.Tracer.Start(ctx, "cache/GetClusterAuditConfig") defer span.End() - rg, err := readCollectionCache(c, c.collections.clusterAuditConfigs) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.clusterAuditConfigs) if err != nil { return nil, trace.Wrap(err) } @@ -1984,7 +2080,7 @@ func (c *Cache) GetClusterNetworkingConfig(ctx context.Context) (types.ClusterNe ctx, span := c.Tracer.Start(ctx, "cache/GetClusterNetworkingConfig") defer span.End() - rg, err := readCollectionCache(c, c.collections.clusterNetworkingConfigs) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.clusterNetworkingConfigs) if err != nil { return nil, trace.Wrap(err) } @@ -2007,7 +2103,7 @@ func (c *Cache) GetClusterName(opts ...services.MarshalOption) (types.ClusterNam ctx, span := c.Tracer.Start(context.TODO(), "cache/GetClusterName") defer span.End() - rg, err := readCollectionCache(c, c.collections.clusterNames) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.clusterNames) if err != nil { return nil, trace.Wrap(err) } @@ -2034,7 +2130,7 @@ func (c *Cache) GetAutoUpdateConfig(ctx context.Context) (*autoupdate.AutoUpdate ctx, span := c.Tracer.Start(ctx, "cache/GetAutoUpdateConfig") defer span.End() - rg, err := readCollectionCache(c, c.collections.autoUpdateConfigs) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.autoUpdateConfigs) if err != nil { return nil, trace.Wrap(err) } @@ -2057,7 +2153,7 @@ func (c *Cache) GetAutoUpdateVersion(ctx context.Context) (*autoupdate.AutoUpdat ctx, span := c.Tracer.Start(ctx, "cache/GetAutoUpdateVersion") defer span.End() - rg, err := readCollectionCache(c, c.collections.autoUpdateVersions) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.autoUpdateVersions) if err != nil { return nil, trace.Wrap(err) } @@ -2080,7 +2176,7 @@ func (c *Cache) GetAutoUpdateAgentRollout(ctx context.Context) (*autoupdate.Auto ctx, span := c.Tracer.Start(ctx, "cache/GetAutoUpdateAgentRollout") defer span.End() - rg, err := readCollectionCache(c, c.collections.autoUpdateAgentRollouts) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.autoUpdateAgentRollouts) if err != nil { return nil, trace.Wrap(err) } @@ -2102,7 +2198,7 @@ func (c *Cache) GetUIConfig(ctx context.Context) (types.UIConfig, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetUIConfig") defer span.End() - rg, err := readCollectionCache(c, c.collections.uiConfigs) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.uiConfigs) if err != nil { return nil, trace.Wrap(err) } @@ -2117,7 +2213,7 @@ func (c *Cache) GetInstaller(ctx context.Context, name string) (types.Installer, ctx, span := c.Tracer.Start(ctx, "cache/GetInstaller") defer span.End() - rg, err := readCollectionCache(c, c.collections.installers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.installers) if err != nil { return nil, trace.Wrap(err) } @@ -2132,7 +2228,7 @@ func (c *Cache) GetInstallers(ctx context.Context) ([]types.Installer, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetInstallers") defer span.End() - rg, err := readCollectionCache(c, c.collections.installers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.installers) if err != nil { return nil, trace.Wrap(err) } @@ -2147,7 +2243,7 @@ func (c *Cache) GetRoles(ctx context.Context) ([]types.Role, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetRoles") defer span.End() - rg, err := readCollectionCache(c, c.collections.roles) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.roles) if err != nil { return nil, trace.Wrap(err) } @@ -2160,7 +2256,7 @@ func (c *Cache) ListRoles(ctx context.Context, req *proto.ListRolesRequest) (*pr ctx, span := c.Tracer.Start(ctx, "cache/ListRoles") defer span.End() - rg, err := readCollectionCache(c, c.collections.roles) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.roles) if err != nil { return nil, trace.Wrap(err) } @@ -2173,7 +2269,7 @@ func (c *Cache) GetRole(ctx context.Context, name string) (types.Role, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetRole") defer span.End() - rg, err := readCollectionCache(c, c.collections.roles) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.roles) if err != nil { return nil, trace.Wrap(err) } @@ -2196,7 +2292,7 @@ func (c *Cache) GetNamespace(name string) (*types.Namespace, error) { _, span := c.Tracer.Start(context.TODO(), "cache/GetNamespace") defer span.End() - rg, err := readCollectionCache(c, c.collections.namespaces) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.namespaces) if err != nil { return nil, trace.Wrap(err) } @@ -2209,7 +2305,7 @@ func (c *Cache) GetNamespaces() ([]types.Namespace, error) { _, span := c.Tracer.Start(context.TODO(), "cache/GetNamespaces") defer span.End() - rg, err := readCollectionCache(c, c.collections.namespaces) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.namespaces) if err != nil { return nil, trace.Wrap(err) } @@ -2222,7 +2318,7 @@ func (c *Cache) GetNode(ctx context.Context, namespace, name string) (types.Serv ctx, span := c.Tracer.Start(ctx, "cache/GetNode") defer span.End() - rg, err := readCollectionCache(c, c.collections.nodes) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.nodes) if err != nil { return nil, trace.Wrap(err) } @@ -2239,7 +2335,7 @@ func (c *Cache) GetNodes(ctx context.Context, namespace string) ([]types.Server, ctx, span := c.Tracer.Start(ctx, "cache/GetNodes") defer span.End() - rg, err := readCollectionCache(c, c.collections.nodes) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.nodes) if err != nil { return nil, trace.Wrap(err) } @@ -2278,7 +2374,7 @@ func (c *Cache) GetAuthServers() ([]types.Server, error) { _, span := c.Tracer.Start(context.TODO(), "cache/GetAuthServers") defer span.End() - rg, err := readCollectionCache(c, c.collections.authServers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.authServers) if err != nil { return nil, trace.Wrap(err) } @@ -2291,7 +2387,7 @@ func (c *Cache) GetProxies() ([]types.Server, error) { _, span := c.Tracer.Start(context.TODO(), "cache/GetProxies") defer span.End() - rg, err := readCollectionCache(c, c.collections.proxies) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.proxies) if err != nil { return nil, trace.Wrap(err) } @@ -2308,7 +2404,7 @@ func (c *Cache) GetRemoteClusters(ctx context.Context) ([]types.RemoteCluster, e ctx, span := c.Tracer.Start(ctx, "cache/GetRemoteClusters") defer span.End() - rg, err := readCollectionCache(c, c.collections.remoteClusters) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.remoteClusters) if err != nil { return nil, trace.Wrap(err) } @@ -2336,7 +2432,7 @@ func (c *Cache) GetRemoteCluster(ctx context.Context, clusterName string) (types ctx, span := c.Tracer.Start(ctx, "cache/GetRemoteCluster") defer span.End() - rg, err := readCollectionCache(c, c.collections.remoteClusters) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.remoteClusters) if err != nil { return nil, trace.Wrap(err) } @@ -2370,7 +2466,7 @@ func (c *Cache) ListRemoteClusters(ctx context.Context, pageSize int, nextToken _, span := c.Tracer.Start(ctx, "cache/ListRemoteClusters") defer span.End() - rg, err := readCollectionCache(c, c.collections.remoteClusters) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.remoteClusters) if err != nil { return nil, "", trace.Wrap(err) } @@ -2387,7 +2483,7 @@ func (c *Cache) GetUser(ctx context.Context, name string, withSecrets bool) (typ if withSecrets { // cache never tracks user secrets return c.Config.Users.GetUser(ctx, name, withSecrets) } - rg, err := readCollectionCache(c, c.collections.users) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.users) if err != nil { return nil, trace.Wrap(err) } @@ -2414,7 +2510,7 @@ func (c *Cache) GetUsers(ctx context.Context, withSecrets bool) ([]types.User, e if withSecrets { // cache never tracks user secrets return c.Users.GetUsers(ctx, withSecrets) } - rg, err := readCollectionCache(c, c.collections.users) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.users) if err != nil { return nil, trace.Wrap(err) } @@ -2431,7 +2527,7 @@ func (c *Cache) ListUsers(ctx context.Context, req *userspb.ListUsersRequest) (* rsp, err := c.Users.ListUsers(ctx, req) return rsp, trace.Wrap(err) } - rg, err := readCollectionCache(c, c.collections.users) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.users) if err != nil { return nil, trace.Wrap(err) } @@ -2445,7 +2541,7 @@ func (c *Cache) GetTunnelConnections(clusterName string, opts ...services.Marsha _, span := c.Tracer.Start(context.TODO(), "cache/GetTunnelConnections") defer span.End() - rg, err := readCollectionCache(c, c.collections.tunnelConnections) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.tunnelConnections) if err != nil { return nil, trace.Wrap(err) } @@ -2458,7 +2554,7 @@ func (c *Cache) GetAllTunnelConnections(opts ...services.MarshalOption) (conns [ _, span := c.Tracer.Start(context.TODO(), "cache/GetAllTunnelConnections") defer span.End() - rg, err := readCollectionCache(c, c.collections.tunnelConnections) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.tunnelConnections) if err != nil { return nil, trace.Wrap(err) } @@ -2471,7 +2567,7 @@ func (c *Cache) GetKubernetesServers(ctx context.Context) ([]types.KubeServer, e ctx, span := c.Tracer.Start(ctx, "cache/GetKubernetesServers") defer span.End() - rg, err := readCollectionCache(c, c.collections.kubeServers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.kubeServers) if err != nil { return nil, trace.Wrap(err) } @@ -2486,7 +2582,7 @@ func (c *Cache) ListKubernetesWaitingContainers(ctx context.Context, pageSize in ctx, span := c.Tracer.Start(ctx, "cache/ListKubernetesWaitingContainers") defer span.End() - rg, err := readCollectionCache(c, c.collections.kubeWaitingContainers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.kubeWaitingContainers) if err != nil { return nil, "", trace.Wrap(err) } @@ -2501,7 +2597,7 @@ func (c *Cache) GetKubernetesWaitingContainer(ctx context.Context, req *kubewait ctx, span := c.Tracer.Start(ctx, "cache/GetKubernetesWaitingContainer") defer span.End() - rg, err := readCollectionCache(c, c.collections.kubeWaitingContainers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.kubeWaitingContainers) if err != nil { return nil, trace.Wrap(err) } @@ -2514,7 +2610,7 @@ func (c *Cache) ListStaticHostUsers(ctx context.Context, pageSize int, pageToken ctx, span := c.Tracer.Start(ctx, "cache/ListStaticHostUsers") defer span.End() - rg, err := readCollectionCache(c, c.collections.staticHostUsers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.staticHostUsers) if err != nil { return nil, "", trace.Wrap(err) } @@ -2527,7 +2623,7 @@ func (c *Cache) GetStaticHostUser(ctx context.Context, name string) (*userprovis ctx, span := c.Tracer.Start(ctx, "cache/GetStaticHostUser") defer span.End() - rg, err := readCollectionCache(c, c.collections.staticHostUsers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.staticHostUsers) if err != nil { return nil, trace.Wrap(err) } @@ -2540,7 +2636,7 @@ func (c *Cache) GetApplicationServers(ctx context.Context, namespace string) ([] ctx, span := c.Tracer.Start(ctx, "cache/GetApplicationServers") defer span.End() - rg, err := readCollectionCache(c, c.collections.appServers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.appServers) if err != nil { return nil, trace.Wrap(err) } @@ -2553,7 +2649,7 @@ func (c *Cache) GetKubernetesClusters(ctx context.Context) ([]types.KubeCluster, ctx, span := c.Tracer.Start(ctx, "cache/GetKubernetesClusters") defer span.End() - rg, err := readCollectionCache(c, c.collections.kubeClusters) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.kubeClusters) if err != nil { return nil, trace.Wrap(err) } @@ -2566,7 +2662,7 @@ func (c *Cache) GetKubernetesCluster(ctx context.Context, name string) (types.Ku ctx, span := c.Tracer.Start(ctx, "cache/GetKubernetesCluster") defer span.End() - rg, err := readCollectionCache(c, c.collections.kubeClusters) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.kubeClusters) if err != nil { return nil, trace.Wrap(err) } @@ -2579,7 +2675,7 @@ func (c *Cache) GetApps(ctx context.Context) ([]types.Application, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetApps") defer span.End() - rg, err := readCollectionCache(c, c.collections.apps) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.apps) if err != nil { return nil, trace.Wrap(err) } @@ -2592,7 +2688,7 @@ func (c *Cache) GetApp(ctx context.Context, name string) (types.Application, err ctx, span := c.Tracer.Start(ctx, "cache/GetApp") defer span.End() - rg, err := readCollectionCache(c, c.collections.apps) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.apps) if err != nil { return nil, trace.Wrap(err) } @@ -2605,7 +2701,7 @@ func (c *Cache) GetAppSession(ctx context.Context, req types.GetAppSessionReques ctx, span := c.Tracer.Start(ctx, "cache/GetAppSession") defer span.End() - rg, err := readCollectionCache(c, c.collections.appSessions) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.appSessions) if err != nil { return nil, trace.Wrap(err) } @@ -2630,7 +2726,7 @@ func (c *Cache) ListAppSessions(ctx context.Context, pageSize int, pageToken, us ctx, span := c.Tracer.Start(ctx, "cache/ListAppSessions") defer span.End() - rg, err := readCollectionCache(c, c.collections.appSessions) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.appSessions) if err != nil { return nil, "", trace.Wrap(err) } @@ -2643,7 +2739,7 @@ func (c *Cache) GetSnowflakeSession(ctx context.Context, req types.GetSnowflakeS ctx, span := c.Tracer.Start(ctx, "cache/GetSnowflakeSession") defer span.End() - rg, err := readCollectionCache(c, c.collections.snowflakeSessions) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.snowflakeSessions) if err != nil { return nil, trace.Wrap(err) } @@ -2669,7 +2765,7 @@ func (c *Cache) GetSAMLIdPSession(ctx context.Context, req types.GetSAMLIdPSessi ctx, span := c.Tracer.Start(ctx, "cache/GetSAMLIdPSession") defer span.End() - rg, err := readCollectionCache(c, c.collections.samlIdPSessions) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.samlIdPSessions) if err != nil { return nil, trace.Wrap(err) } @@ -2695,7 +2791,7 @@ func (c *Cache) GetDatabaseServers(ctx context.Context, namespace string, opts . ctx, span := c.Tracer.Start(ctx, "cache/GetDatabaseServers") defer span.End() - rg, err := readCollectionCache(c, c.collections.databaseServers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.databaseServers) if err != nil { return nil, trace.Wrap(err) } @@ -2708,7 +2804,7 @@ func (c *Cache) GetDatabases(ctx context.Context) ([]types.Database, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetDatabases") defer span.End() - rg, err := readCollectionCache(c, c.collections.databases) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.databases) if err != nil { return nil, trace.Wrap(err) } @@ -2720,7 +2816,7 @@ func (c *Cache) GetDatabaseObject(ctx context.Context, name string) (*dbobjectv1 ctx, span := c.Tracer.Start(ctx, "cache/GetDatabaseObject") defer span.End() - rg, err := readCollectionCache(c, c.collections.databaseObjects) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.databaseObjects) if err != nil { return nil, trace.Wrap(err) } @@ -2732,7 +2828,7 @@ func (c *Cache) ListDatabaseObjects(ctx context.Context, size int, pageToken str ctx, span := c.Tracer.Start(ctx, "cache/ListWindowsDesktopServices") defer span.End() - rg, err := readCollectionCache(c, c.collections.databaseObjects) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.databaseObjects) if err != nil { return nil, "", trace.Wrap(err) } @@ -2745,7 +2841,7 @@ func (c *Cache) GetDatabase(ctx context.Context, name string) (types.Database, e ctx, span := c.Tracer.Start(ctx, "cache/GetDatabase") defer span.End() - rg, err := readCollectionCache(c, c.collections.databases) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.databases) if err != nil { return nil, trace.Wrap(err) } @@ -2758,7 +2854,7 @@ func (c *Cache) GetWebSession(ctx context.Context, req types.GetWebSessionReques ctx, span := c.Tracer.Start(ctx, "cache/GetWebSession") defer span.End() - rg, err := readCollectionCache(c, c.collections.webSessions) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.webSessions) if err != nil { return nil, trace.Wrap(err) } @@ -2784,7 +2880,7 @@ func (c *Cache) GetWebToken(ctx context.Context, req types.GetWebTokenRequest) ( ctx, span := c.Tracer.Start(ctx, "cache/GetWebToken") defer span.End() - rg, err := readCollectionCache(c, c.collections.webTokens) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.webTokens) if err != nil { return nil, trace.Wrap(err) } @@ -2797,7 +2893,7 @@ func (c *Cache) GetAuthPreference(ctx context.Context) (types.AuthPreference, er ctx, span := c.Tracer.Start(ctx, "cache/GetAuthPreference") defer span.End() - rg, err := readCollectionCache(c, c.collections.authPreferences) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.authPreferences) if err != nil { return nil, trace.Wrap(err) } @@ -2810,7 +2906,7 @@ func (c *Cache) GetSessionRecordingConfig(ctx context.Context) (types.SessionRec ctx, span := c.Tracer.Start(ctx, "cache/GetSessionRecordingConfig") defer span.End() - rg, err := readCollectionCache(c, c.collections.sessionRecordingConfigs) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.sessionRecordingConfigs) if err != nil { return nil, trace.Wrap(err) } @@ -2823,7 +2919,7 @@ func (c *Cache) GetNetworkRestrictions(ctx context.Context) (types.NetworkRestri ctx, span := c.Tracer.Start(ctx, "cache/GetNetworkRestrictions") defer span.End() - rg, err := readCollectionCache(c, c.collections.networkRestrictions) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.networkRestrictions) if err != nil { return nil, trace.Wrap(err) } @@ -2837,7 +2933,7 @@ func (c *Cache) GetLock(ctx context.Context, name string) (types.Lock, error) { ctx, span := c.Tracer.Start(ctx, "cache/GetLock") defer span.End() - rg, err := readCollectionCache(c, c.collections.locks) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.locks) if err != nil { return nil, trace.Wrap(err) } @@ -2862,7 +2958,7 @@ func (c *Cache) GetLocks(ctx context.Context, inForceOnly bool, targets ...types ctx, span := c.Tracer.Start(ctx, "cache/GetLocks") defer span.End() - rg, err := readCollectionCache(c, c.collections.locks) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.locks) if err != nil { return nil, trace.Wrap(err) } @@ -2875,7 +2971,7 @@ func (c *Cache) GetWindowsDesktopServices(ctx context.Context) ([]types.WindowsD ctx, span := c.Tracer.Start(ctx, "cache/GetWindowsDesktopServices") defer span.End() - rg, err := readCollectionCache(c, c.collections.windowsDesktopServices) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.windowsDesktopServices) if err != nil { return nil, trace.Wrap(err) } @@ -2888,7 +2984,7 @@ func (c *Cache) GetWindowsDesktopService(ctx context.Context, name string) (type ctx, span := c.Tracer.Start(ctx, "cache/GetWindowsDesktopService") defer span.End() - rg, err := readCollectionCache(c, c.collections.windowsDesktopServices) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.windowsDesktopServices) if err != nil { return nil, trace.Wrap(err) } @@ -2901,7 +2997,7 @@ func (c *Cache) GetWindowsDesktops(ctx context.Context, filter types.WindowsDesk ctx, span := c.Tracer.Start(ctx, "cache/GetWindowsDesktops") defer span.End() - rg, err := readCollectionCache(c, c.collections.windowsDesktops) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.windowsDesktops) if err != nil { return nil, trace.Wrap(err) } @@ -2914,7 +3010,7 @@ func (c *Cache) ListWindowsDesktops(ctx context.Context, req types.ListWindowsDe ctx, span := c.Tracer.Start(ctx, "cache/ListWindowsDesktops") defer span.End() - rg, err := readCollectionCache(c, c.collections.windowsDesktops) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.windowsDesktops) if err != nil { return nil, trace.Wrap(err) } @@ -2927,7 +3023,7 @@ func (c *Cache) ListWindowsDesktopServices(ctx context.Context, req types.ListWi ctx, span := c.Tracer.Start(ctx, "cache/ListWindowsDesktopServices") defer span.End() - rg, err := readCollectionCache(c, c.collections.windowsDesktopServices) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.windowsDesktopServices) if err != nil { return nil, trace.Wrap(err) } @@ -2940,7 +3036,7 @@ func (c *Cache) GetDynamicWindowsDesktop(ctx context.Context, name string) (type ctx, span := c.Tracer.Start(ctx, "cache/GetDynamicWindowsDesktop") defer span.End() - rg, err := readCollectionCache(c, c.collections.dynamicWindowsDesktops) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.dynamicWindowsDesktops) if err != nil { return nil, trace.Wrap(err) } @@ -2953,7 +3049,7 @@ func (c *Cache) ListDynamicWindowsDesktops(ctx context.Context, pageSize int, ne ctx, span := c.Tracer.Start(ctx, "cache/ListDynamicWindowsDesktops") defer span.End() - rg, err := readCollectionCache(c, c.collections.dynamicWindowsDesktops) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.dynamicWindowsDesktops) if err != nil { return nil, "", trace.Wrap(err) } @@ -2966,7 +3062,7 @@ func (c *Cache) ListSAMLIdPServiceProviders(ctx context.Context, pageSize int, n ctx, span := c.Tracer.Start(ctx, "cache/ListSAMLIdPServiceProviders") defer span.End() - rg, err := readCollectionCache(c, c.collections.samlIdPServiceProviders) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.samlIdPServiceProviders) if err != nil { return nil, "", trace.Wrap(err) } @@ -2979,7 +3075,7 @@ func (c *Cache) GetSAMLIdPServiceProvider(ctx context.Context, name string) (typ ctx, span := c.Tracer.Start(ctx, "cache/GetSAMLIdPServiceProvider") defer span.End() - rg, err := readCollectionCache(c, c.collections.samlIdPServiceProviders) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.samlIdPServiceProviders) if err != nil { return nil, trace.Wrap(err) } @@ -2992,7 +3088,7 @@ func (c *Cache) ListUserGroups(ctx context.Context, pageSize int, nextKey string ctx, span := c.Tracer.Start(ctx, "cache/ListUserGroups") defer span.End() - rg, err := readCollectionCache(c, c.collections.userGroups) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.userGroups) if err != nil { return nil, "", trace.Wrap(err) } @@ -3005,7 +3101,7 @@ func (c *Cache) GetUserGroup(ctx context.Context, name string) (types.UserGroup, ctx, span := c.Tracer.Start(ctx, "cache/GetUserGroup") defer span.End() - rg, err := readCollectionCache(c, c.collections.userGroups) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.userGroups) if err != nil { return nil, trace.Wrap(err) } @@ -3018,7 +3114,7 @@ func (c *Cache) ListOktaImportRules(ctx context.Context, pageSize int, nextKey s ctx, span := c.Tracer.Start(ctx, "cache/ListOktaImportRules") defer span.End() - rg, err := readCollectionCache(c, c.collections.oktaImportRules) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.oktaImportRules) if err != nil { return nil, "", trace.Wrap(err) } @@ -3031,7 +3127,7 @@ func (c *Cache) GetOktaImportRule(ctx context.Context, name string) (types.OktaI ctx, span := c.Tracer.Start(ctx, "cache/GetOktaImportRule") defer span.End() - rg, err := readCollectionCache(c, c.collections.oktaImportRules) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.oktaImportRules) if err != nil { return nil, trace.Wrap(err) } @@ -3044,7 +3140,7 @@ func (c *Cache) ListOktaAssignments(ctx context.Context, pageSize int, nextKey s ctx, span := c.Tracer.Start(ctx, "cache/ListOktaAssignments") defer span.End() - rg, err := readCollectionCache(c, c.collections.oktaAssignments) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.oktaAssignments) if err != nil { return nil, "", trace.Wrap(err) } @@ -3057,7 +3153,7 @@ func (c *Cache) GetOktaAssignment(ctx context.Context, name string) (types.OktaA ctx, span := c.Tracer.Start(ctx, "cache/GetOktaAssignment") defer span.End() - rg, err := readCollectionCache(c, c.collections.oktaAssignments) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.oktaAssignments) if err != nil { return nil, trace.Wrap(err) } @@ -3070,7 +3166,7 @@ func (c *Cache) ListIntegrations(ctx context.Context, pageSize int, nextKey stri ctx, span := c.Tracer.Start(ctx, "cache/ListIntegrations") defer span.End() - rg, err := readCollectionCache(c, c.collections.integrations) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.integrations) if err != nil { return nil, "", trace.Wrap(err) } @@ -3083,7 +3179,7 @@ func (c *Cache) GetIntegration(ctx context.Context, name string) (types.Integrat ctx, span := c.Tracer.Start(ctx, "cache/GetIntegration") defer span.End() - rg, err := readCollectionCache(c, c.collections.integrations) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.integrations) if err != nil { return nil, trace.Wrap(err) } @@ -3096,7 +3192,7 @@ func (c *Cache) ListUserTasks(ctx context.Context, pageSize int64, nextKey strin ctx, span := c.Tracer.Start(ctx, "cache/ListUserTasks") defer span.End() - rg, err := readCollectionCache(c, c.collections.userTasks) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.userTasks) if err != nil { return nil, "", trace.Wrap(err) } @@ -3109,7 +3205,7 @@ func (c *Cache) GetUserTask(ctx context.Context, name string) (*usertasksv1.User ctx, span := c.Tracer.Start(ctx, "cache/GetUserTask") defer span.End() - rg, err := readCollectionCache(c, c.collections.userTasks) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.userTasks) if err != nil { return nil, trace.Wrap(err) } @@ -3122,7 +3218,7 @@ func (c *Cache) ListDiscoveryConfigs(ctx context.Context, pageSize int, nextKey ctx, span := c.Tracer.Start(ctx, "cache/ListDiscoveryConfigs") defer span.End() - rg, err := readCollectionCache(c, c.collections.discoveryConfigs) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.discoveryConfigs) if err != nil { return nil, "", trace.Wrap(err) } @@ -3135,7 +3231,7 @@ func (c *Cache) GetDiscoveryConfig(ctx context.Context, name string) (*discovery ctx, span := c.Tracer.Start(ctx, "cache/GetDiscoveryConfig") defer span.End() - rg, err := readCollectionCache(c, c.collections.discoveryConfigs) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.discoveryConfigs) if err != nil { return nil, trace.Wrap(err) } @@ -3148,7 +3244,7 @@ func (c *Cache) ListCrownJewels(ctx context.Context, pageSize int64, nextKey str ctx, span := c.Tracer.Start(ctx, "cache/ListCrownJewels") defer span.End() - rg, err := readCollectionCache(c, c.collections.crownJewels) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.crownJewels) if err != nil { return nil, "", trace.Wrap(err) } @@ -3161,7 +3257,7 @@ func (c *Cache) GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.C ctx, span := c.Tracer.Start(ctx, "cache/GetCrownJewel") defer span.End() - rg, err := readCollectionCache(c, c.collections.crownJewels) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.crownJewels) if err != nil { return nil, trace.Wrap(err) } @@ -3174,7 +3270,7 @@ func (c *Cache) GetSecurityAuditQuery(ctx context.Context, name string) (*secrep ctx, span := c.Tracer.Start(ctx, "cache/GetSecurityAuditQuery") defer span.End() - rg, err := readCollectionCache(c, c.collections.auditQueries) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.auditQueries) if err != nil { return nil, trace.Wrap(err) } @@ -3187,7 +3283,7 @@ func (c *Cache) GetSecurityAuditQueries(ctx context.Context) ([]*secreports.Audi ctx, span := c.Tracer.Start(ctx, "cache/GetSecurityAuditQueries") defer span.End() - rg, err := readCollectionCache(c, c.collections.auditQueries) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.auditQueries) if err != nil { return nil, trace.Wrap(err) } @@ -3200,7 +3296,7 @@ func (c *Cache) ListSecurityAuditQueries(ctx context.Context, pageSize int, next ctx, span := c.Tracer.Start(ctx, "cache/ListSecurityAuditQueries") defer span.End() - rg, err := readCollectionCache(c, c.collections.auditQueries) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.auditQueries) if err != nil { return nil, "", trace.Wrap(err) } @@ -3213,7 +3309,7 @@ func (c *Cache) GetSecurityReport(ctx context.Context, name string) (*secreports ctx, span := c.Tracer.Start(ctx, "cache/GetSecurityReport") defer span.End() - rg, err := readCollectionCache(c, c.collections.secReports) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.secReports) if err != nil { return nil, trace.Wrap(err) } @@ -3226,7 +3322,7 @@ func (c *Cache) GetSecurityReports(ctx context.Context) ([]*secreports.Report, e ctx, span := c.Tracer.Start(ctx, "cache/GetSecurityReports") defer span.End() - rg, err := readCollectionCache(c, c.collections.secReports) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.secReports) if err != nil { return nil, trace.Wrap(err) } @@ -3239,7 +3335,7 @@ func (c *Cache) ListSecurityReports(ctx context.Context, pageSize int, nextKey s ctx, span := c.Tracer.Start(ctx, "cache/ListSecurityReports") defer span.End() - rg, err := readCollectionCache(c, c.collections.secReports) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.secReports) if err != nil { return nil, "", trace.Wrap(err) } @@ -3252,7 +3348,7 @@ func (c *Cache) GetSecurityReportState(ctx context.Context, name string) (*secre ctx, span := c.Tracer.Start(ctx, "cache/GetSecurityReportState") defer span.End() - rg, err := readCollectionCache(c, c.collections.secReportsStates) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.secReportsStates) if err != nil { return nil, trace.Wrap(err) } @@ -3265,7 +3361,7 @@ func (c *Cache) GetSecurityReportsStates(ctx context.Context) ([]*secreports.Rep ctx, span := c.Tracer.Start(ctx, "cache/GetSecurityReportsStates") defer span.End() - rg, err := readCollectionCache(c, c.collections.secReportsStates) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.secReportsStates) if err != nil { return nil, trace.Wrap(err) } @@ -3278,7 +3374,7 @@ func (c *Cache) ListSecurityReportsStates(ctx context.Context, pageSize int, nex ctx, span := c.Tracer.Start(ctx, "cache/ListSecurityReportsStates") defer span.End() - rg, err := readCollectionCache(c, c.collections.secReportsStates) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.secReportsStates) if err != nil { return nil, "", trace.Wrap(err) } @@ -3291,7 +3387,7 @@ func (c *Cache) GetUserLoginStates(ctx context.Context) ([]*userloginstate.UserL ctx, span := c.Tracer.Start(ctx, "cache/GetUserLoginStates") defer span.End() - rg, err := readCollectionCache(c, c.collections.userLoginStates) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.userLoginStates) if err != nil { return nil, trace.Wrap(err) } @@ -3304,7 +3400,7 @@ func (c *Cache) GetUserLoginState(ctx context.Context, name string) (*userlogins ctx, span := c.Tracer.Start(ctx, "cache/GetUserLoginState") defer span.End() - rg, err := readCollectionCache(c, c.collections.userLoginStates) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.userLoginStates) if err != nil { return nil, trace.Wrap(err) } @@ -3328,7 +3424,7 @@ func (c *Cache) GetAccessLists(ctx context.Context) ([]*accesslist.AccessList, e ctx, span := c.Tracer.Start(ctx, "cache/GetAccessLists") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessLists) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessLists) if err != nil { return nil, trace.Wrap(err) } @@ -3341,7 +3437,7 @@ func (c *Cache) ListAccessLists(ctx context.Context, pageSize int, nextToken str ctx, span := c.Tracer.Start(ctx, "cache/ListAccessLists") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessLists) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessLists) if err != nil { return nil, "", trace.Wrap(err) } @@ -3354,7 +3450,7 @@ func (c *Cache) GetAccessList(ctx context.Context, name string) (*accesslist.Acc ctx, span := c.Tracer.Start(ctx, "cache/GetAccessList") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessLists) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessLists) if err != nil { return nil, trace.Wrap(err) } @@ -3377,7 +3473,7 @@ func (c *Cache) CountAccessListMembers(ctx context.Context, accessListName strin ctx, span := c.Tracer.Start(ctx, "cache/CountAccessListMembers") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessListMembers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessListMembers) if err != nil { return 0, 0, trace.Wrap(err) } @@ -3393,7 +3489,7 @@ func (c *Cache) ListAccessListMembers(ctx context.Context, accessListName string ctx, span := c.Tracer.Start(ctx, "cache/ListAccessListMembers") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessListMembers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessListMembers) if err != nil { return nil, "", trace.Wrap(err) } @@ -3406,7 +3502,7 @@ func (c *Cache) ListAllAccessListMembers(ctx context.Context, pageSize int, page ctx, span := c.Tracer.Start(ctx, "cache/ListAllAccessListMembers") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessListMembers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessListMembers) if err != nil { return nil, "", trace.Wrap(err) } @@ -3422,7 +3518,7 @@ func (c *Cache) GetAccessListMember(ctx context.Context, accessList string, memb ctx, span := c.Tracer.Start(ctx, "cache/GetAccessListMember") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessListMembers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessListMembers) if err != nil { return nil, trace.Wrap(err) } @@ -3435,7 +3531,7 @@ func (c *Cache) ListAccessListReviews(ctx context.Context, accessList string, pa ctx, span := c.Tracer.Start(ctx, "cache/ListAccessListReviews") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessListReviews) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessListReviews) if err != nil { return nil, "", trace.Wrap(err) } @@ -3448,7 +3544,7 @@ func (c *Cache) ListUserNotifications(ctx context.Context, pageSize int, startKe ctx, span := c.Tracer.Start(ctx, "cache/ListUserNotifications") defer span.End() - rg, err := readCollectionCache(c, c.collections.userNotifications) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.userNotifications) if err != nil { return nil, "", trace.Wrap(err) } @@ -3464,7 +3560,7 @@ func (c *Cache) ListGlobalNotifications(ctx context.Context, pageSize int, start ctx, span := c.Tracer.Start(ctx, "cache/ListGlobalNotifications") defer span.End() - rg, err := readCollectionCache(c, c.collections.globalNotifications) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.globalNotifications) if err != nil { return nil, "", trace.Wrap(err) } @@ -3478,7 +3574,7 @@ func (c *Cache) ListAccessMonitoringRules(ctx context.Context, pageSize int, nex ctx, span := c.Tracer.Start(ctx, "cache/ListAccessMonitoringRules") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessMonitoringRules) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessMonitoringRules) if err != nil { return nil, "", trace.Wrap(err) @@ -3493,7 +3589,7 @@ func (c *Cache) ListAccessMonitoringRulesWithFilter(ctx context.Context, pageSiz ctx, span := c.Tracer.Start(ctx, "cache/ListAccessMonitoringRules") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessMonitoringRules) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessMonitoringRules) if err != nil { return nil, "", trace.Wrap(err) @@ -3508,7 +3604,7 @@ func (c *Cache) GetAccessMonitoringRule(ctx context.Context, name string) (*acce ctx, span := c.Tracer.Start(ctx, "cache/GetAccessMonitoringRule") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessMonitoringRules) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessMonitoringRules) if err != nil { return nil, trace.Wrap(err) } @@ -3572,7 +3668,7 @@ func (c *Cache) GetAccessGraphSettings(ctx context.Context) (*clusterconfigpb.Ac ctx, span := c.Tracer.Start(ctx, "cache/GetAccessGraphSettings") defer span.End() - rg, err := readCollectionCache(c, c.collections.accessGraphSettings) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.accessGraphSettings) if err != nil { return nil, trace.Wrap(err) } @@ -3596,7 +3692,7 @@ func (c *Cache) GetProvisioningState(ctx context.Context, downstream services.Do ctx, span := c.Tracer.Start(ctx, "cache/GetProvisioningState") defer span.End() - rg, err := readCollectionCache(c, c.collections.provisioningStates) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.provisioningStates) if err != nil { return nil, trace.Wrap(err) } @@ -3609,7 +3705,7 @@ func (c *Cache) GetAccountAssignment(ctx context.Context, id services.IdentityCe ctx, span := c.Tracer.Start(ctx, "cache/GetAccountAssignment") defer span.End() - rg, err := readCollectionCache(c, c.collections.identityCenterAccountAssignments) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.identityCenterAccountAssignments) if err != nil { return services.IdentityCenterAccountAssignment{}, trace.Wrap(err) } @@ -3623,7 +3719,7 @@ func (c *Cache) ListAccountAssignments(ctx context.Context, pageSize int, pageTo ctx, span := c.Tracer.Start(ctx, "cache/ListAccountAssignments") defer span.End() - rg, err := readCollectionCache(c, c.collections.identityCenterAccountAssignments) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.identityCenterAccountAssignments) if err != nil { return nil, "", trace.Wrap(err) } @@ -3636,7 +3732,7 @@ func (c *Cache) GetIdentityCenterAccount(ctx context.Context, name services.Iden ctx, span := c.Tracer.Start(ctx, "cache/GetIdentityCenterAccount") defer span.End() - rg, err := readCollectionCache(c, c.collections.identityCenterAccounts) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.identityCenterAccounts) if err != nil { return services.IdentityCenterAccount{}, trace.Wrap(err) } @@ -3649,7 +3745,7 @@ func (c *Cache) ListIdentityCenterAccounts(ctx context.Context, pageSize int, to ctx, span := c.Tracer.Start(ctx, "cache/ListIdentityCenterAccounts") defer span.End() - rg, err := readCollectionCache(c, c.collections.identityCenterAccounts) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.identityCenterAccounts) if err != nil { return nil, "", trace.Wrap(err) } @@ -3662,7 +3758,7 @@ func (c *Cache) GetPrincipalAssignment(ctx context.Context, id services.Principa ctx, span := c.Tracer.Start(ctx, "cache/GetPrincipalAssignment") defer span.End() - rg, err := readCollectionCache(c, c.collections.identityCenterPrincipalAssignments) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.identityCenterPrincipalAssignments) if err != nil { return nil, trace.Wrap(err) } @@ -3675,7 +3771,7 @@ func (c *Cache) ListPrincipalAssignments(ctx context.Context, pageSize int, req ctx, span := c.Tracer.Start(ctx, "cache/ListPrincipalAssignments") defer span.End() - rg, err := readCollectionCache(c, c.collections.identityCenterPrincipalAssignments) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.identityCenterPrincipalAssignments) if err != nil { return nil, "", trace.Wrap(err) } @@ -3688,7 +3784,7 @@ func (c *Cache) ListProvisioningStatesForAllDownstreams(ctx context.Context, pag ctx, span := c.Tracer.Start(ctx, "cache/ListPrincipalAssignments") defer span.End() - rg, err := readCollectionCache(c, c.collections.provisioningStates) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.provisioningStates) if err != nil { return nil, "", trace.Wrap(err) } diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go index 2990af386f059..f27b0a3bb9aed 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -2990,11 +2990,11 @@ func testResources[T types.Resource](t *testing.T, p *testPack, funcs testFuncs[ require.Empty(t, cmp.Diff([]T{r}, out, cmpOpts...)) // Wait until the information has been replicated to the cache. - require.Eventually(t, func() bool { + require.EventuallyWithT(t, func(t *assert.CollectT) { // 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 + assert.Empty(t, cmp.Diff([]T{r}, out, cmpOpts...)) }, time.Second*2, time.Millisecond*250) // cacheGet is optional as not every resource implements it @@ -3020,11 +3020,11 @@ func testResources[T types.Resource](t *testing.T, p *testPack, funcs testFuncs[ require.Empty(t, cmp.Diff([]T{r}, out, cmpOpts...)) // Check that information has been replicated to the cache. - require.Eventually(t, func() bool { + require.EventuallyWithT(t, func(t *assert.CollectT) { // 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 + assert.Empty(t, cmp.Diff([]T{r}, out, cmpOpts...)) }, time.Second*2, time.Millisecond*250) // Remove all service providers from the backend. @@ -3060,20 +3060,20 @@ func testResources153[T types.Resource153](t *testing.T, p *testPack, funcs test } assertCacheContents := func(expected []T) { - require.EventuallyWithT(t, func(collect *assert.CollectT) { + require.EventuallyWithT(t, func(t *assert.CollectT) { out, err := funcs.cacheList(ctx) - assert.NoError(collect, err) + assert.NoError(t, err) // If the cache is expected to be empty, then test explicitly for // *that* rather than do an equality test. An equality test here // would be overly-pedantic about a service returning `nil` rather // than an empty slice. if len(expected) == 0 { - assert.Empty(collect, out) + assert.Empty(t, out) return } - assert.Empty(collect, cmp.Diff(expected, out, cmpOpts...)) + assert.Empty(t, cmp.Diff(expected, out, cmpOpts...)) }, 2*time.Second, 10*time.Millisecond) } diff --git a/lib/cache/collection.go b/lib/cache/collection.go new file mode 100644 index 0000000000000..e1d7c8c670db6 --- /dev/null +++ b/lib/cache/collection.go @@ -0,0 +1,165 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cache + +import ( + "context" + "reflect" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/types" +) + +// collection is responsible for managing a cached resource. +type collection[T any, I comparable] struct { + // fetcher is called by fetch to retrieve and seed the + // store with all known resources from upstream. + fetcher func(ctx context.Context, loadSecrets bool) ([]T, error) + // store persists all resources in memory. + store *store[T, I] + // watch contains the kind of resource being monitored. + watch types.WatchKind + // headerTransform is used when handling delete events in [onDelete]. Since + // [types.OpDelete] events only contain information about the resource key, + // most event handlers only emit a [types.ResourceHeader] which has enough + // information to identify a resource. Some resources do emit a half + // populated [T], or have enough information from the key to emit a full [T]. + // + // If this optional transformation is supplied it will be called when + // processing delete events before attempting to delete the resource + // from the store. + headerTransform func(hdr *types.ResourceHeader) T + // filter is an optional function used to prevent some resources + // from being persisted in the store. + filter func(T) bool + // singleton indicates if the resource should only ever have a single item. + // TODO(tross|fspmarshall|espadolini) investigate if special singleton + // behavior can be removed. + singleton bool +} + +func (c collection[_, _]) watchKind() types.WatchKind { + return c.watch +} + +// onDelete attempts to remove the provided resource from the store. +// An error is returned if the resource is of an unexpected type, or +// the resource is a [types.ResourceHeader] and no headerTransform was +// specified. +// +// This is a no-op if the configured filter does not return true. +func (c *collection[T, _]) onDelete(r types.Resource) error { + switch t := r.(type) { + case interface{ UnwrapT() T }: + tt := t.UnwrapT() + if c.filter != nil && !c.filter(tt) { + return nil + } + + return trace.Wrap(c.store.delete(tt)) + case *types.ResourceHeader: + if c.headerTransform == nil { + return trace.BadParameter("unable to convert types.ResourceHeader to %v (no transform specified, this is a bug)", reflect.TypeFor[T]()) + } + + tt := c.headerTransform(t) + if c.filter != nil && !c.filter(tt) { + return nil + } + + return trace.Wrap(c.store.delete(tt)) + case T: + if c.filter != nil && !c.filter(t) { + return nil + } + + return trace.Wrap(c.store.delete(t)) + default: + return trace.BadParameter("unexpected type %T (expected %v)", r, reflect.TypeFor[T]()) + } +} + +// onUpdate attempts to place the resource into the local store. +// An error is returned if the resource is of an unexpected type +// +// This is a no-op if the configured filter does not return true. +func (c *collection[T, _]) onPut(r types.Resource) error { + switch t := r.(type) { + case interface{ UnwrapT() T }: + tt := t.UnwrapT() + if c.filter != nil && !c.filter(tt) { + return nil + } + + c.store.put(tt) + return nil + case T: + if c.filter != nil && !c.filter(t) { + return nil + } + + c.store.put(t) + return nil + default: + return trace.BadParameter("unexpected type %T (expected %v)", r, reflect.TypeFor[T]()) + } +} + +// fetch populates the store with items received by the configured fetcher. +func (c collection[T, _]) fetch(ctx context.Context, cacheOK bool) (apply func(context.Context) error, err error) { + // Singleton objects will only get deleted or updated, not both + // TODO(tross|fspmarshall|espadolini) investigate if special singleton + // behavior can be removed. + deleteSingleton := false + + var resources []T + if cacheOK { + resources, err = c.fetcher(ctx, c.watch.LoadSecrets) + if err != nil { + if !trace.IsNotFound(err) { + return nil, trace.Wrap(err) + } + deleteSingleton = true + } + } + + return func(ctx context.Context) error { + // Always perform the delete if this is not a singleton, otherwise + // only perform the delete if the singleton wasn't found + // or the resource kind isn't cached in the current generation. + if !c.singleton || deleteSingleton || !cacheOK { + if err := c.store.clear(); err != nil { + if !trace.IsNotFound(err) { + return trace.Wrap(err) + } + } + } + // If this is a singleton and we performed a deletion, return here + // because we only want to update or delete a singleton, not both. + // Also don't continue if the resource kind isn't cached in the current generation. + if c.singleton && deleteSingleton || !cacheOK { + return nil + } + for _, resource := range resources { + if err := c.store.put(resource); err != nil { + return trace.Wrap(err) + } + } + return nil + }, nil +} diff --git a/lib/cache/collections.go b/lib/cache/collections.go index 69ca476134ad4..830ed639a6a3f 100644 --- a/lib/cache/collections.go +++ b/lib/cache/collections.go @@ -1,3576 +1,89 @@ -/* - * Teleport - * Copyright (C) 2023 Gravitational, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . -//nolint:unused // Because the executors generate a large amount of false positives. package cache import ( "context" - "fmt" "github.com/gravitational/trace" - "github.com/gravitational/teleport/api/client" - "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" - identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1" - kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" - machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" - notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1" - provisioningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/provisioning/v1" - userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2" - userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" - usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" - workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" "github.com/gravitational/teleport/api/types" - "github.com/gravitational/teleport/api/types/accesslist" - "github.com/gravitational/teleport/api/types/discoveryconfig" - "github.com/gravitational/teleport/api/types/secreports" - "github.com/gravitational/teleport/api/types/userloginstate" - "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/services" ) -// collection is responsible for managing collection -// of resources updates -type collection interface { +// collectionHandler is used by the [Cache] to seed the initial +// data and process events for a particular resource. +type collectionHandler interface { // fetch fetches resources and returns a function which will apply said resources to the cache. // fetch *must* not mutate cache state outside of the apply function. // The provided cacheOK flag indicates whether this collection will be included in the cache generation that is // being prepared. If cacheOK is false, fetch shouldn't fetch any resources, but the apply function that it // returns must still delete resources from the backend. fetch(ctx context.Context, cacheOK bool) (apply func(ctx context.Context) error, err error) - // processEvent processes event - processEvent(ctx context.Context, e types.Event) error + // onDelete will delete a single target resource from the cache. For + // singletons, this is usually an alias to clear. + onDelete(t types.Resource) error + // onPut will update a single target resource from the cache + onPut(t types.Resource) error // watchKind returns a watch // required for this collection watchKind() types.WatchKind } -// executor[T, R] is a specific way to run the collector operations that we need -// for the genericCollector for a generic resource type T and its reader type R. -type executor[T any, R any] interface { - // getAll returns all of the target resources from the auth server. - // For singleton objects, this should be a size-1 slice. - getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]T, error) - - // upsert will create or update a target resource in the cache. - upsert(ctx context.Context, cache *Cache, value T) error - - // deleteAll will delete all target resources of the type in the cache. - deleteAll(ctx context.Context, cache *Cache) error - - // delete will delete a single target resource from the cache. For - // singletons, this is usually an alias to deleteAll. - delete(ctx context.Context, cache *Cache, resource types.Resource) error - - // isSingleton will return true if the target resource is a singleton. - isSingleton() bool - - // getReader returns the appropriate reader type R based on the health status of the cache. - // Reader type R provides getter methods related to the collection, e.g. GetNodes(), GetRoles(). - // Note that cacheOK set to true means that cache is overall healthy and the collection was confirmed as supported. - getReader(c *Cache, cacheOK bool) R -} - -// noReader is returned by getReader for resources which aren't directly used by the cache, and therefore have no associated reader. -type noReader struct{} - -type crownjewelsGetter interface { - ListCrownJewels(ctx context.Context, pageSize int64, nextToken string) ([]*crownjewelv1.CrownJewel, string, error) - GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.CrownJewel, error) -} - -type userTasksGetter interface { - ListUserTasks(ctx context.Context, pageSize int64, nextToken string, filters *usertasksv1.ListUserTasksFilters) ([]*usertasksv1.UserTask, string, error) - GetUserTask(ctx context.Context, name string) (*usertasksv1.UserTask, error) -} - -// cacheCollections is a registry of resource collections used by Cache. -type cacheCollections struct { - // byKind is a map of registered collections by resource Kind/SubKind - byKind map[resourceKind]collection - - auditQueries collectionReader[services.SecurityAuditQueryGetter] - secReports collectionReader[services.SecurityReportGetter] - secReportsStates collectionReader[services.SecurityReportStateGetter] - accessLists collectionReader[accessListsGetter] - accessListMembers collectionReader[accessListMembersGetter] - accessListReviews collectionReader[accessListReviewsGetter] - apps collectionReader[services.AppGetter] - nodes collectionReader[nodeGetter] - tunnelConnections collectionReader[tunnelConnectionGetter] - appSessions collectionReader[appSessionGetter] - appServers collectionReader[appServerGetter] - authPreferences collectionReader[authPreferenceGetter] - authServers collectionReader[authServerGetter] - certAuthorities collectionReader[services.AuthorityGetter] - clusterAuditConfigs collectionReader[clusterAuditConfigGetter] - clusterNames collectionReader[clusterNameGetter] - clusterNetworkingConfigs collectionReader[clusterNetworkingConfigGetter] - databases collectionReader[services.DatabaseGetter] - databaseObjects collectionReader[services.DatabaseObjectsGetter] - databaseServers collectionReader[databaseServerGetter] - discoveryConfigs collectionReader[services.DiscoveryConfigsGetter] - installers collectionReader[installerGetter] - integrations collectionReader[services.IntegrationsGetter] - userTasks collectionReader[userTasksGetter] - crownJewels collectionReader[crownjewelsGetter] - kubeClusters collectionReader[kubernetesClusterGetter] - kubeWaitingContainers collectionReader[kubernetesWaitingContainerGetter] - staticHostUsers collectionReader[staticHostUserGetter] - kubeServers collectionReader[kubeServerGetter] - locks collectionReader[services.LockGetter] - namespaces collectionReader[namespaceGetter] - networkRestrictions collectionReader[networkRestrictionGetter] - oktaAssignments collectionReader[oktaAssignmentGetter] - oktaImportRules collectionReader[oktaImportRuleGetter] - proxies collectionReader[services.ProxyGetter] - remoteClusters collectionReader[remoteClusterGetter] - reverseTunnels collectionReader[reverseTunnelGetter] - roles collectionReader[roleGetter] - samlIdPServiceProviders collectionReader[samlIdPServiceProviderGetter] - samlIdPSessions collectionReader[samlIdPSessionGetter] - sessionRecordingConfigs collectionReader[sessionRecordingConfigGetter] - snowflakeSessions collectionReader[snowflakeSessionGetter] - staticTokens collectionReader[staticTokensGetter] - tokens collectionReader[tokenGetter] - uiConfigs collectionReader[uiConfigGetter] - users collectionReader[userGetter] - userGroups collectionReader[userGroupGetter] - userLoginStates collectionReader[services.UserLoginStatesGetter] - webSessions collectionReader[webSessionGetter] - webTokens collectionReader[webTokenGetter] - windowsDesktops collectionReader[windowsDesktopsGetter] - dynamicWindowsDesktops collectionReader[dynamicWindowsDesktopsGetter] - windowsDesktopServices collectionReader[windowsDesktopServiceGetter] - userNotifications collectionReader[notificationGetter] - accessGraphSettings collectionReader[accessGraphSettingsGetter] - globalNotifications collectionReader[notificationGetter] - accessMonitoringRules collectionReader[accessMonitoringRuleGetter] - spiffeFederations collectionReader[SPIFFEFederationReader] - autoUpdateConfigs collectionReader[autoUpdateConfigGetter] - autoUpdateVersions collectionReader[autoUpdateVersionGetter] - autoUpdateAgentRollouts collectionReader[autoUpdateAgentRolloutGetter] - provisioningStates collectionReader[provisioningStateGetter] - identityCenterAccounts collectionReader[identityCenterAccountGetter] - identityCenterPrincipalAssignments collectionReader[identityCenterPrincipalAssignmentGetter] - identityCenterAccountAssignments collectionReader[identityCenterAccountAssignmentGetter] - workloadIdentity collectionReader[WorkloadIdentityReader] - pluginStaticCredentials collectionReader[pluginStaticCredentialsGetter] - gitServers collectionReader[services.GitServerGetter] -} - -// setupCollections returns a registry of collections. -func setupCollections(c *Cache, watches []types.WatchKind) (*cacheCollections, error) { - collections := &cacheCollections{ - byKind: make(map[resourceKind]collection, len(watches)), - } - for _, watch := range watches { - resourceKind := resourceKindFromWatchKind(watch) - switch watch.Kind { - case types.KindCertAuthority: - if c.Trust == nil { - return nil, trace.BadParameter("missing parameter Trust") - } - var filter types.CertAuthorityFilter - filter.FromMap(watch.Filter) - - collections.certAuthorities = &genericCollection[types.CertAuthority, services.AuthorityGetter, certAuthorityExecutor]{ - cache: c, - exec: certAuthorityExecutor{filter: filter}, - watch: watch, - } - collections.byKind[resourceKind] = collections.certAuthorities - case types.KindStaticTokens: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.staticTokens = &genericCollection[types.StaticTokens, staticTokensGetter, staticTokensExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.staticTokens - case types.KindToken: - if c.Provisioner == nil { - return nil, trace.BadParameter("missing parameter Provisioner") - } - collections.tokens = &genericCollection[types.ProvisionToken, tokenGetter, provisionTokenExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.tokens - case types.KindClusterName: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.clusterNames = &genericCollection[types.ClusterName, clusterNameGetter, clusterNameExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.clusterNames - case types.KindClusterAuditConfig: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.clusterAuditConfigs = &genericCollection[types.ClusterAuditConfig, clusterAuditConfigGetter, clusterAuditConfigExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.clusterAuditConfigs - case types.KindClusterNetworkingConfig: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.clusterNetworkingConfigs = &genericCollection[types.ClusterNetworkingConfig, clusterNetworkingConfigGetter, clusterNetworkingConfigExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.clusterNetworkingConfigs - case types.KindClusterAuthPreference: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.authPreferences = &genericCollection[types.AuthPreference, authPreferenceGetter, authPreferenceExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.authPreferences - case types.KindSessionRecordingConfig: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.sessionRecordingConfigs = &genericCollection[types.SessionRecordingConfig, sessionRecordingConfigGetter, sessionRecordingConfigExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.sessionRecordingConfigs - case types.KindInstaller: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.installers = &genericCollection[types.Installer, installerGetter, installerConfigExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.installers - case types.KindUIConfig: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.uiConfigs = &genericCollection[types.UIConfig, uiConfigGetter, uiConfigExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.uiConfigs - case types.KindUser: - if c.Users == nil { - return nil, trace.BadParameter("missing parameter Users") - } - collections.users = &genericCollection[types.User, userGetter, userExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.users - case types.KindRole: - if c.Access == nil { - return nil, trace.BadParameter("missing parameter Access") - } - collections.roles = &genericCollection[types.Role, roleGetter, roleExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.roles - case types.KindNamespace: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.namespaces = &genericCollection[*types.Namespace, namespaceGetter, namespaceExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.namespaces - case types.KindNode: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.nodes = &genericCollection[types.Server, nodeGetter, nodeExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.nodes - case types.KindProxy: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.proxies = &genericCollection[types.Server, services.ProxyGetter, proxyExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.proxies - case types.KindAuthServer: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.authServers = &genericCollection[types.Server, authServerGetter, authServerExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.authServers - case types.KindReverseTunnel: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.reverseTunnels = &genericCollection[types.ReverseTunnel, reverseTunnelGetter, reverseTunnelExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.reverseTunnels - case types.KindTunnelConnection: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.tunnelConnections = &genericCollection[types.TunnelConnection, tunnelConnectionGetter, tunnelConnectionExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.tunnelConnections - case types.KindRemoteCluster: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.remoteClusters = &genericCollection[types.RemoteCluster, remoteClusterGetter, remoteClusterExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.remoteClusters - case types.KindAccessRequest: - if c.DynamicAccess == nil { - return nil, trace.BadParameter("missing parameter DynamicAccess") - } - collections.byKind[resourceKind] = &genericCollection[types.AccessRequest, noReader, accessRequestExecutor]{cache: c, watch: watch} - case types.KindAppServer: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.appServers = &genericCollection[types.AppServer, appServerGetter, appServerExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.appServers - case types.KindWebSession: - switch watch.SubKind { - case types.KindAppSession: - if c.AppSession == nil { - return nil, trace.BadParameter("missing parameter AppSession") - } - collections.appSessions = &genericCollection[types.WebSession, appSessionGetter, appSessionExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.appSessions - case types.KindSnowflakeSession: - if c.SnowflakeSession == nil { - return nil, trace.BadParameter("missing parameter SnowflakeSession") - } - collections.snowflakeSessions = &genericCollection[types.WebSession, snowflakeSessionGetter, snowflakeSessionExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.snowflakeSessions - case types.KindSAMLIdPSession: - if c.SAMLIdPSession == nil { - return nil, trace.BadParameter("missing parameter SAMLIdPSession") - } - collections.samlIdPSessions = &genericCollection[types.WebSession, samlIdPSessionGetter, samlIdPSessionExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.samlIdPSessions - case types.KindWebSession: - if c.WebSession == nil { - return nil, trace.BadParameter("missing parameter WebSession") - } - collections.webSessions = &genericCollection[types.WebSession, webSessionGetter, webSessionExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.webSessions - } - case types.KindWebToken: - if c.WebToken == nil { - return nil, trace.BadParameter("missing parameter WebToken") - } - collections.webTokens = &genericCollection[types.WebToken, webTokenGetter, webTokenExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.webTokens - case types.KindKubeServer: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.kubeServers = &genericCollection[types.KubeServer, kubeServerGetter, kubeServerExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.kubeServers - case types.KindDatabaseServer: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.databaseServers = &genericCollection[types.DatabaseServer, databaseServerGetter, databaseServerExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.databaseServers - case types.KindDatabaseService: - if c.DatabaseServices == nil { - return nil, trace.BadParameter("missing parameter DatabaseServices") - } - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.byKind[resourceKind] = &genericCollection[types.DatabaseService, noReader, databaseServiceExecutor]{cache: c, watch: watch} - case types.KindApp: - if c.Apps == nil { - return nil, trace.BadParameter("missing parameter Apps") - } - collections.apps = &genericCollection[types.Application, services.AppGetter, appExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.apps - case types.KindDatabase: - if c.Databases == nil { - return nil, trace.BadParameter("missing parameter Databases") - } - collections.databases = &genericCollection[types.Database, services.DatabaseGetter, databaseExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.databases - case types.KindDatabaseObject: - if c.DatabaseObjects == nil { - return nil, trace.BadParameter("missing parameter DatabaseObject") - } - collections.databaseObjects = &genericCollection[*dbobjectv1.DatabaseObject, services.DatabaseObjectsGetter, databaseObjectExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.databaseObjects - case types.KindKubernetesCluster: - if c.Kubernetes == nil { - return nil, trace.BadParameter("missing parameter Kubernetes") - } - collections.kubeClusters = &genericCollection[types.KubeCluster, kubernetesClusterGetter, kubeClusterExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.kubeClusters - case types.KindCrownJewel: - if c.CrownJewels == nil { - return nil, trace.BadParameter("missing parameter crownjewels") - } - collections.crownJewels = &genericCollection[*crownjewelv1.CrownJewel, crownjewelsGetter, crownJewelsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.crownJewels - case types.KindNetworkRestrictions: - if c.Restrictions == nil { - return nil, trace.BadParameter("missing parameter Restrictions") - } - collections.networkRestrictions = &genericCollection[types.NetworkRestrictions, networkRestrictionGetter, networkRestrictionsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.networkRestrictions - case types.KindLock: - if c.Access == nil { - return nil, trace.BadParameter("missing parameter Access") - } - collections.locks = &genericCollection[types.Lock, services.LockGetter, lockExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.locks - case types.KindWindowsDesktopService: - if c.Presence == nil { - return nil, trace.BadParameter("missing parameter Presence") - } - collections.windowsDesktopServices = &genericCollection[types.WindowsDesktopService, windowsDesktopServiceGetter, windowsDesktopServicesExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.windowsDesktopServices - case types.KindWindowsDesktop: - if c.WindowsDesktops == nil { - return nil, trace.BadParameter("missing parameter WindowsDesktops") - } - collections.windowsDesktops = &genericCollection[types.WindowsDesktop, windowsDesktopsGetter, windowsDesktopsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.windowsDesktops - case types.KindDynamicWindowsDesktop: - if c.WindowsDesktops == nil { - return nil, trace.BadParameter("missing parameter DynamicWindowsDesktops") - } - collections.dynamicWindowsDesktops = &genericCollection[types.DynamicWindowsDesktop, dynamicWindowsDesktopsGetter, dynamicWindowsDesktopsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.dynamicWindowsDesktops - case types.KindSAMLIdPServiceProvider: - if c.SAMLIdPServiceProviders == nil { - return nil, trace.BadParameter("missing parameter SAMLIdPServiceProviders") - } - collections.samlIdPServiceProviders = &genericCollection[types.SAMLIdPServiceProvider, samlIdPServiceProviderGetter, samlIdPServiceProvidersExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.samlIdPServiceProviders - case types.KindUserGroup: - if c.UserGroups == nil { - return nil, trace.BadParameter("missing parameter UserGroups") - } - collections.userGroups = &genericCollection[types.UserGroup, userGroupGetter, userGroupsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.userGroups - case types.KindOktaImportRule: - if c.Okta == nil { - return nil, trace.BadParameter("missing parameter Okta") - } - collections.oktaImportRules = &genericCollection[types.OktaImportRule, oktaImportRuleGetter, oktaImportRulesExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.oktaImportRules - case types.KindOktaAssignment: - if c.Okta == nil { - return nil, trace.BadParameter("missing parameter Okta") - } - collections.oktaAssignments = &genericCollection[types.OktaAssignment, oktaAssignmentGetter, oktaAssignmentsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.oktaAssignments - case types.KindIntegration: - if c.Integrations == nil { - return nil, trace.BadParameter("missing parameter Integrations") - } - collections.integrations = &genericCollection[types.Integration, services.IntegrationsGetter, integrationsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.integrations - case types.KindUserTask: - if c.UserTasks == nil { - return nil, trace.BadParameter("missing parameter user tasks") - } - collections.userTasks = &genericCollection[*usertasksv1.UserTask, userTasksGetter, userTasksExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.userTasks - case types.KindDiscoveryConfig: - if c.DiscoveryConfigs == nil { - return nil, trace.BadParameter("missing parameter DiscoveryConfigs") - } - collections.discoveryConfigs = &genericCollection[*discoveryconfig.DiscoveryConfig, services.DiscoveryConfigsGetter, discoveryConfigExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.discoveryConfigs - case types.KindHeadlessAuthentication: - // For headless authentications, we need only process events. We don't need to keep the cache up to date. - collections.byKind[resourceKind] = &genericCollection[*types.HeadlessAuthentication, noReader, noopExecutor]{cache: c, watch: watch} - case types.KindAuditQuery: - if c.SecReports == nil { - return nil, trace.BadParameter("missing parameter SecReports") - } - collections.auditQueries = &genericCollection[*secreports.AuditQuery, services.SecurityAuditQueryGetter, auditQueryExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.auditQueries - case types.KindSecurityReport: - if c.SecReports == nil { - return nil, trace.BadParameter("missing parameter KindSecurityReport") - } - collections.secReports = &genericCollection[*secreports.Report, services.SecurityReportGetter, secReportExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.secReports - case types.KindSecurityReportState: - if c.SecReports == nil { - return nil, trace.BadParameter("missing parameter KindSecurityReport") - } - collections.secReportsStates = &genericCollection[*secreports.ReportState, services.SecurityReportStateGetter, secReportStateExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.secReportsStates - case types.KindUserLoginState: - if c.UserLoginStates == nil { - return nil, trace.BadParameter("missing parameter UserLoginStates") - } - collections.userLoginStates = &genericCollection[*userloginstate.UserLoginState, services.UserLoginStatesGetter, userLoginStateExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.userLoginStates - case types.KindAccessList: - if c.AccessLists == nil { - return nil, trace.BadParameter("missing parameter AccessLists") - } - collections.accessLists = &genericCollection[*accesslist.AccessList, accessListsGetter, accessListExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.accessLists - case types.KindAccessListMember: - if c.AccessLists == nil { - return nil, trace.BadParameter("missing parameter AccessLists") - } - collections.accessListMembers = &genericCollection[*accesslist.AccessListMember, accessListMembersGetter, accessListMemberExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.accessListMembers - case types.KindAccessListReview: - if c.AccessLists == nil { - return nil, trace.BadParameter("missing parameter AccessLists") - } - collections.accessListReviews = &genericCollection[*accesslist.Review, accessListReviewsGetter, accessListReviewExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.accessListReviews - case types.KindKubeWaitingContainer: - if c.KubeWaitingContainers == nil { - return nil, trace.BadParameter("missing parameter KubeWaitingContainers") - } - collections.kubeWaitingContainers = &genericCollection[*kubewaitingcontainerpb.KubernetesWaitingContainer, kubernetesWaitingContainerGetter, kubeWaitingContainerExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.kubeWaitingContainers - case types.KindStaticHostUser: - if c.StaticHostUsers == nil { - return nil, trace.BadParameter("missing parameter StaticHostUsers") - } - collections.staticHostUsers = &genericCollection[*userprovisioningpb.StaticHostUser, staticHostUserGetter, staticHostUserExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.staticHostUsers - case types.KindNotification: - if c.Notifications == nil { - return nil, trace.BadParameter("missing parameter Notifications") - } - collections.userNotifications = &genericCollection[*notificationsv1.Notification, notificationGetter, userNotificationExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.userNotifications - case types.KindGlobalNotification: - if c.Notifications == nil { - return nil, trace.BadParameter("missing parameter Notifications") - } - collections.globalNotifications = &genericCollection[*notificationsv1.GlobalNotification, notificationGetter, globalNotificationExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.globalNotifications - case types.KindAccessMonitoringRule: - if c.AccessMonitoringRules == nil { - return nil, trace.BadParameter("missing parameter AccessMonitoringRule") - } - collections.accessMonitoringRules = &genericCollection[*accessmonitoringrulesv1.AccessMonitoringRule, accessMonitoringRuleGetter, accessMonitoringRulesExecutor]{cache: c, watch: watch} - collections.byKind[resourceKind] = collections.accessMonitoringRules - case types.KindAccessGraphSettings: - if c.ClusterConfig == nil { - return nil, trace.BadParameter("missing parameter ClusterConfig") - } - collections.accessGraphSettings = &genericCollection[*clusterconfigpb.AccessGraphSettings, accessGraphSettingsGetter, accessGraphSettingsExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.accessGraphSettings - case types.KindSPIFFEFederation: - if c.Config.SPIFFEFederations == nil { - return nil, trace.BadParameter("missing parameter SPIFFEFederations") - } - collections.spiffeFederations = &genericCollection[*machineidv1.SPIFFEFederation, SPIFFEFederationReader, spiffeFederationExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.spiffeFederations - case types.KindWorkloadIdentity: - if c.Config.WorkloadIdentity == nil { - return nil, trace.BadParameter("missing parameter WorkloadIdentity") - } - collections.workloadIdentity = &genericCollection[*workloadidentityv1pb.WorkloadIdentity, WorkloadIdentityReader, workloadIdentityExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.workloadIdentity - 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 - case types.KindAutoUpdateAgentRollout: - if c.AutoUpdateService == nil { - return nil, trace.BadParameter("missing parameter AutoUpdateService") - } - collections.autoUpdateAgentRollouts = &genericCollection[*autoupdate.AutoUpdateAgentRollout, autoUpdateAgentRolloutGetter, autoUpdateAgentRolloutExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.autoUpdateAgentRollouts - - case types.KindProvisioningPrincipalState: - if c.ProvisioningStates == nil { - return nil, trace.BadParameter("missing parameter KindProvisioningState") - } - collections.provisioningStates = &genericCollection[*provisioningv1.PrincipalState, provisioningStateGetter, provisioningStateExecutor]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.provisioningStates - - case types.KindIdentityCenterAccount: - if c.IdentityCenter == nil { - return nil, trace.BadParameter("missing upstream IdentityCenter collection") - } - collections.identityCenterAccounts = &genericCollection[ - services.IdentityCenterAccount, - identityCenterAccountGetter, - identityCenterAccountExecutor, - ]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.identityCenterAccounts - - case types.KindIdentityCenterPrincipalAssignment: - if c.IdentityCenter == nil { - return nil, trace.BadParameter("missing parameter IdentityCenter") - } - collections.identityCenterPrincipalAssignments = &genericCollection[ - *identitycenterv1.PrincipalAssignment, - identityCenterPrincipalAssignmentGetter, - identityCenterPrincipalAssignmentExecutor, - ]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.identityCenterPrincipalAssignments - - case types.KindIdentityCenterAccountAssignment: - if c.IdentityCenter == nil { - return nil, trace.BadParameter("missing parameter IdentityCenter") - } - collections.identityCenterAccountAssignments = &genericCollection[ - services.IdentityCenterAccountAssignment, - identityCenterAccountAssignmentGetter, - identityCenterAccountAssignmentExecutor, - ]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.identityCenterAccountAssignments - - case types.KindPluginStaticCredentials: - if c.PluginStaticCredentials == nil { - return nil, trace.BadParameter("missing parameter PluginStaticCredentials") - } - collections.pluginStaticCredentials = &genericCollection[ - types.PluginStaticCredentials, - pluginStaticCredentialsGetter, - pluginStaticCredentialsExecutor, - ]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.pluginStaticCredentials - - case types.KindGitServer: - if c.GitServers == nil { - return nil, trace.BadParameter("missing parameter GitServers") - } - collections.gitServers = &genericCollection[ - types.Server, - services.GitServerGetter, - gitServerExecutor, - ]{ - cache: c, - watch: watch, - } - collections.byKind[resourceKind] = collections.gitServers - default: - return nil, trace.BadParameter("resource %q is not supported", watch.Kind) - } - } - return collections, nil -} - -func resourceKindFromWatchKind(wk types.WatchKind) resourceKind { - switch wk.Kind { - case types.KindWebSession: - // Web sessions use subkind to differentiate between - // the types of sessions - return resourceKind{ - kind: wk.Kind, - subkind: wk.SubKind, - } - } - return resourceKind{ - kind: wk.Kind, - } -} - -func resourceKindFromResource(res types.Resource) resourceKind { - switch res.GetKind() { - case types.KindWebSession: - // Web sessions use subkind to differentiate between - // the types of sessions - return resourceKind{ - kind: res.GetKind(), - subkind: res.GetSubKind(), - } - } - return resourceKind{ - kind: res.GetKind(), - } -} - -type resourceKind struct { - kind string - subkind string -} - -func (r resourceKind) String() string { - if r.subkind == "" { - return r.kind - } - return fmt.Sprintf("%s/%s", r.kind, r.subkind) -} - -type accessRequestExecutor struct{} - -func (accessRequestExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.AccessRequest, error) { - return cache.DynamicAccess.GetAccessRequests(ctx, types.AccessRequestFilter{}) -} - -func (accessRequestExecutor) upsert(ctx context.Context, cache *Cache, resource types.AccessRequest) error { - return cache.dynamicAccessCache.UpsertAccessRequest(ctx, resource) -} - -func (accessRequestExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.dynamicAccessCache.DeleteAllAccessRequests(ctx) -} - -func (accessRequestExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.dynamicAccessCache.DeleteAccessRequest(ctx, resource.GetName()) -} - -func (accessRequestExecutor) isSingleton() bool { return false } - -func (accessRequestExecutor) getReader(_ *Cache, _ bool) noReader { - return noReader{} -} - -var _ executor[types.AccessRequest, noReader] = accessRequestExecutor{} - -type tunnelConnectionExecutor struct{} - -func (tunnelConnectionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.TunnelConnection, error) { - return cache.Trust.GetAllTunnelConnections() -} - -func (tunnelConnectionExecutor) upsert(ctx context.Context, cache *Cache, resource types.TunnelConnection) error { - return cache.trustCache.UpsertTunnelConnection(resource) -} - -func (tunnelConnectionExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.trustCache.DeleteAllTunnelConnections() -} - -func (tunnelConnectionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.trustCache.DeleteTunnelConnection(resource.GetSubKind(), resource.GetName()) -} - -func (tunnelConnectionExecutor) isSingleton() bool { return false } - -func (tunnelConnectionExecutor) getReader(cache *Cache, cacheOK bool) tunnelConnectionGetter { - if cacheOK { - return cache.trustCache - } - return cache.Config.Trust -} - -type tunnelConnectionGetter interface { - GetAllTunnelConnections(opts ...services.MarshalOption) (conns []types.TunnelConnection, err error) - GetTunnelConnections(clusterName string, opts ...services.MarshalOption) ([]types.TunnelConnection, error) -} - -var _ executor[types.TunnelConnection, tunnelConnectionGetter] = tunnelConnectionExecutor{} - -type remoteClusterExecutor struct{} - -func (remoteClusterExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.RemoteCluster, error) { - return cache.Trust.GetRemoteClusters(ctx) -} - -func (remoteClusterExecutor) upsert(ctx context.Context, cache *Cache, resource types.RemoteCluster) error { - err := cache.trustCache.DeleteRemoteCluster(ctx, resource.GetName()) - if err != nil { - if !trace.IsNotFound(err) { - cache.Logger.WithError(err).Warnf("Failed to delete remote cluster %v.", resource.GetName()) - return trace.Wrap(err) - } - } - _, err = cache.trustCache.CreateRemoteCluster(ctx, resource) - return trace.Wrap(err) -} - -func (remoteClusterExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.trustCache.DeleteAllRemoteClusters(ctx) -} - -func (remoteClusterExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.trustCache.DeleteRemoteCluster(ctx, resource.GetName()) -} - -func (remoteClusterExecutor) isSingleton() bool { return false } - -func (remoteClusterExecutor) getReader(cache *Cache, cacheOK bool) remoteClusterGetter { - if cacheOK { - return cache.trustCache - } - return cache.Config.Trust -} - -type remoteClusterGetter interface { - GetRemoteClusters(ctx context.Context) ([]types.RemoteCluster, error) - GetRemoteCluster(ctx context.Context, clusterName string) (types.RemoteCluster, error) - ListRemoteClusters(ctx context.Context, pageSize int, pageToken string) ([]types.RemoteCluster, string, error) -} - -var _ executor[types.RemoteCluster, remoteClusterGetter] = remoteClusterExecutor{} - -type proxyExecutor struct{} - -func (proxyExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Server, error) { - return cache.Presence.GetProxies() -} - -func (proxyExecutor) upsert(ctx context.Context, cache *Cache, resource types.Server) error { - return cache.presenceCache.UpsertProxy(ctx, resource) -} - -func (proxyExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllProxies() -} - -func (proxyExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteProxy(ctx, resource.GetName()) -} - -func (proxyExecutor) isSingleton() bool { return false } - -func (proxyExecutor) getReader(cache *Cache, cacheOK bool) services.ProxyGetter { - if cacheOK { - return cache.presenceCache - } - return cache.Config.Presence -} - -var _ executor[types.Server, services.ProxyGetter] = proxyExecutor{} - -type authServerExecutor struct{} - -func (authServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Server, error) { - return cache.Presence.GetAuthServers() -} - -func (authServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.Server) error { - return cache.presenceCache.UpsertAuthServer(ctx, resource) -} - -func (authServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllAuthServers() -} - -func (authServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteAuthServer(resource.GetName()) -} - -func (authServerExecutor) isSingleton() bool { return false } - -func (authServerExecutor) getReader(cache *Cache, cacheOK bool) authServerGetter { - if cacheOK { - return cache.presenceCache - } - return cache.Config.Presence -} - -type authServerGetter interface { - GetAuthServers() ([]types.Server, error) -} - -var _ executor[types.Server, authServerGetter] = authServerExecutor{} - -type nodeExecutor struct{} - -func (nodeExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Server, error) { - return cache.Presence.GetNodes(ctx, apidefaults.Namespace) -} - -func (nodeExecutor) upsert(ctx context.Context, cache *Cache, resource types.Server) error { - _, err := cache.presenceCache.UpsertNode(ctx, resource) - return trace.Wrap(err) -} - -func (nodeExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllNodes(ctx, apidefaults.Namespace) -} - -func (nodeExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteNode(ctx, resource.GetMetadata().Namespace, resource.GetName()) -} - -func (nodeExecutor) isSingleton() bool { return false } - -func (nodeExecutor) getReader(cache *Cache, cacheOK bool) nodeGetter { - if cacheOK { - return cache.presenceCache - } - return cache.Config.Presence -} - -type nodeGetter interface { - GetNodes(ctx context.Context, namespace string) ([]types.Server, error) - GetNode(ctx context.Context, namespace, name string) (types.Server, error) -} - -var _ executor[types.Server, nodeGetter] = nodeExecutor{} - -type namespaceExecutor struct{} - -func (namespaceExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*types.Namespace, error) { - namespaces, err := cache.Presence.GetNamespaces() - if err != nil { - return nil, trace.Wrap(err) - } - - derefNamespaces := make([]*types.Namespace, len(namespaces)) - for i := range namespaces { - ns := namespaces[i] - derefNamespaces[i] = &ns - } - return derefNamespaces, nil -} - -func (namespaceExecutor) upsert(ctx context.Context, cache *Cache, resource *types.Namespace) error { - return cache.presenceCache.UpsertNamespace(*resource) -} - -func (namespaceExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllNamespaces() -} - -func (namespaceExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteNamespace(resource.GetName()) -} - -func (namespaceExecutor) isSingleton() bool { return false } - -func (namespaceExecutor) getReader(cache *Cache, cacheOK bool) namespaceGetter { - if cacheOK { - return cache.presenceCache - } - return cache.Config.Presence -} - -type namespaceGetter interface { - GetNamespaces() ([]types.Namespace, error) - GetNamespace(name string) (*types.Namespace, error) -} - -var _ executor[*types.Namespace, namespaceGetter] = namespaceExecutor{} - -type certAuthorityExecutor struct { - // extracted from watch.Filter, to avoid rebuilding on every event - filter types.CertAuthorityFilter -} - -// delete implements executor[types.CertAuthority] -func (certAuthorityExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - err := cache.trustCache.DeleteCertAuthority(ctx, types.CertAuthID{ - Type: types.CertAuthType(resource.GetSubKind()), - DomainName: resource.GetName(), - }) - return trace.Wrap(err) -} - -// deleteAll implements executor[types.CertAuthority] -func (certAuthorityExecutor) deleteAll(ctx context.Context, cache *Cache) error { - for _, caType := range types.CertAuthTypes { - if err := cache.trustCache.DeleteAllCertAuthorities(caType); err != nil { - return trace.Wrap(err) - } - } - return nil -} - -// getAll implements executor[types.CertAuthority] -func (e certAuthorityExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.CertAuthority, error) { - var authorities []types.CertAuthority - for _, caType := range types.CertAuthTypes { - cas, err := cache.Trust.GetCertAuthorities(ctx, caType, loadSecrets) - // if caType was added in this major version we might get a BadParameter - // error if we're connecting to an older upstream that doesn't know about it - if err != nil { - if !types.IsUnsupportedAuthorityErr(err) || !caType.NewlyAdded() { - return nil, trace.Wrap(err) - } - continue - } - - // this can be removed once we get the ability to fetch CAs with a filter, - // but it should be harmless, and it could be kept as additional safety - if !e.filter.IsEmpty() { - filtered := cas[:0] - for _, ca := range cas { - if e.filter.Match(ca) { - filtered = append(filtered, ca) - } - } - cas = filtered - } - - authorities = append(authorities, cas...) - } - - return authorities, nil -} - -// upsert implements executor[types.CertAuthority] -func (e certAuthorityExecutor) upsert(ctx context.Context, cache *Cache, value types.CertAuthority) error { - if !e.filter.Match(value) { - return nil - } - - return cache.trustCache.UpsertCertAuthority(ctx, value) -} - -func (certAuthorityExecutor) isSingleton() bool { return false } - -func (certAuthorityExecutor) getReader(cache *Cache, cacheOK bool) services.AuthorityGetter { - if cacheOK { - return cache.trustCache - } - return cache.Config.Trust -} - -var _ executor[types.CertAuthority, services.AuthorityGetter] = certAuthorityExecutor{} - -type staticTokensExecutor struct{} - -func (staticTokensExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.StaticTokens, error) { - token, err := cache.ClusterConfig.GetStaticTokens() - if err != nil { - return nil, trace.Wrap(err) - } - return []types.StaticTokens{token}, nil -} - -func (staticTokensExecutor) upsert(ctx context.Context, cache *Cache, resource types.StaticTokens) error { - return cache.clusterConfigCache.SetStaticTokens(resource) -} - -func (staticTokensExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteStaticTokens() -} - -func (staticTokensExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteStaticTokens() -} - -func (staticTokensExecutor) isSingleton() bool { return true } - -func (staticTokensExecutor) getReader(cache *Cache, cacheOK bool) staticTokensGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type staticTokensGetter interface { - GetStaticTokens() (types.StaticTokens, error) -} - -var _ executor[types.StaticTokens, staticTokensGetter] = staticTokensExecutor{} - -type provisionTokenExecutor struct{} - -func (provisionTokenExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ProvisionToken, error) { - return cache.Provisioner.GetTokens(ctx) -} - -func (provisionTokenExecutor) upsert(ctx context.Context, cache *Cache, resource types.ProvisionToken) error { - return cache.provisionerCache.UpsertToken(ctx, resource) -} - -func (provisionTokenExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.provisionerCache.DeleteAllTokens() -} - -func (provisionTokenExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.provisionerCache.DeleteToken(ctx, resource.GetName()) -} - -func (provisionTokenExecutor) isSingleton() bool { return false } - -func (provisionTokenExecutor) getReader(cache *Cache, cacheOK bool) tokenGetter { - if cacheOK { - return cache.provisionerCache - } - return cache.Config.Provisioner -} - -type tokenGetter interface { - GetTokens(ctx context.Context) ([]types.ProvisionToken, error) - GetToken(ctx context.Context, token string) (types.ProvisionToken, error) -} - -var _ executor[types.ProvisionToken, tokenGetter] = provisionTokenExecutor{} - -type clusterNameExecutor struct{} - -func (clusterNameExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ClusterName, error) { - name, err := cache.ClusterConfig.GetClusterName() - return []types.ClusterName{name}, trace.Wrap(err) -} - -func (clusterNameExecutor) upsert(ctx context.Context, cache *Cache, resource types.ClusterName) error { - return cache.clusterConfigCache.UpsertClusterName(resource) -} - -func (clusterNameExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteClusterName() -} - -func (clusterNameExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteClusterName() -} - -func (clusterNameExecutor) isSingleton() bool { return true } - -func (clusterNameExecutor) getReader(cache *Cache, cacheOK bool) clusterNameGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -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 autoUpdateAgentRolloutExecutor struct{} - -func (autoUpdateAgentRolloutExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*autoupdate.AutoUpdateAgentRollout, error) { - plan, err := cache.AutoUpdateService.GetAutoUpdateAgentRollout(ctx) - return []*autoupdate.AutoUpdateAgentRollout{plan}, trace.Wrap(err) -} - -func (autoUpdateAgentRolloutExecutor) upsert(ctx context.Context, cache *Cache, resource *autoupdate.AutoUpdateAgentRollout) error { - _, err := cache.autoUpdateCache.UpsertAutoUpdateAgentRollout(ctx, resource) - return trace.Wrap(err) -} - -func (autoUpdateAgentRolloutExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.autoUpdateCache.DeleteAutoUpdateAgentRollout(ctx) -} - -func (autoUpdateAgentRolloutExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.autoUpdateCache.DeleteAutoUpdateAgentRollout(ctx) -} - -func (autoUpdateAgentRolloutExecutor) isSingleton() bool { return true } - -func (autoUpdateAgentRolloutExecutor) getReader(cache *Cache, cacheOK bool) autoUpdateAgentRolloutGetter { - if cacheOK { - return cache.autoUpdateCache - } - return cache.Config.AutoUpdateService -} - -type autoUpdateAgentRolloutGetter interface { - GetAutoUpdateAgentRollout(ctx context.Context) (*autoupdate.AutoUpdateAgentRollout, error) -} - -var _ executor[*autoupdate.AutoUpdateAgentRollout, autoUpdateAgentRolloutGetter] = autoUpdateAgentRolloutExecutor{} - -type userExecutor struct{} - -func (userExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.User, error) { - return cache.Users.GetUsers(ctx, loadSecrets) -} - -func (userExecutor) upsert(ctx context.Context, cache *Cache, resource types.User) error { - _, err := cache.usersCache.UpsertUser(ctx, resource) - return err -} - -func (userExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.usersCache.DeleteAllUsers(ctx) -} - -func (userExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.usersCache.DeleteUser(ctx, resource.GetName()) -} - -func (userExecutor) isSingleton() bool { return false } - -func (userExecutor) getReader(cache *Cache, cacheOK bool) userGetter { - if cacheOK { - return cache.usersCache - } - return cache.Config.Users -} - -type userGetter interface { - GetUser(ctx context.Context, user string, withSecrets bool) (types.User, error) - GetUsers(ctx context.Context, withSecrets bool) ([]types.User, error) - ListUsers(ctx context.Context, req *userspb.ListUsersRequest) (*userspb.ListUsersResponse, error) -} - -var _ executor[types.User, userGetter] = userExecutor{} - -type roleExecutor struct{} - -func (roleExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Role, error) { - return cache.Access.GetRoles(ctx) -} - -func (roleExecutor) upsert(ctx context.Context, cache *Cache, resource types.Role) error { - _, err := cache.accessCache.UpsertRole(ctx, resource) - return err -} - -func (roleExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.accessCache.DeleteAllRoles(ctx) -} - -func (roleExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.accessCache.DeleteRole(ctx, resource.GetName()) -} - -func (roleExecutor) isSingleton() bool { return false } - -func (roleExecutor) getReader(cache *Cache, cacheOK bool) roleGetter { - if cacheOK { - return cache.accessCache - } - return cache.Config.Access -} - -type roleGetter interface { - GetRoles(ctx context.Context) ([]types.Role, error) - GetRole(ctx context.Context, name string) (types.Role, error) - ListRoles(ctx context.Context, req *proto.ListRolesRequest) (*proto.ListRolesResponse, error) -} - -var _ executor[types.Role, roleGetter] = roleExecutor{} - -type databaseServerExecutor struct{} - -func (databaseServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.DatabaseServer, error) { - return cache.Presence.GetDatabaseServers(ctx, apidefaults.Namespace) -} - -func (databaseServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.DatabaseServer) error { - _, err := cache.presenceCache.UpsertDatabaseServer(ctx, resource) - return trace.Wrap(err) -} - -func (databaseServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllDatabaseServers(ctx, apidefaults.Namespace) -} - -func (databaseServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteDatabaseServer(ctx, - resource.GetMetadata().Namespace, - resource.GetMetadata().Description, // Cache passes host ID via description field. - resource.GetName()) -} - -func (databaseServerExecutor) isSingleton() bool { return false } - -func (databaseServerExecutor) getReader(cache *Cache, cacheOK bool) databaseServerGetter { - if cacheOK { - return cache.presenceCache - } - return cache.Config.Presence -} - -type databaseServerGetter interface { - GetDatabaseServers(context.Context, string, ...services.MarshalOption) ([]types.DatabaseServer, error) -} - -var _ executor[types.DatabaseServer, databaseServerGetter] = databaseServerExecutor{} - -type databaseServiceExecutor struct{} - -func (databaseServiceExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.DatabaseService, error) { - resources, err := client.GetResourcesWithFilters(ctx, cache.Presence, proto.ListResourcesRequest{ResourceType: types.KindDatabaseService}) - if err != nil { - return nil, trace.Wrap(err) - } - - dbsvcs := make([]types.DatabaseService, len(resources)) - for i, resource := range resources { - dbsvc, ok := resource.(types.DatabaseService) - if !ok { - return nil, trace.BadParameter("unexpected resource %T", resource) - } - dbsvcs[i] = dbsvc - } - - return dbsvcs, nil -} - -func (databaseServiceExecutor) upsert(ctx context.Context, cache *Cache, resource types.DatabaseService) error { - _, err := cache.databaseServicesCache.UpsertDatabaseService(ctx, resource) - return trace.Wrap(err) -} - -func (databaseServiceExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.databaseServicesCache.DeleteAllDatabaseServices(ctx) -} - -func (databaseServiceExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.databaseServicesCache.DeleteDatabaseService(ctx, resource.GetName()) -} - -func (databaseServiceExecutor) isSingleton() bool { return false } - -func (databaseServiceExecutor) getReader(_ *Cache, _ bool) noReader { - return noReader{} -} - -var _ executor[types.DatabaseService, noReader] = databaseServiceExecutor{} - -type databaseExecutor struct{} - -func (databaseExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Database, error) { - return cache.Databases.GetDatabases(ctx) -} - -func (databaseExecutor) upsert(ctx context.Context, cache *Cache, resource types.Database) error { - if err := cache.databasesCache.CreateDatabase(ctx, resource); err != nil { - if !trace.IsAlreadyExists(err) { - return trace.Wrap(err) - } - return trace.Wrap(cache.databasesCache.UpdateDatabase(ctx, resource)) - } - - return nil -} - -func (databaseExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.databasesCache.DeleteAllDatabases(ctx) -} - -func (databaseExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.databasesCache.DeleteDatabase(ctx, resource.GetName()) -} - -func (databaseExecutor) isSingleton() bool { return false } - -func (databaseExecutor) getReader(cache *Cache, cacheOK bool) services.DatabaseGetter { - if cacheOK { - return cache.databasesCache - } - return cache.Config.Databases -} - -var _ executor[types.Database, services.DatabaseGetter] = databaseExecutor{} - -type databaseObjectExecutor struct{} - -func (databaseObjectExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*dbobjectv1.DatabaseObject, error) { - var out []*dbobjectv1.DatabaseObject - var nextToken string - for { - var page []*dbobjectv1.DatabaseObject - var err error - - page, nextToken, err = cache.DatabaseObjects.ListDatabaseObjects(ctx, 0, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - out = append(out, page...) - if nextToken == "" { - break - } - } - return out, nil -} - -func (databaseObjectExecutor) upsert(ctx context.Context, cache *Cache, resource *dbobjectv1.DatabaseObject) error { - _, err := cache.databaseObjectsCache.UpsertDatabaseObject(ctx, resource) - return trace.Wrap(err) -} - -func (databaseObjectExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return trace.Wrap(cache.databaseObjectsCache.DeleteAllDatabaseObjects(ctx)) -} - -func (databaseObjectExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return trace.Wrap(cache.databaseObjectsCache.DeleteDatabaseObject(ctx, resource.GetName())) -} - -func (databaseObjectExecutor) isSingleton() bool { return false } - -func (databaseObjectExecutor) getReader(cache *Cache, cacheOK bool) services.DatabaseObjectsGetter { - if cacheOK { - return cache.databaseObjectsCache - } - return cache.Config.DatabaseObjects -} - -var _ executor[*dbobjectv1.DatabaseObject, services.DatabaseObjectsGetter] = databaseObjectExecutor{} - -type appExecutor struct{} - -func (appExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Application, error) { - return cache.Apps.GetApps(ctx) -} - -func (appExecutor) upsert(ctx context.Context, cache *Cache, resource types.Application) error { - if err := cache.appsCache.CreateApp(ctx, resource); err != nil { - if !trace.IsAlreadyExists(err) { - return trace.Wrap(err) - } - return trace.Wrap(cache.appsCache.UpdateApp(ctx, resource)) - } - - return nil -} - -func (appExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.appsCache.DeleteAllApps(ctx) -} - -func (appExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.appsCache.DeleteApp(ctx, resource.GetName()) -} - -func (appExecutor) getReader(cache *Cache, cacheOK bool) services.AppGetter { - if cacheOK { - return cache.appsCache - } - return cache.Apps -} - -func (appExecutor) isSingleton() bool { return false } - -var _ executor[types.Application, services.AppGetter] = appExecutor{} - -type appServerExecutor struct{} - -func (appServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.AppServer, error) { - return cache.Presence.GetApplicationServers(ctx, apidefaults.Namespace) -} - -func (appServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.AppServer) error { - _, err := cache.presenceCache.UpsertApplicationServer(ctx, resource) - return trace.Wrap(err) -} - -func (appServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllApplicationServers(ctx, apidefaults.Namespace) -} - -func (appServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteApplicationServer(ctx, - resource.GetMetadata().Namespace, - resource.GetMetadata().Description, // Cache passes host ID via description field. - resource.GetName()) -} - -func (appServerExecutor) isSingleton() bool { return false } - -func (appServerExecutor) getReader(cache *Cache, cacheOK bool) appServerGetter { - if cacheOK { - return cache.presenceCache - } - return cache.Config.Presence -} - -type appServerGetter interface { - GetApplicationServers(context.Context, string) ([]types.AppServer, error) -} - -var _ executor[types.AppServer, appServerGetter] = appServerExecutor{} - -type appSessionExecutor struct{} - -func (appSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { - var ( - startKey string - sessions []types.WebSession - ) - for { - webSessions, nextKey, err := cache.AppSession.ListAppSessions(ctx, 0, startKey, "") - if err != nil { - return nil, trace.Wrap(err) - } - - if !loadSecrets { - for i := 0; i < len(webSessions); i++ { - webSessions[i] = webSessions[i].WithoutSecrets() - } - } - - sessions = append(sessions, webSessions...) - - if nextKey == "" { - break - } - startKey = nextKey - } - return sessions, nil -} - -func (appSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { - return cache.appSessionCache.UpsertAppSession(ctx, resource) -} - -func (appSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.appSessionCache.DeleteAllAppSessions(ctx) -} - -func (appSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.appSessionCache.DeleteAppSession(ctx, types.DeleteAppSessionRequest{ - SessionID: resource.GetName(), - }) -} - -func (appSessionExecutor) isSingleton() bool { return false } - -func (appSessionExecutor) getReader(cache *Cache, cacheOK bool) appSessionGetter { - if cacheOK { - return cache.appSessionCache - } - return cache.Config.AppSession -} - -type appSessionGetter interface { - GetAppSession(ctx context.Context, req types.GetAppSessionRequest) (types.WebSession, error) - ListAppSessions(ctx context.Context, pageSize int, pageToken, user string) ([]types.WebSession, string, error) -} - -var _ executor[types.WebSession, appSessionGetter] = appSessionExecutor{} - -type snowflakeSessionExecutor struct{} - -func (snowflakeSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { - webSessions, err := cache.SnowflakeSession.GetSnowflakeSessions(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - - if !loadSecrets { - for i := 0; i < len(webSessions); i++ { - webSessions[i] = webSessions[i].WithoutSecrets() - } - } - - return webSessions, nil -} - -func (snowflakeSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { - return cache.snowflakeSessionCache.UpsertSnowflakeSession(ctx, resource) -} - -func (snowflakeSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.snowflakeSessionCache.DeleteAllSnowflakeSessions(ctx) -} - -func (snowflakeSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.snowflakeSessionCache.DeleteSnowflakeSession(ctx, types.DeleteSnowflakeSessionRequest{ - SessionID: resource.GetName(), - }) -} - -func (snowflakeSessionExecutor) isSingleton() bool { return false } - -func (snowflakeSessionExecutor) getReader(cache *Cache, cacheOK bool) snowflakeSessionGetter { - if cacheOK { - return cache.snowflakeSessionCache - } - return cache.Config.SnowflakeSession -} - -type snowflakeSessionGetter interface { - GetSnowflakeSession(context.Context, types.GetSnowflakeSessionRequest) (types.WebSession, error) -} - -var _ executor[types.WebSession, snowflakeSessionGetter] = snowflakeSessionExecutor{} - -//nolint:revive // Because we want this to be IdP. -type samlIdPSessionExecutor struct{} - -func (samlIdPSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { - var ( - startKey string - sessions []types.WebSession - ) - for { - webSessions, nextKey, err := cache.SAMLIdPSession.ListSAMLIdPSessions(ctx, 0, startKey, "") - if err != nil { - return nil, trace.Wrap(err) - } - - if !loadSecrets { - for i := 0; i < len(webSessions); i++ { - webSessions[i] = webSessions[i].WithoutSecrets() - } - } - - sessions = append(sessions, webSessions...) - - if nextKey == "" { - break - } - startKey = nextKey - } - return sessions, nil -} - -func (samlIdPSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { - return cache.samlIdPSessionCache.UpsertSAMLIdPSession(ctx, resource) -} - -func (samlIdPSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.samlIdPSessionCache.DeleteAllSAMLIdPSessions(ctx) -} - -func (samlIdPSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.samlIdPSessionCache.DeleteSAMLIdPSession(ctx, types.DeleteSAMLIdPSessionRequest{ - SessionID: resource.GetName(), - }) -} - -func (samlIdPSessionExecutor) isSingleton() bool { return false } - -func (samlIdPSessionExecutor) getReader(cache *Cache, cacheOK bool) samlIdPSessionGetter { - if cacheOK { - return cache.samlIdPSessionCache - } - return cache.Config.SAMLIdPSession -} - -type samlIdPSessionGetter interface { - GetSAMLIdPSession(context.Context, types.GetSAMLIdPSessionRequest) (types.WebSession, error) -} - -var _ executor[types.WebSession, samlIdPSessionGetter] = samlIdPSessionExecutor{} - -type webSessionExecutor struct{} - -func (webSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { - webSessions, err := cache.WebSession.List(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - - if !loadSecrets { - for i := 0; i < len(webSessions); i++ { - webSessions[i] = webSessions[i].WithoutSecrets() - } - } - - return webSessions, nil -} - -func (webSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { - return cache.webSessionCache.Upsert(ctx, resource) -} - -func (webSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.webSessionCache.DeleteAll(ctx) -} - -func (webSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.webSessionCache.Delete(ctx, types.DeleteWebSessionRequest{ - SessionID: resource.GetName(), - }) -} - -func (webSessionExecutor) isSingleton() bool { return false } - -func (webSessionExecutor) getReader(cache *Cache, cacheOK bool) webSessionGetter { - if cacheOK { - return cache.webSessionCache - } - return cache.Config.WebSession -} - -type webSessionGetter interface { - Get(ctx context.Context, req types.GetWebSessionRequest) (types.WebSession, error) -} - -var _ executor[types.WebSession, webSessionGetter] = webSessionExecutor{} - -type webTokenExecutor struct{} - -func (webTokenExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebToken, error) { - return cache.WebToken.List(ctx) -} - -func (webTokenExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebToken) error { - return cache.webTokenCache.Upsert(ctx, resource) -} - -func (webTokenExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.webTokenCache.DeleteAll(ctx) -} - -func (webTokenExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.webTokenCache.Delete(ctx, types.DeleteWebTokenRequest{ - Token: resource.GetName(), - }) -} - -func (webTokenExecutor) isSingleton() bool { return false } - -func (webTokenExecutor) getReader(cache *Cache, cacheOK bool) webTokenGetter { - if cacheOK { - return cache.webTokenCache - } - return cache.Config.WebToken -} - -type webTokenGetter interface { - Get(ctx context.Context, req types.GetWebTokenRequest) (types.WebToken, error) -} - -var _ executor[types.WebToken, webTokenGetter] = webTokenExecutor{} - -type kubeServerExecutor struct{} - -func (kubeServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.KubeServer, error) { - return cache.Presence.GetKubernetesServers(ctx) -} - -func (kubeServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.KubeServer) error { - _, err := cache.presenceCache.UpsertKubernetesServer(ctx, resource) - return trace.Wrap(err) -} - -func (kubeServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllKubernetesServers(ctx) -} - -func (kubeServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteKubernetesServer( - ctx, - resource.GetMetadata().Description, // Cache passes host ID via description field. - resource.GetName(), - ) -} - -func (kubeServerExecutor) isSingleton() bool { return false } - -func (kubeServerExecutor) getReader(cache *Cache, cacheOK bool) kubeServerGetter { - if cacheOK { - return cache.presenceCache - } - return cache.Config.Presence -} - -type kubeServerGetter interface { - GetKubernetesServers(context.Context) ([]types.KubeServer, error) -} - -var _ executor[types.KubeServer, kubeServerGetter] = kubeServerExecutor{} - -type authPreferenceExecutor struct{} - -func (authPreferenceExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.AuthPreference, error) { - authPref, err := cache.ClusterConfig.GetAuthPreference(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - return []types.AuthPreference{authPref}, nil -} - -func (authPreferenceExecutor) upsert(ctx context.Context, cache *Cache, resource types.AuthPreference) error { - _, err := cache.clusterConfigCache.UpsertAuthPreference(ctx, resource) - return trace.Wrap(err) -} - -func (authPreferenceExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteAuthPreference(ctx) -} - -func (authPreferenceExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteAuthPreference(ctx) -} - -func (authPreferenceExecutor) isSingleton() bool { return true } - -func (authPreferenceExecutor) getReader(cache *Cache, cacheOK bool) authPreferenceGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type authPreferenceGetter interface { - GetAuthPreference(ctx context.Context) (types.AuthPreference, error) -} - -var _ executor[types.AuthPreference, authPreferenceGetter] = authPreferenceExecutor{} - -type clusterAuditConfigExecutor struct{} - -func (clusterAuditConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ClusterAuditConfig, error) { - auditConfig, err := cache.ClusterConfig.GetClusterAuditConfig(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - return []types.ClusterAuditConfig{auditConfig}, nil -} - -func (clusterAuditConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.ClusterAuditConfig) error { - return cache.clusterConfigCache.SetClusterAuditConfig(ctx, resource) -} - -func (clusterAuditConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteClusterAuditConfig(ctx) -} - -func (clusterAuditConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteClusterAuditConfig(ctx) -} - -func (clusterAuditConfigExecutor) isSingleton() bool { return true } - -func (clusterAuditConfigExecutor) getReader(cache *Cache, cacheOK bool) clusterAuditConfigGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type clusterAuditConfigGetter interface { - GetClusterAuditConfig(context.Context) (types.ClusterAuditConfig, error) -} - -var _ executor[types.ClusterAuditConfig, clusterAuditConfigGetter] = clusterAuditConfigExecutor{} - -type clusterNetworkingConfigExecutor struct{} - -func (clusterNetworkingConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ClusterNetworkingConfig, error) { - networkingConfig, err := cache.ClusterConfig.GetClusterNetworkingConfig(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - return []types.ClusterNetworkingConfig{networkingConfig}, nil -} - -func (clusterNetworkingConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.ClusterNetworkingConfig) error { - _, err := cache.clusterConfigCache.UpsertClusterNetworkingConfig(ctx, resource) - return trace.Wrap(err) -} - -func (clusterNetworkingConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteClusterNetworkingConfig(ctx) -} - -func (clusterNetworkingConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteClusterNetworkingConfig(ctx) -} - -func (clusterNetworkingConfigExecutor) isSingleton() bool { return true } - -func (clusterNetworkingConfigExecutor) getReader(cache *Cache, cacheOK bool) clusterNetworkingConfigGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type clusterNetworkingConfigGetter interface { - GetClusterNetworkingConfig(context.Context) (types.ClusterNetworkingConfig, error) -} - -var _ executor[types.ClusterNetworkingConfig, clusterNetworkingConfigGetter] = clusterNetworkingConfigExecutor{} - -type uiConfigExecutor struct{} - -func (uiConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.UIConfig, error) { - uiConfig, err := cache.ClusterConfig.GetUIConfig(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - return []types.UIConfig{uiConfig}, nil -} - -func (uiConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.UIConfig) error { - return cache.clusterConfigCache.SetUIConfig(ctx, resource) -} - -func (uiConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteUIConfig(ctx) -} - -func (uiConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteUIConfig(ctx) -} - -func (uiConfigExecutor) isSingleton() bool { return true } - -func (uiConfigExecutor) getReader(cache *Cache, cacheOK bool) uiConfigGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type uiConfigGetter interface { - GetUIConfig(context.Context) (types.UIConfig, error) -} - -var _ executor[types.UIConfig, uiConfigGetter] = uiConfigExecutor{} - -type sessionRecordingConfigExecutor struct{} - -func (sessionRecordingConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.SessionRecordingConfig, error) { - sessionRecordingConfig, err := cache.ClusterConfig.GetSessionRecordingConfig(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - return []types.SessionRecordingConfig{sessionRecordingConfig}, nil -} - -func (sessionRecordingConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.SessionRecordingConfig) error { - _, err := cache.clusterConfigCache.UpsertSessionRecordingConfig(ctx, resource) - return trace.Wrap(err) -} - -func (sessionRecordingConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteSessionRecordingConfig(ctx) -} - -func (sessionRecordingConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteSessionRecordingConfig(ctx) -} - -func (sessionRecordingConfigExecutor) isSingleton() bool { return true } - -func (sessionRecordingConfigExecutor) getReader(cache *Cache, cacheOK bool) sessionRecordingConfigGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type sessionRecordingConfigGetter interface { - GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error) -} - -var _ executor[types.SessionRecordingConfig, sessionRecordingConfigGetter] = sessionRecordingConfigExecutor{} - -type installerConfigExecutor struct{} - -func (installerConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Installer, error) { - return cache.ClusterConfig.GetInstallers(ctx) -} - -func (installerConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.Installer) error { - return cache.clusterConfigCache.SetInstaller(ctx, resource) -} - -func (installerConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.clusterConfigCache.DeleteAllInstallers(ctx) -} - -func (installerConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.clusterConfigCache.DeleteInstaller(ctx, resource.GetName()) -} - -func (installerConfigExecutor) isSingleton() bool { return false } - -func (installerConfigExecutor) getReader(cache *Cache, cacheOK bool) installerGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type installerGetter interface { - GetInstallers(context.Context) ([]types.Installer, error) - GetInstaller(ctx context.Context, name string) (types.Installer, error) -} - -var _ executor[types.Installer, installerGetter] = installerConfigExecutor{} - -type networkRestrictionsExecutor struct{} - -func (networkRestrictionsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.NetworkRestrictions, error) { - restrictions, err := cache.Restrictions.GetNetworkRestrictions(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - return []types.NetworkRestrictions{restrictions}, nil -} - -func (networkRestrictionsExecutor) upsert(ctx context.Context, cache *Cache, resource types.NetworkRestrictions) error { - return cache.restrictionsCache.SetNetworkRestrictions(ctx, resource) -} - -func (networkRestrictionsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.restrictionsCache.DeleteNetworkRestrictions(ctx) -} - -func (networkRestrictionsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.restrictionsCache.DeleteNetworkRestrictions(ctx) -} - -func (networkRestrictionsExecutor) isSingleton() bool { return true } - -func (networkRestrictionsExecutor) getReader(cache *Cache, cacheOK bool) networkRestrictionGetter { - if cacheOK { - return cache.restrictionsCache - } - return cache.Config.Restrictions -} - -type networkRestrictionGetter interface { - GetNetworkRestrictions(context.Context) (types.NetworkRestrictions, error) -} - -var _ executor[types.NetworkRestrictions, networkRestrictionGetter] = networkRestrictionsExecutor{} - -type lockExecutor struct{} - -func (lockExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Lock, error) { - return cache.Access.GetLocks(ctx, false) -} - -func (lockExecutor) upsert(ctx context.Context, cache *Cache, resource types.Lock) error { - return cache.accessCache.UpsertLock(ctx, resource) -} - -func (lockExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.accessCache.DeleteAllLocks(ctx) -} - -func (lockExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.accessCache.DeleteLock(ctx, resource.GetName()) -} - -func (lockExecutor) isSingleton() bool { return false } - -func (lockExecutor) getReader(cache *Cache, cacheOK bool) services.LockGetter { - if cacheOK { - return cache.accessCache - } - return cache.Config.Access -} - -var _ executor[types.Lock, services.LockGetter] = lockExecutor{} - -type windowsDesktopServicesExecutor struct{} - -func (windowsDesktopServicesExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WindowsDesktopService, error) { - return cache.Presence.GetWindowsDesktopServices(ctx) -} - -func (windowsDesktopServicesExecutor) upsert(ctx context.Context, cache *Cache, resource types.WindowsDesktopService) error { - _, err := cache.presenceCache.UpsertWindowsDesktopService(ctx, resource) - return trace.Wrap(err) -} - -func (windowsDesktopServicesExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.presenceCache.DeleteAllWindowsDesktopServices(ctx) -} - -func (windowsDesktopServicesExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.presenceCache.DeleteWindowsDesktopService(ctx, resource.GetName()) -} - -func (windowsDesktopServicesExecutor) isSingleton() bool { return false } - -func (windowsDesktopServicesExecutor) getReader(cache *Cache, cacheOK bool) windowsDesktopServiceGetter { - if cacheOK { - return windowsDesktopServiceAggregate{ - Presence: cache.presenceCache, - WindowsDesktops: cache.windowsDesktopsCache, - } - } - return windowsDesktopServiceAggregate{ - Presence: cache.Config.Presence, - WindowsDesktops: cache.Config.WindowsDesktops, - } -} - -type windowsDesktopServiceAggregate struct { - services.Presence - services.WindowsDesktops -} - -type windowsDesktopServiceGetter interface { - GetWindowsDesktopServices(ctx context.Context) ([]types.WindowsDesktopService, error) - GetWindowsDesktopService(ctx context.Context, name string) (types.WindowsDesktopService, error) - ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error) -} - -var _ executor[types.WindowsDesktopService, windowsDesktopServiceGetter] = windowsDesktopServicesExecutor{} - -type windowsDesktopsExecutor struct{} - -func (windowsDesktopsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WindowsDesktop, error) { - return cache.WindowsDesktops.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) -} - -func (windowsDesktopsExecutor) upsert(ctx context.Context, cache *Cache, resource types.WindowsDesktop) error { - return cache.windowsDesktopsCache.UpsertWindowsDesktop(ctx, resource) -} - -func (windowsDesktopsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.windowsDesktopsCache.DeleteAllWindowsDesktops(ctx) -} - -func (windowsDesktopsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.windowsDesktopsCache.DeleteWindowsDesktop(ctx, - resource.GetMetadata().Description, // Cache passes host ID via description field. - resource.GetName(), - ) -} - -func (windowsDesktopsExecutor) isSingleton() bool { return false } - -func (windowsDesktopsExecutor) getReader(cache *Cache, cacheOK bool) windowsDesktopsGetter { - if cacheOK { - return cache.windowsDesktopsCache - } - return cache.Config.WindowsDesktops -} - -type windowsDesktopsGetter interface { - GetWindowsDesktops(context.Context, types.WindowsDesktopFilter) ([]types.WindowsDesktop, error) - ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) -} - -var _ executor[types.WindowsDesktop, windowsDesktopsGetter] = windowsDesktopsExecutor{} - -type dynamicWindowsDesktopsExecutor struct{} - -func (dynamicWindowsDesktopsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.DynamicWindowsDesktop, error) { - var desktops []types.DynamicWindowsDesktop - next := "" - for { - d, token, err := cache.Config.DynamicWindowsDesktops.ListDynamicWindowsDesktops(ctx, defaults.MaxIterationLimit, next) - if err != nil { - return nil, err - } - desktops = append(desktops, d...) - if token == "" { - break - } - next = token - } - return desktops, nil -} - -func (dynamicWindowsDesktopsExecutor) upsert(ctx context.Context, cache *Cache, resource types.DynamicWindowsDesktop) error { - _, err := cache.dynamicWindowsDesktopsCache.UpsertDynamicWindowsDesktop(ctx, resource) - return err -} - -func (dynamicWindowsDesktopsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.dynamicWindowsDesktopsCache.DeleteAllDynamicWindowsDesktops(ctx) -} - -func (dynamicWindowsDesktopsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.dynamicWindowsDesktopsCache.DeleteDynamicWindowsDesktop(ctx, resource.GetName()) -} - -func (dynamicWindowsDesktopsExecutor) isSingleton() bool { return false } - -func (dynamicWindowsDesktopsExecutor) getReader(cache *Cache, cacheOK bool) dynamicWindowsDesktopsGetter { - if cacheOK { - return cache.dynamicWindowsDesktopsCache - } - return cache.Config.DynamicWindowsDesktops -} - -type dynamicWindowsDesktopsGetter interface { - GetDynamicWindowsDesktop(ctx context.Context, name string) (types.DynamicWindowsDesktop, error) - ListDynamicWindowsDesktops(ctx context.Context, pageSize int, nextPage string) ([]types.DynamicWindowsDesktop, string, error) -} - -var _ executor[types.DynamicWindowsDesktop, dynamicWindowsDesktopsGetter] = dynamicWindowsDesktopsExecutor{} - -type kubeClusterExecutor struct{} - -func (kubeClusterExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.KubeCluster, error) { - return cache.Kubernetes.GetKubernetesClusters(ctx) -} - -func (kubeClusterExecutor) upsert(ctx context.Context, cache *Cache, resource types.KubeCluster) error { - if err := cache.kubernetesCache.CreateKubernetesCluster(ctx, resource); err != nil { - if !trace.IsAlreadyExists(err) { - return trace.Wrap(err) - } - return trace.Wrap(cache.kubernetesCache.UpdateKubernetesCluster(ctx, resource)) - } - - return nil -} - -func (kubeClusterExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.kubernetesCache.DeleteAllKubernetesClusters(ctx) -} - -func (kubeClusterExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.kubernetesCache.DeleteKubernetesCluster(ctx, resource.GetName()) -} - -func (kubeClusterExecutor) isSingleton() bool { return false } - -func (kubeClusterExecutor) getReader(cache *Cache, cacheOK bool) kubernetesClusterGetter { - if cacheOK { - return cache.kubernetesCache - } - return cache.Config.Kubernetes -} - -type kubernetesClusterGetter interface { - GetKubernetesClusters(ctx context.Context) ([]types.KubeCluster, error) - GetKubernetesCluster(ctx context.Context, name string) (types.KubeCluster, error) -} - -type kubeWaitingContainerExecutor struct{} - -func (kubeWaitingContainerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*kubewaitingcontainerpb.KubernetesWaitingContainer, error) { - var ( - startKey string - allConts []*kubewaitingcontainerpb.KubernetesWaitingContainer - ) - for { - conts, nextKey, err := cache.KubeWaitingContainers.ListKubernetesWaitingContainers(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - allConts = append(allConts, conts...) - - if nextKey == "" { - break - } - startKey = nextKey - } - return allConts, nil -} - -func (kubeWaitingContainerExecutor) upsert(ctx context.Context, cache *Cache, resource *kubewaitingcontainerpb.KubernetesWaitingContainer) error { - _, err := cache.kubeWaitingContsCache.UpsertKubernetesWaitingContainer(ctx, resource) - return trace.Wrap(err) -} - -func (kubeWaitingContainerExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return trace.Wrap(cache.kubeWaitingContsCache.DeleteAllKubernetesWaitingContainers(ctx)) -} - -func (kubeWaitingContainerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - switch r := resource.(type) { - case types.Resource153Unwrapper: - switch wc := r.Unwrap().(type) { - case *kubewaitingcontainerpb.KubernetesWaitingContainer: - err := cache.kubeWaitingContsCache.DeleteKubernetesWaitingContainer(ctx, &kubewaitingcontainerpb.DeleteKubernetesWaitingContainerRequest{ - Username: wc.Spec.Username, - Cluster: wc.Spec.Cluster, - Namespace: wc.Spec.Namespace, - PodName: wc.Spec.PodName, - ContainerName: wc.Spec.ContainerName, - }) - return trace.Wrap(err) - } - } - - return trace.BadParameter("unknown KubeWaitingContainer type, expected *kubewaitingcontainerpb.KubernetesWaitingContainer, got %T", resource) -} - -func (kubeWaitingContainerExecutor) isSingleton() bool { return false } - -func (kubeWaitingContainerExecutor) getReader(cache *Cache, cacheOK bool) kubernetesWaitingContainerGetter { - if cacheOK { - return cache.kubeWaitingContsCache - } - return cache.Config.KubeWaitingContainers -} - -type kubernetesWaitingContainerGetter interface { - ListKubernetesWaitingContainers(ctx context.Context, pageSize int, pageToken string) ([]*kubewaitingcontainerpb.KubernetesWaitingContainer, string, error) - GetKubernetesWaitingContainer(ctx context.Context, req *kubewaitingcontainerpb.GetKubernetesWaitingContainerRequest) (*kubewaitingcontainerpb.KubernetesWaitingContainer, error) -} - -var _ executor[*kubewaitingcontainerpb.KubernetesWaitingContainer, kubernetesWaitingContainerGetter] = kubeWaitingContainerExecutor{} - -type staticHostUserExecutor struct{} - -func (staticHostUserExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*userprovisioningpb.StaticHostUser, error) { - var ( - startKey string - allUsers []*userprovisioningpb.StaticHostUser - ) - for { - users, nextKey, err := cache.StaticHostUsers.ListStaticHostUsers(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - allUsers = append(allUsers, users...) - - if nextKey == "" { - break - } - startKey = nextKey - } - return allUsers, nil -} - -func (staticHostUserExecutor) upsert(ctx context.Context, cache *Cache, resource *userprovisioningpb.StaticHostUser) error { - _, err := cache.staticHostUsersCache.UpsertStaticHostUser(ctx, resource) - return trace.Wrap(err) -} - -func (staticHostUserExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return trace.Wrap(cache.staticHostUsersCache.DeleteAllStaticHostUsers(ctx)) -} - -func (staticHostUserExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return trace.Wrap(cache.staticHostUsersCache.DeleteStaticHostUser(ctx, resource.GetName())) -} - -func (staticHostUserExecutor) isSingleton() bool { return false } - -func (staticHostUserExecutor) getReader(cache *Cache, cacheOK bool) staticHostUserGetter { - if cacheOK { - return cache.staticHostUsersCache - } - return cache.Config.StaticHostUsers -} - -type staticHostUserGetter interface { - ListStaticHostUsers(ctx context.Context, pageSize int, pageToken string) ([]*userprovisioningpb.StaticHostUser, string, error) - GetStaticHostUser(ctx context.Context, name string) (*userprovisioningpb.StaticHostUser, error) -} - -type crownJewelsExecutor struct{} - -func (crownJewelsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*crownjewelv1.CrownJewel, error) { - var resources []*crownjewelv1.CrownJewel - var nextToken string - for { - var page []*crownjewelv1.CrownJewel - var err error - page, nextToken, err = cache.CrownJewels.ListCrownJewels(ctx, 0 /* page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - resources = append(resources, page...) - - if nextToken == "" { - break - } - } - return resources, nil -} - -func (crownJewelsExecutor) upsert(ctx context.Context, cache *Cache, resource *crownjewelv1.CrownJewel) error { - _, err := cache.crownJewelsCache.UpsertCrownJewel(ctx, resource) - return trace.Wrap(err) -} - -func (crownJewelsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.crownJewelsCache.DeleteAllCrownJewels(ctx) -} - -func (crownJewelsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.crownJewelsCache.DeleteCrownJewel(ctx, resource.GetName()) -} - -func (crownJewelsExecutor) isSingleton() bool { return false } - -func (crownJewelsExecutor) getReader(cache *Cache, cacheOK bool) crownjewelsGetter { - if cacheOK { - return cache.crownJewelsCache - } - return cache.Config.CrownJewels -} - -var _ executor[*crownjewelv1.CrownJewel, crownjewelsGetter] = crownJewelsExecutor{} - -type userTasksExecutor struct{} - -func (userTasksExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*usertasksv1.UserTask, error) { - var resources []*usertasksv1.UserTask - var nextToken string - for { - var page []*usertasksv1.UserTask - var err error - page, nextToken, err = cache.UserTasks.ListUserTasks(ctx, 0 /* page size */, nextToken, &usertasksv1.ListUserTasksFilters{}) - if err != nil { - return nil, trace.Wrap(err) - } - resources = append(resources, page...) - - if nextToken == "" { - break - } - } - return resources, nil -} - -func (userTasksExecutor) upsert(ctx context.Context, cache *Cache, resource *usertasksv1.UserTask) error { - _, err := cache.userTasksCache.UpsertUserTask(ctx, resource) - return trace.Wrap(err) -} - -func (userTasksExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.userTasksCache.DeleteAllUserTasks(ctx) -} - -func (userTasksExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.userTasksCache.DeleteUserTask(ctx, resource.GetName()) -} - -func (userTasksExecutor) isSingleton() bool { return false } - -func (userTasksExecutor) getReader(cache *Cache, cacheOK bool) userTasksGetter { - if cacheOK { - return cache.userTasksCache - } - return cache.Config.UserTasks -} - -var _ executor[*usertasksv1.UserTask, userTasksGetter] = userTasksExecutor{} - -//nolint:revive // Because we want this to be IdP. -type samlIdPServiceProvidersExecutor struct{} - -func (samlIdPServiceProvidersExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.SAMLIdPServiceProvider, error) { - var ( - startKey string - sps []types.SAMLIdPServiceProvider - ) - for { - var samlProviders []types.SAMLIdPServiceProvider - var err error - samlProviders, startKey, err = cache.SAMLIdPServiceProviders.ListSAMLIdPServiceProviders(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - sps = append(sps, samlProviders...) - - if startKey == "" { - break - } - } - - return sps, nil -} - -func (samlIdPServiceProvidersExecutor) upsert(ctx context.Context, cache *Cache, resource types.SAMLIdPServiceProvider) error { - err := cache.samlIdPServiceProvidersCache.CreateSAMLIdPServiceProvider(ctx, resource) - if trace.IsAlreadyExists(err) { - err = cache.samlIdPServiceProvidersCache.UpdateSAMLIdPServiceProvider(ctx, resource) - } - return trace.Wrap(err) -} - -func (samlIdPServiceProvidersExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.samlIdPServiceProvidersCache.DeleteAllSAMLIdPServiceProviders(ctx) -} - -func (samlIdPServiceProvidersExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.samlIdPServiceProvidersCache.DeleteSAMLIdPServiceProvider(ctx, resource.GetName()) -} - -func (samlIdPServiceProvidersExecutor) isSingleton() bool { return false } - -func (samlIdPServiceProvidersExecutor) getReader(cache *Cache, cacheOK bool) samlIdPServiceProviderGetter { - if cacheOK { - return cache.samlIdPServiceProvidersCache - } - return cache.Config.SAMLIdPServiceProviders -} - -type samlIdPServiceProviderGetter interface { - ListSAMLIdPServiceProviders(context.Context, int, string) ([]types.SAMLIdPServiceProvider, string, error) - GetSAMLIdPServiceProvider(ctx context.Context, name string) (types.SAMLIdPServiceProvider, error) -} - -var _ executor[types.SAMLIdPServiceProvider, samlIdPServiceProviderGetter] = samlIdPServiceProvidersExecutor{} - -type userGroupsExecutor struct{} - -func (userGroupsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.UserGroup, error) { - var ( - startKey string - resources []types.UserGroup - ) - for { - var userGroups []types.UserGroup - var err error - userGroups, startKey, err = cache.UserGroups.ListUserGroups(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - resources = append(resources, userGroups...) - - if startKey == "" { - break - } - } - - return resources, nil -} - -func (userGroupsExecutor) upsert(ctx context.Context, cache *Cache, resource types.UserGroup) error { - err := cache.userGroupsCache.CreateUserGroup(ctx, resource) - if trace.IsAlreadyExists(err) { - err = cache.userGroupsCache.UpdateUserGroup(ctx, resource) - } - return trace.Wrap(err) -} - -func (userGroupsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.userGroupsCache.DeleteAllUserGroups(ctx) -} - -func (userGroupsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.userGroupsCache.DeleteUserGroup(ctx, resource.GetName()) -} - -func (userGroupsExecutor) isSingleton() bool { return false } - -func (userGroupsExecutor) getReader(cache *Cache, cacheOK bool) userGroupGetter { - if cacheOK { - return cache.userGroupsCache - } - return cache.Config.UserGroups -} - -type userGroupGetter interface { - GetUserGroup(ctx context.Context, name string) (types.UserGroup, error) - ListUserGroups(context.Context, int, string) ([]types.UserGroup, string, error) -} - -var _ executor[types.UserGroup, userGroupGetter] = userGroupsExecutor{} - -type oktaImportRulesExecutor struct{} - -func (oktaImportRulesExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.OktaImportRule, error) { - var ( - startKey string - resources []types.OktaImportRule - ) - for { - var importRules []types.OktaImportRule - var err error - importRules, startKey, err = cache.Okta.ListOktaImportRules(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - resources = append(resources, importRules...) - - if startKey == "" { - break - } - } - - return resources, nil -} - -func (oktaImportRulesExecutor) upsert(ctx context.Context, cache *Cache, resource types.OktaImportRule) error { - _, err := cache.oktaCache.CreateOktaImportRule(ctx, resource) - if trace.IsAlreadyExists(err) { - _, err = cache.oktaCache.UpdateOktaImportRule(ctx, resource) - } - return trace.Wrap(err) -} - -func (oktaImportRulesExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.oktaCache.DeleteAllOktaImportRules(ctx) -} - -func (oktaImportRulesExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.oktaCache.DeleteOktaImportRule(ctx, resource.GetName()) -} - -func (oktaImportRulesExecutor) isSingleton() bool { return false } - -func (oktaImportRulesExecutor) getReader(cache *Cache, cacheOK bool) oktaImportRuleGetter { - if cacheOK { - return cache.oktaCache - } - return cache.Config.Okta -} - -type oktaImportRuleGetter interface { - ListOktaImportRules(context.Context, int, string) ([]types.OktaImportRule, string, error) - GetOktaImportRule(ctx context.Context, name string) (types.OktaImportRule, error) -} - -var _ executor[types.OktaImportRule, oktaImportRuleGetter] = oktaImportRulesExecutor{} - -type oktaAssignmentsExecutor struct{} - -func (oktaAssignmentsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.OktaAssignment, error) { - var ( - startKey string - resources []types.OktaAssignment - ) - for { - var assignments []types.OktaAssignment - var err error - assignments, startKey, err = cache.Okta.ListOktaAssignments(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - resources = append(resources, assignments...) - - if startKey == "" { - break - } - } - - return resources, nil -} - -func (oktaAssignmentsExecutor) upsert(ctx context.Context, cache *Cache, resource types.OktaAssignment) error { - _, err := cache.oktaCache.CreateOktaAssignment(ctx, resource) - if trace.IsAlreadyExists(err) { - _, err = cache.oktaCache.UpdateOktaAssignment(ctx, resource) - } - return trace.Wrap(err) -} - -func (oktaAssignmentsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.oktaCache.DeleteAllOktaAssignments(ctx) -} - -func (oktaAssignmentsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.oktaCache.DeleteOktaAssignment(ctx, resource.GetName()) -} - -func (oktaAssignmentsExecutor) isSingleton() bool { return false } - -func (oktaAssignmentsExecutor) getReader(cache *Cache, cacheOK bool) oktaAssignmentGetter { - if cacheOK { - return cache.oktaCache - } - return cache.Config.Okta -} - -type oktaAssignmentGetter interface { - GetOktaAssignment(ctx context.Context, name string) (types.OktaAssignment, error) - ListOktaAssignments(context.Context, int, string) ([]types.OktaAssignment, string, error) -} - -var _ executor[types.OktaAssignment, oktaAssignmentGetter] = oktaAssignmentsExecutor{} - -// collectionReader extends the collection interface, adding routing capabilities. -type collectionReader[R any] interface { - collection - - // getReader returns the appropriate reader type T based on the health status of the cache. - // Reader type R provides getter methods related to the collection, e.g. GetNodes(), GetRoles(). - // Note that cacheOK set to true means that cache is overall healthy and the collection was confirmed as supported. - getReader(cacheOK bool) R -} - -type resourceGetter interface { - ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) -} - -type integrationsExecutor struct{} - -func (integrationsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Integration, error) { - var ( - startKey string - resources []types.Integration - ) - for { - var igs []types.Integration - var err error - igs, startKey, err = cache.Integrations.ListIntegrations(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - resources = append(resources, igs...) - - if startKey == "" { - break - } - } - - return resources, nil -} - -func (integrationsExecutor) upsert(ctx context.Context, cache *Cache, resource types.Integration) error { - _, err := cache.integrationsCache.CreateIntegration(ctx, resource) - if trace.IsAlreadyExists(err) { - _, err = cache.integrationsCache.UpdateIntegration(ctx, resource) - } - return trace.Wrap(err) -} - -func (integrationsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.integrationsCache.DeleteAllIntegrations(ctx) -} - -func (integrationsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.integrationsCache.DeleteIntegration(ctx, resource.GetName()) -} - -func (integrationsExecutor) isSingleton() bool { return false } - -func (integrationsExecutor) getReader(cache *Cache, cacheOK bool) services.IntegrationsGetter { - if cacheOK { - return cache.integrationsCache - } - return cache.Config.Integrations +// collections is the group of resource [collection]s +// that the [Cache] supports. +type collections struct { + byKind map[resourceKind]collectionHandler } -var _ executor[types.Integration, services.IntegrationsGetter] = integrationsExecutor{} - -type discoveryConfigExecutor struct{} - -func (discoveryConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*discoveryconfig.DiscoveryConfig, error) { - var discoveryConfigs []*discoveryconfig.DiscoveryConfig - var nextToken string - for { - var page []*discoveryconfig.DiscoveryConfig - var err error - - page, nextToken, err = cache.DiscoveryConfigs.ListDiscoveryConfigs(ctx, 0 /* default page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - - discoveryConfigs = append(discoveryConfigs, page...) - - if nextToken == "" { - break - } +// isKnownUncollectedKind is true if a resource kind is not stored in +// the cache itself but it's only configured in the cache so that the +// resources events can be processed by downstream watchers. +func isKnownUncollectedKind(kind string) bool { + switch kind { + case types.KindAccessRequest, types.KindHeadlessAuthentication: + return true + default: + return false } - return discoveryConfigs, nil } -func (discoveryConfigExecutor) upsert(ctx context.Context, cache *Cache, resource *discoveryconfig.DiscoveryConfig) error { - _, err := cache.discoveryConfigsCache.UpsertDiscoveryConfig(ctx, resource) - return trace.Wrap(err) -} - -func (discoveryConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.discoveryConfigsCache.DeleteAllDiscoveryConfigs(ctx) -} - -func (discoveryConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.discoveryConfigsCache.DeleteDiscoveryConfig(ctx, resource.GetName()) -} - -func (discoveryConfigExecutor) isSingleton() bool { return false } - -func (discoveryConfigExecutor) getReader(cache *Cache, cacheOK bool) services.DiscoveryConfigsGetter { - if cacheOK { - return cache.discoveryConfigsCache +// setupCollections ensures that the appropriate [collection] is +// initialized for all provided [types.WatcKind]s. An error is +// returned if a [types.WatchKind] has no associated [collection]. +func setupCollections(c Config, legacyCollections map[resourceKind]legacyCollection) (*collections, error) { + out := &collections{ + byKind: make(map[resourceKind]collectionHandler, 1), } - return cache.Config.DiscoveryConfigs -} -var _ executor[*discoveryconfig.DiscoveryConfig, services.DiscoveryConfigsGetter] = discoveryConfigExecutor{} - -type auditQueryExecutor struct{} - -func (auditQueryExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*secreports.AuditQuery, error) { - var out []*secreports.AuditQuery - var nextToken string - for { - var page []*secreports.AuditQuery - var err error - - page, nextToken, err = cache.secReportsCache.ListSecurityAuditQueries(ctx, 0 /* default page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - out = append(out, page...) - if nextToken == "" { - break + for _, watch := range c.Watches { + if isKnownUncollectedKind(watch.Kind) { + continue } - } - return out, nil -} - -func (auditQueryExecutor) upsert(ctx context.Context, cache *Cache, resource *secreports.AuditQuery) error { - err := cache.secReportsCache.UpsertSecurityAuditQuery(ctx, resource) - return trace.Wrap(err) -} - -func (auditQueryExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return trace.Wrap(cache.secReportsCache.DeleteAllSecurityReports(ctx)) -} - -func (auditQueryExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return trace.Wrap(cache.secReportsCache.DeleteSecurityAuditQuery(ctx, resource.GetName())) -} - -func (auditQueryExecutor) isSingleton() bool { return false } - -func (auditQueryExecutor) getReader(cache *Cache, cacheOK bool) services.SecurityAuditQueryGetter { - if cacheOK { - return cache.secReportsCache - } - return cache.Config.SecReports -} - -var _ executor[*secreports.AuditQuery, services.SecurityAuditQueryGetter] = auditQueryExecutor{} - -type secReportExecutor struct{} -func (secReportExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*secreports.Report, error) { - var out []*secreports.Report - var nextToken string - for { - var page []*secreports.Report - var err error - - page, nextToken, err = cache.secReportsCache.ListSecurityReports(ctx, 0 /* default page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - out = append(out, page...) - if nextToken == "" { - break + resourceKind := resourceKindFromWatchKind(watch) + switch watch.Kind { + default: + _, legacyOk := legacyCollections[resourceKind] + if _, ok := out.byKind[resourceKind]; !ok && !legacyOk { + return nil, trace.BadParameter("resource %q is not supported", watch.Kind) + } } - } - return out, nil -} - -func (secReportExecutor) upsert(ctx context.Context, cache *Cache, resource *secreports.Report) error { - err := cache.secReportsCache.UpsertSecurityReport(ctx, resource) - return trace.Wrap(err) -} - -func (secReportExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return trace.Wrap(cache.secReportsCache.DeleteAllSecurityReports(ctx)) -} - -func (secReportExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return trace.Wrap(cache.secReportsCache.DeleteSecurityReport(ctx, resource.GetName())) -} - -func (secReportExecutor) isSingleton() bool { return false } -func (secReportExecutor) getReader(cache *Cache, cacheOK bool) services.SecurityReportGetter { - if cacheOK { - return cache.secReportsCache } - return cache.Config.SecReports -} - -var _ executor[*secreports.Report, services.SecurityReportGetter] = secReportExecutor{} - -type secReportStateExecutor struct{} - -func (secReportStateExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*secreports.ReportState, error) { - var out []*secreports.ReportState - var nextToken string - for { - var page []*secreports.ReportState - var err error - page, nextToken, err = cache.secReportsCache.ListSecurityReportsStates(ctx, 0 /* default page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - out = append(out, page...) - if nextToken == "" { - break - } - } return out, nil } - -func (secReportStateExecutor) upsert(ctx context.Context, cache *Cache, resource *secreports.ReportState) error { - err := cache.secReportsCache.UpsertSecurityReportsState(ctx, resource) - return trace.Wrap(err) -} - -func (secReportStateExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return trace.Wrap(cache.secReportsCache.DeleteAllSecurityReportsStates(ctx)) -} - -func (secReportStateExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return trace.Wrap(cache.secReportsCache.DeleteSecurityReportsState(ctx, resource.GetName())) -} - -func (secReportStateExecutor) isSingleton() bool { return false } - -func (secReportStateExecutor) getReader(cache *Cache, cacheOK bool) services.SecurityReportStateGetter { - if cacheOK { - return cache.secReportsCache - } - return cache.Config.SecReports -} - -var _ executor[*secreports.ReportState, services.SecurityReportStateGetter] = secReportStateExecutor{} - -// noopExecutor can be used when a resource's events do not need to processed by -// the cache itself, only passed on to other watchers. -type noopExecutor struct{} - -func (noopExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*types.HeadlessAuthentication, error) { - return nil, nil -} - -func (noopExecutor) upsert(ctx context.Context, cache *Cache, resource *types.HeadlessAuthentication) error { - return nil -} - -func (noopExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return nil -} - -func (noopExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return nil -} - -func (noopExecutor) isSingleton() bool { return false } - -func (noopExecutor) getReader(_ *Cache, _ bool) noReader { - return noReader{} -} - -var _ executor[*types.HeadlessAuthentication, noReader] = noopExecutor{} - -type userLoginStateExecutor struct{} - -func (userLoginStateExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*userloginstate.UserLoginState, error) { - resources, err := cache.UserLoginStates.GetUserLoginStates(ctx) - return resources, trace.Wrap(err) -} - -func (userLoginStateExecutor) upsert(ctx context.Context, cache *Cache, resource *userloginstate.UserLoginState) error { - _, err := cache.userLoginStateCache.UpsertUserLoginState(ctx, resource) - return trace.Wrap(err) -} - -func (userLoginStateExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.userLoginStateCache.DeleteAllUserLoginStates(ctx) -} - -func (userLoginStateExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.userLoginStateCache.DeleteUserLoginState(ctx, resource.GetName()) -} - -func (userLoginStateExecutor) isSingleton() bool { return false } - -func (userLoginStateExecutor) getReader(cache *Cache, cacheOK bool) services.UserLoginStatesGetter { - if cacheOK { - return cache.userLoginStateCache - } - return cache.Config.UserLoginStates -} - -var _ executor[*userloginstate.UserLoginState, services.UserLoginStatesGetter] = userLoginStateExecutor{} - -type accessListExecutor struct{} - -func (accessListExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accesslist.AccessList, error) { - var resources []*accesslist.AccessList - var nextToken string - for { - var page []*accesslist.AccessList - var err error - page, nextToken, err = cache.AccessLists.ListAccessLists(ctx, 0 /* page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - - resources = append(resources, page...) - - if nextToken == "" { - break - } - } - return resources, nil -} - -func (accessListExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.AccessList) error { - _, err := cache.accessListCache.UnconditionalUpsertAccessList(ctx, resource) - return trace.Wrap(err) -} - -func (accessListExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.accessListCache.DeleteAllAccessLists(ctx) -} - -func (accessListExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.accessListCache.UnconditionalDeleteAccessList(ctx, resource.GetName()) -} - -func (accessListExecutor) isSingleton() bool { return false } - -func (accessListExecutor) getReader(cache *Cache, cacheOK bool) accessListsGetter { - if cacheOK { - return cache.accessListCache - } - return cache.Config.AccessLists -} - -type accessListsGetter interface { - GetAccessLists(ctx context.Context) ([]*accesslist.AccessList, error) - ListAccessLists(ctx context.Context, pageSize int, nextToken string) ([]*accesslist.AccessList, string, error) - GetAccessList(ctx context.Context, name string) (*accesslist.AccessList, error) -} - -type accessListMemberExecutor struct{} - -func (accessListMemberExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accesslist.AccessListMember, error) { - var resources []*accesslist.AccessListMember - var nextToken string - for { - var page []*accesslist.AccessListMember - var err error - page, nextToken, err = cache.AccessLists.ListAllAccessListMembers(ctx, 0 /* page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - - resources = append(resources, page...) - - if nextToken == "" { - break - } - } - return resources, nil -} - -func (accessListMemberExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.AccessListMember) error { - _, err := cache.accessListCache.UnconditionalUpsertAccessListMember(ctx, resource) - return trace.Wrap(err) -} - -func (accessListMemberExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.accessListCache.DeleteAllAccessListMembers(ctx) -} - -func (accessListMemberExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.accessListCache.UnconditionalDeleteAccessListMember(ctx, - resource.GetMetadata().Description, // Cache passes access ID via description field. - resource.GetName()) -} - -func (accessListMemberExecutor) isSingleton() bool { return false } - -func (accessListMemberExecutor) getReader(cache *Cache, cacheOK bool) accessListMembersGetter { - if cacheOK { - return cache.accessListCache - } - return cache.Config.AccessLists -} - -type accessListMembersGetter interface { - CountAccessListMembers(ctx context.Context, accessListName string) (uint32, uint32, error) - ListAccessListMembers(ctx context.Context, accessListName string, pageSize int, nextToken string) ([]*accesslist.AccessListMember, string, error) - GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error) - ListAllAccessListMembers(ctx context.Context, pageSize int, pageToken string) ([]*accesslist.AccessListMember, string, error) -} - -type accessListReviewExecutor struct{} - -func (accessListReviewExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accesslist.Review, error) { - var resources []*accesslist.Review - var nextToken string - for { - var page []*accesslist.Review - var err error - page, nextToken, err = cache.AccessLists.ListAllAccessListReviews(ctx, 0 /* page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - - resources = append(resources, page...) - - if nextToken == "" { - break - } - } - return resources, nil -} - -func (accessListReviewExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.Review) error { - if _, _, err := cache.accessListCache.CreateAccessListReview(ctx, resource); err != nil { - if !trace.IsAlreadyExists(err) { - return trace.Wrap(err) - } - - if err := cache.accessListCache.DeleteAccessListReview(ctx, resource.Spec.AccessList, resource.GetName()); err != nil { - return trace.Wrap(err) - } - - if _, _, err := cache.accessListCache.CreateAccessListReview(ctx, resource); err != nil { - return trace.Wrap(err) - } - } - return nil -} - -func (accessListReviewExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.accessListCache.DeleteAllAccessListReviews(ctx) -} - -func (accessListReviewExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.accessListCache.DeleteAccessListReview(ctx, - resource.GetMetadata().Description, // Cache passes access ID via description field. - resource.GetName()) -} - -func (accessListReviewExecutor) isSingleton() bool { return false } - -func (accessListReviewExecutor) getReader(cache *Cache, cacheOK bool) accessListReviewsGetter { - if cacheOK { - return cache.accessListCache - } - return cache.Config.AccessLists -} - -type accessListReviewsGetter interface { - ListAccessListReviews(ctx context.Context, accessList string, pageSize int, pageToken string) (reviews []*accesslist.Review, nextToken string, err error) -} - -type notificationGetter interface { - ListUserNotifications(ctx context.Context, pageSize int, startKey string) ([]*notificationsv1.Notification, string, error) - ListGlobalNotifications(ctx context.Context, pageSize int, startKey string) ([]*notificationsv1.GlobalNotification, string, error) -} - -type userNotificationExecutor struct{} - -func (userNotificationExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*notificationsv1.Notification, error) { - var notifications []*notificationsv1.Notification - var startKey string - for { - notifs, nextKey, err := cache.notificationsCache.ListUserNotifications(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - notifications = append(notifications, notifs...) - - if nextKey == "" { - break - } - startKey = nextKey - } - - return notifications, nil -} - -func (userNotificationExecutor) upsert(ctx context.Context, cache *Cache, notification *notificationsv1.Notification) error { - _, err := cache.notificationsCache.UpsertUserNotification(ctx, notification) - if err != nil { - return trace.Wrap(err) - } - - return nil -} - -func (userNotificationExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.notificationsCache.DeleteAllUserNotifications(ctx) -} - -func (userNotificationExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - r, ok := resource.(types.Resource153Unwrapper) - if !ok { - return trace.BadParameter("unknown resource type, expected types.Resource153Unwrapper, got %T", resource) - } - - notification, ok := r.Unwrap().(*notificationsv1.Notification) - if !ok { - return trace.BadParameter("unknown Notification type, expected *notificationsv1.Notification, got %T", resource) - } - - username := notification.GetSpec().GetUsername() - notificationId := notification.GetMetadata().GetName() - - err := cache.notificationsCache.DeleteUserNotification(ctx, username, notificationId) - return trace.Wrap(err) -} - -func (userNotificationExecutor) isSingleton() bool { return false } - -func (userNotificationExecutor) getReader(cache *Cache, cacheOK bool) notificationGetter { - if cacheOK { - return cache.notificationsCache - } - return cache.Config.Notifications -} - -var _ executor[*notificationsv1.Notification, notificationGetter] = userNotificationExecutor{} - -type globalNotificationExecutor struct{} - -func (globalNotificationExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*notificationsv1.GlobalNotification, error) { - var notifications []*notificationsv1.GlobalNotification - var startKey string - for { - notifs, nextKey, err := cache.notificationsCache.ListGlobalNotifications(ctx, 0, startKey) - if err != nil { - return nil, trace.Wrap(err) - } - - notifications = append(notifications, notifs...) - - if nextKey == "" { - break - } - startKey = nextKey - } - - return notifications, nil -} - -func (globalNotificationExecutor) upsert(ctx context.Context, cache *Cache, notification *notificationsv1.GlobalNotification) error { - if _, err := cache.notificationsCache.UpsertGlobalNotification(ctx, notification); err != nil { - return trace.Wrap(err) - } - - return nil -} - -func (globalNotificationExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.notificationsCache.DeleteAllGlobalNotifications(ctx) -} - -func (globalNotificationExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - - r, ok := resource.(types.Resource153Unwrapper) - if !ok { - return trace.BadParameter("unknown resource type, expected types.Resource153Unwrapper, got %T", resource) - } - - globalNotification, ok := r.Unwrap().(*notificationsv1.GlobalNotification) - if !ok { - return trace.BadParameter("unknown Notification type, expected *notificationsv1.GlobalNotification, got %T", resource) - } - - notificationId := globalNotification.GetMetadata().GetName() - - err := cache.notificationsCache.DeleteGlobalNotification(ctx, notificationId) - return trace.Wrap(err) -} - -func (globalNotificationExecutor) isSingleton() bool { return false } - -func (globalNotificationExecutor) getReader(cache *Cache, cacheOK bool) notificationGetter { - if cacheOK { - return cache.notificationsCache - } - return cache.Config.Notifications -} - -var _ executor[*notificationsv1.GlobalNotification, notificationGetter] = globalNotificationExecutor{} - -type accessMonitoringRulesExecutor struct{} - -func (accessMonitoringRulesExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accessmonitoringrulesv1.AccessMonitoringRule, error) { - var resources []*accessmonitoringrulesv1.AccessMonitoringRule - var nextToken string - for { - var page []*accessmonitoringrulesv1.AccessMonitoringRule - var err error - page, nextToken, err = cache.AccessMonitoringRules.ListAccessMonitoringRules(ctx, 0 /* page size */, nextToken) - if err != nil { - return nil, trace.Wrap(err) - } - resources = append(resources, page...) - - if nextToken == "" { - break - } - } - return resources, nil -} - -func (accessMonitoringRulesExecutor) upsert(ctx context.Context, cache *Cache, resource *accessmonitoringrulesv1.AccessMonitoringRule) error { - _, err := cache.accessMontoringRuleCache.UpsertAccessMonitoringRule(ctx, resource) - return trace.Wrap(err) -} - -func (accessMonitoringRulesExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return cache.accessMontoringRuleCache.DeleteAllAccessMonitoringRules(ctx) -} - -func (accessMonitoringRulesExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return cache.accessMontoringRuleCache.DeleteAccessMonitoringRule(ctx, resource.GetName()) -} - -func (accessMonitoringRulesExecutor) isSingleton() bool { return false } - -func (accessMonitoringRulesExecutor) getReader(cache *Cache, cacheOK bool) accessMonitoringRuleGetter { - if cacheOK { - return cache.accessMontoringRuleCache - } - return cache.Config.AccessMonitoringRules -} - -type accessMonitoringRuleGetter interface { - GetAccessMonitoringRule(ctx context.Context, name string) (*accessmonitoringrulesv1.AccessMonitoringRule, error) - ListAccessMonitoringRules(ctx context.Context, limit int, startKey string) ([]*accessmonitoringrulesv1.AccessMonitoringRule, string, error) - ListAccessMonitoringRulesWithFilter(ctx context.Context, pageSize int, nextToken string, subjects []string, notificationName string) ([]*accessmonitoringrulesv1.AccessMonitoringRule, string, error) -} - -type accessGraphSettingsExecutor struct{} - -func (accessGraphSettingsExecutor) getAll(ctx context.Context, cache *Cache, _ bool) ([]*clusterconfigpb.AccessGraphSettings, error) { - set, err := cache.ClusterConfig.GetAccessGraphSettings(ctx) - if err != nil { - return nil, trace.Wrap(err) - } - - return []*clusterconfigpb.AccessGraphSettings{set}, nil -} - -func (accessGraphSettingsExecutor) upsert(ctx context.Context, cache *Cache, resource *clusterconfigpb.AccessGraphSettings) error { - _, err := cache.clusterConfigCache.UpsertAccessGraphSettings(ctx, resource) - return trace.Wrap(err) -} - -func (accessGraphSettingsExecutor) deleteAll(ctx context.Context, cache *Cache) error { - return trace.Wrap(cache.clusterConfigCache.DeleteAccessGraphSettings(ctx)) -} - -func (accessGraphSettingsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { - return trace.Wrap(cache.clusterConfigCache.DeleteAccessGraphSettings(ctx)) -} - -func (accessGraphSettingsExecutor) isSingleton() bool { return false } - -func (accessGraphSettingsExecutor) getReader(cache *Cache, cacheOK bool) accessGraphSettingsGetter { - if cacheOK { - return cache.clusterConfigCache - } - return cache.Config.ClusterConfig -} - -type accessGraphSettingsGetter interface { - GetAccessGraphSettings(context.Context) (*clusterconfigpb.AccessGraphSettings, error) -} - -var _ executor[*clusterconfigpb.AccessGraphSettings, accessGraphSettingsGetter] = accessGraphSettingsExecutor{} diff --git a/lib/cache/genericcollection.go b/lib/cache/generic_legacy_collection.go similarity index 97% rename from lib/cache/genericcollection.go rename to lib/cache/generic_legacy_collection.go index ac0260da844d9..031bf6506e741 100644 --- a/lib/cache/genericcollection.go +++ b/lib/cache/generic_legacy_collection.go @@ -117,7 +117,7 @@ func (g *genericCollection[T, R, _]) watchKind() types.WatchKind { return g.watch } -var _ collection = (*genericCollection[types.Resource, any, executor[types.Resource, any]])(nil) +var _ legacyCollection = (*genericCollection[types.Resource, any, executor[types.Resource, any]])(nil) // genericCollection obtains the reader object from the executor based on the provided health status of the cache. // Note that cacheOK set to true means that cache is overall healthy and the collection was confirmed as supported. diff --git a/lib/cache/generic_operations.go b/lib/cache/generic_operations.go new file mode 100644 index 0000000000000..d514dbdf9f929 --- /dev/null +++ b/lib/cache/generic_operations.go @@ -0,0 +1,125 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cache + +import ( + "context" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/defaults" +) + +// genericGetter is a helper to retrieve a single item from a cache collection. +type genericGetter[T any, I comparable] struct { + // cache to perform the primary read from. + cache *Cache + // collection that contains the item. + collection *collection[T, I] + // index of the collection to read with. + index I + // upstreamGet is used to retrieve the item if the + // cache is not healthy. + upstreamGet func(context.Context, string) (T, error) +} + +// get retrieves a single item by an identifier from +// a cache collection. If the cache is not healthy, then the item is retrieved +// from the upstream backend. The item returned is cloned and ownership +// is retained by the caller. +func (g genericGetter[T, I]) get(ctx context.Context, identifier string) (T, error) { + var t T + rg, err := acquireReadGuard(g.cache, g.collection) + if err != nil { + return t, trace.Wrap(err) + } + defer rg.Release() + + if !rg.ReadCache() { + out, err := g.upstreamGet(ctx, identifier) + return out, trace.Wrap(err) + } + + out, err := rg.store.get(g.index, identifier) + if err != nil { + return t, trace.Wrap(err) + } + + return g.collection.store.clone(out), nil +} + +// genericLister is a helper to retrieve a page of items from a cache collection. +type genericLister[T any, I comparable] struct { + // cache to perform the primary read from. + cache *Cache + // collection that contains the item. + collection *collection[T, I] + // index of the collection to read with. + index I + // defaultPageSize optionally defines a page size to use if + // one is not specified by the caller. If not set then + // [defaults.DefaultChunkSize] is used. + defaultPageSize int + // upstreamList is used to retrieve the items if the + // cache is not healthy. + upstreamList func(context.Context, int, string) ([]T, string, error) + // nextToken is used to derive the next token returned from + // the item at which the next page should start from. + nextToken func(T) string + // filter is an optional function used to exclude items from + // cache reads. + filter func(T) bool +} + +// list retrieves a page of items from the configured cache collection. +// If the cache is not healthy, then the items are retrieved from the upstream backend. +// The items returned are cloned and ownership is retained by the caller. +func (l genericLister[T, I]) list(ctx context.Context, pageSize int, startToken string) ([]T, string, error) { + rg, err := acquireReadGuard(l.cache, l.collection) + if err != nil { + return nil, "", trace.Wrap(err) + } + defer rg.Release() + + if !rg.ReadCache() { + out, next, err := l.upstreamList(ctx, pageSize, startToken) + return out, next, trace.Wrap(err) + } + + defaultPageSize := defaults.DefaultChunkSize + if l.defaultPageSize > 0 { + defaultPageSize = l.defaultPageSize + } + + if pageSize <= 0 { + pageSize = defaultPageSize + } + + var out []T + for sf := range rg.store.resources(l.index, startToken, "") { + if len(out) == pageSize { + return out, l.nextToken(sf), nil + } + + if l.filter != nil && !l.filter(sf) { + continue + } + out = append(out, l.collection.store.clone(sf)) + } + + return out, "", nil +} diff --git a/lib/cache/generic_operations_test.go b/lib/cache/generic_operations_test.go new file mode 100644 index 0000000000000..5fe08828efc67 --- /dev/null +++ b/lib/cache/generic_operations_test.go @@ -0,0 +1,147 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cache + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/types" +) + +func TestGetter(t *testing.T) { + t.Parallel() + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + store := newStore( + func(role types.Role) types.Role { + return role + }, + map[string]func(types.Role) string{ + "default": types.Role.GetName, + }) + + require.NoError(t, store.put(&types.RoleV6{Metadata: types.Metadata{Name: "a"}})) + + var upstreamRead bool + g := genericGetter[types.Role, string]{ + cache: p.cache, + index: "default", + upstreamGet: func(ctx context.Context, s string) (types.Role, error) { + upstreamRead = true + return &types.RoleV6{Metadata: types.Metadata{Name: "upstream-" + s}}, nil + }, + collection: &collection[types.Role, string]{ + store: store, + fetcher: func(ctx context.Context, loadSecrets bool) ([]types.Role, error) { + return []types.Role{ + &types.RoleV6{Metadata: types.Metadata{Name: "a"}}, + &types.RoleV6{Metadata: types.Metadata{Name: "b"}}, + &types.RoleV6{Metadata: types.Metadata{Name: "c"}}, + }, nil + }, + headerTransform: func(hdr *types.ResourceHeader) types.Role { + return &types.RoleV6{ + Kind: hdr.Kind, + Version: hdr.Version, + Metadata: types.Metadata{ + Name: hdr.Metadata.Name, + }, + } + }, + watch: types.WatchKind{Kind: types.KindRole}, + }, + } + + out, err := g.get(context.Background(), "a") + require.NoError(t, err) + assert.Equal(t, "a", out.GetName()) + assert.False(t, upstreamRead) + + p.cache.ok = false + + out, err = g.get(context.Background(), "a") + require.NoError(t, err) + assert.Equal(t, "upstream-a", out.GetName()) + assert.True(t, upstreamRead) +} + +func TestLister(t *testing.T) { + t.Parallel() + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + store := newStore( + func(role types.Role) types.Role { + return role + }, + map[string]func(types.Role) string{ + "default": types.Role.GetName, + }) + + require.NoError(t, store.put(&types.RoleV6{Metadata: types.Metadata{Name: "a"}})) + + var upstreamRead bool + g := genericLister[types.Role, string]{ + cache: p.cache, + index: "default", + upstreamList: func(ctx context.Context, limit int, start string) ([]types.Role, string, error) { + upstreamRead = true + return []types.Role{&types.RoleV6{Metadata: types.Metadata{Name: "upstream-role"}}}, "", nil + }, + collection: &collection[types.Role, string]{ + store: store, + fetcher: func(ctx context.Context, loadSecrets bool) ([]types.Role, error) { + return []types.Role{ + &types.RoleV6{Metadata: types.Metadata{Name: "a"}}, + &types.RoleV6{Metadata: types.Metadata{Name: "b"}}, + &types.RoleV6{Metadata: types.Metadata{Name: "c"}}, + }, nil + }, + headerTransform: func(hdr *types.ResourceHeader) types.Role { + return &types.RoleV6{ + Kind: hdr.Kind, + Version: hdr.Version, + Metadata: types.Metadata{ + Name: hdr.Metadata.Name, + }, + } + }, + watch: types.WatchKind{Kind: types.KindRole}, + }, + } + + out, next, err := g.list(context.Background(), 10, "") + require.NoError(t, err) + require.Len(t, out, 1) + assert.Equal(t, "a", out[0].GetName()) + assert.Empty(t, next) + assert.False(t, upstreamRead) + + p.cache.ok = false + + out, next, err = g.list(context.Background(), 10, "") + require.NoError(t, err) + require.Len(t, out, 1) + assert.Equal(t, "upstream-role", out[0].GetName()) + assert.Empty(t, next) + assert.True(t, upstreamRead) +} diff --git a/lib/cache/git_server.go b/lib/cache/git_server.go index b585b0b169817..75192da298334 100644 --- a/lib/cache/git_server.go +++ b/lib/cache/git_server.go @@ -42,7 +42,7 @@ func (c *Cache) GetGitServer(ctx context.Context, name string) (types.Server, er ctx, span := c.Tracer.Start(ctx, "cache/GetGitServer") defer span.End() - rg, err := readCollectionCache(c, c.collections.gitServers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.gitServers) if err != nil { return nil, trace.Wrap(err) } @@ -54,7 +54,7 @@ func (c *Cache) ListGitServers(ctx context.Context, pageSize int, pageToken stri ctx, span := c.Tracer.Start(ctx, "cache/ListGitServers") defer span.End() - rg, err := readCollectionCache(c, c.collections.gitServers) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.gitServers) if err != nil { return nil, "", trace.Wrap(err) } diff --git a/lib/cache/legacy_collections.go b/lib/cache/legacy_collections.go new file mode 100644 index 0000000000000..2749347778d0c --- /dev/null +++ b/lib/cache/legacy_collections.go @@ -0,0 +1,3512 @@ +/* + * Teleport + * Copyright (C) 2023 Gravitational, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +//nolint:unused // Because the executors generate a large amount of false positives. +package cache + +import ( + "context" + "fmt" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/client" + "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" + identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1" + kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1" + provisioningv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/provisioning/v1" + userprovisioningpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/userprovisioning/v2" + userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" + usertasksv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/usertasks/v1" + workloadidentityv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/workloadidentity/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/accesslist" + "github.com/gravitational/teleport/api/types/discoveryconfig" + "github.com/gravitational/teleport/api/types/secreports" + "github.com/gravitational/teleport/api/types/userloginstate" + "github.com/gravitational/teleport/lib/defaults" + "github.com/gravitational/teleport/lib/services" +) + +// legacyCollection is responsible for managing collection +// of resources updates +type legacyCollection interface { + // fetch fetches resources and returns a function which will apply said resources to the cache. + // fetch *must* not mutate cache state outside of the apply function. + // The provided cacheOK flag indicates whether this collection will be included in the cache generation that is + // being prepared. If cacheOK is false, fetch shouldn't fetch any resources, but the apply function that it + // returns must still delete resources from the backend. + fetch(ctx context.Context, cacheOK bool) (apply func(ctx context.Context) error, err error) + // processEvent processes event + processEvent(ctx context.Context, e types.Event) error + // watchKind returns a watch + // required for this collection + watchKind() types.WatchKind +} + +// executor[T, R] is a specific way to run the collector operations that we need +// for the genericCollector for a generic resource type T and its reader type R. +type executor[T any, R any] interface { + // getAll returns all of the target resources from the auth server. + // For singleton objects, this should be a size-1 slice. + getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]T, error) + + // upsert will create or update a target resource in the cache. + upsert(ctx context.Context, cache *Cache, value T) error + + // deleteAll will delete all target resources of the type in the cache. + deleteAll(ctx context.Context, cache *Cache) error + + // delete will delete a single target resource from the cache. For + // singletons, this is usually an alias to deleteAll. + delete(ctx context.Context, cache *Cache, resource types.Resource) error + + // isSingleton will return true if the target resource is a singleton. + isSingleton() bool + + // getReader returns the appropriate reader type R based on the health status of the cache. + // Reader type R provides getter methods related to the collection, e.g. GetNodes(), GetRoles(). + // Note that cacheOK set to true means that cache is overall healthy and the collection was confirmed as supported. + getReader(c *Cache, cacheOK bool) R +} + +// noReader is returned by getReader for resources which aren't directly used by the cache, and therefore have no associated reader. +type noReader struct{} + +type crownjewelsGetter interface { + ListCrownJewels(ctx context.Context, pageSize int64, nextToken string) ([]*crownjewelv1.CrownJewel, string, error) + GetCrownJewel(ctx context.Context, name string) (*crownjewelv1.CrownJewel, error) +} + +type userTasksGetter interface { + ListUserTasks(ctx context.Context, pageSize int64, nextToken string, filters *usertasksv1.ListUserTasksFilters) ([]*usertasksv1.UserTask, string, error) + GetUserTask(ctx context.Context, name string) (*usertasksv1.UserTask, error) +} + +// legacyCollections is a registry of resource collections used by Cache. +type legacyCollections struct { + // byKind is a map of registered collections by resource Kind/SubKind + byKind map[resourceKind]legacyCollection + + auditQueries collectionReader[services.SecurityAuditQueryGetter] + secReports collectionReader[services.SecurityReportGetter] + secReportsStates collectionReader[services.SecurityReportStateGetter] + accessLists collectionReader[accessListsGetter] + accessListMembers collectionReader[accessListMembersGetter] + accessListReviews collectionReader[accessListReviewsGetter] + apps collectionReader[services.AppGetter] + nodes collectionReader[nodeGetter] + tunnelConnections collectionReader[tunnelConnectionGetter] + appSessions collectionReader[appSessionGetter] + appServers collectionReader[appServerGetter] + authPreferences collectionReader[authPreferenceGetter] + authServers collectionReader[authServerGetter] + certAuthorities collectionReader[services.AuthorityGetter] + clusterAuditConfigs collectionReader[clusterAuditConfigGetter] + clusterNames collectionReader[clusterNameGetter] + clusterNetworkingConfigs collectionReader[clusterNetworkingConfigGetter] + databases collectionReader[services.DatabaseGetter] + databaseObjects collectionReader[services.DatabaseObjectsGetter] + databaseServers collectionReader[databaseServerGetter] + discoveryConfigs collectionReader[services.DiscoveryConfigsGetter] + installers collectionReader[installerGetter] + integrations collectionReader[services.IntegrationsGetter] + userTasks collectionReader[userTasksGetter] + crownJewels collectionReader[crownjewelsGetter] + kubeClusters collectionReader[kubernetesClusterGetter] + kubeWaitingContainers collectionReader[kubernetesWaitingContainerGetter] + staticHostUsers collectionReader[staticHostUserGetter] + kubeServers collectionReader[kubeServerGetter] + locks collectionReader[services.LockGetter] + namespaces collectionReader[namespaceGetter] + networkRestrictions collectionReader[networkRestrictionGetter] + oktaAssignments collectionReader[oktaAssignmentGetter] + oktaImportRules collectionReader[oktaImportRuleGetter] + proxies collectionReader[services.ProxyGetter] + remoteClusters collectionReader[remoteClusterGetter] + reverseTunnels collectionReader[reverseTunnelGetter] + roles collectionReader[roleGetter] + samlIdPServiceProviders collectionReader[samlIdPServiceProviderGetter] + samlIdPSessions collectionReader[samlIdPSessionGetter] + sessionRecordingConfigs collectionReader[sessionRecordingConfigGetter] + snowflakeSessions collectionReader[snowflakeSessionGetter] + staticTokens collectionReader[staticTokensGetter] + tokens collectionReader[tokenGetter] + uiConfigs collectionReader[uiConfigGetter] + users collectionReader[userGetter] + userGroups collectionReader[userGroupGetter] + userLoginStates collectionReader[services.UserLoginStatesGetter] + webSessions collectionReader[webSessionGetter] + webTokens collectionReader[webTokenGetter] + windowsDesktops collectionReader[windowsDesktopsGetter] + dynamicWindowsDesktops collectionReader[dynamicWindowsDesktopsGetter] + windowsDesktopServices collectionReader[windowsDesktopServiceGetter] + userNotifications collectionReader[notificationGetter] + accessGraphSettings collectionReader[accessGraphSettingsGetter] + globalNotifications collectionReader[notificationGetter] + accessMonitoringRules collectionReader[accessMonitoringRuleGetter] + spiffeFederations collectionReader[SPIFFEFederationReader] + autoUpdateConfigs collectionReader[autoUpdateConfigGetter] + autoUpdateVersions collectionReader[autoUpdateVersionGetter] + autoUpdateAgentRollouts collectionReader[autoUpdateAgentRolloutGetter] + provisioningStates collectionReader[provisioningStateGetter] + identityCenterAccounts collectionReader[identityCenterAccountGetter] + identityCenterPrincipalAssignments collectionReader[identityCenterPrincipalAssignmentGetter] + identityCenterAccountAssignments collectionReader[identityCenterAccountAssignmentGetter] + workloadIdentity collectionReader[WorkloadIdentityReader] + pluginStaticCredentials collectionReader[pluginStaticCredentialsGetter] + gitServers collectionReader[services.GitServerGetter] +} + +// setupLegacyCollections returns a registry of legacyCollections. +func setupLegacyCollections(c *Cache, watches []types.WatchKind) (*legacyCollections, error) { + collections := &legacyCollections{ + byKind: make(map[resourceKind]legacyCollection, len(watches)), + } + for _, watch := range watches { + resourceKind := resourceKindFromWatchKind(watch) + switch watch.Kind { + case types.KindCertAuthority: + if c.Trust == nil { + return nil, trace.BadParameter("missing parameter Trust") + } + var filter types.CertAuthorityFilter + filter.FromMap(watch.Filter) + + collections.certAuthorities = &genericCollection[types.CertAuthority, services.AuthorityGetter, certAuthorityExecutor]{ + cache: c, + exec: certAuthorityExecutor{filter: filter}, + watch: watch, + } + collections.byKind[resourceKind] = collections.certAuthorities + case types.KindStaticTokens: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.staticTokens = &genericCollection[types.StaticTokens, staticTokensGetter, staticTokensExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.staticTokens + case types.KindToken: + if c.Provisioner == nil { + return nil, trace.BadParameter("missing parameter Provisioner") + } + collections.tokens = &genericCollection[types.ProvisionToken, tokenGetter, provisionTokenExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.tokens + case types.KindClusterName: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.clusterNames = &genericCollection[types.ClusterName, clusterNameGetter, clusterNameExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.clusterNames + case types.KindClusterAuditConfig: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.clusterAuditConfigs = &genericCollection[types.ClusterAuditConfig, clusterAuditConfigGetter, clusterAuditConfigExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.clusterAuditConfigs + case types.KindClusterNetworkingConfig: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.clusterNetworkingConfigs = &genericCollection[types.ClusterNetworkingConfig, clusterNetworkingConfigGetter, clusterNetworkingConfigExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.clusterNetworkingConfigs + case types.KindClusterAuthPreference: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.authPreferences = &genericCollection[types.AuthPreference, authPreferenceGetter, authPreferenceExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.authPreferences + case types.KindSessionRecordingConfig: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.sessionRecordingConfigs = &genericCollection[types.SessionRecordingConfig, sessionRecordingConfigGetter, sessionRecordingConfigExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.sessionRecordingConfigs + case types.KindInstaller: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.installers = &genericCollection[types.Installer, installerGetter, installerConfigExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.installers + case types.KindUIConfig: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.uiConfigs = &genericCollection[types.UIConfig, uiConfigGetter, uiConfigExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.uiConfigs + case types.KindUser: + if c.Users == nil { + return nil, trace.BadParameter("missing parameter Users") + } + collections.users = &genericCollection[types.User, userGetter, userExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.users + case types.KindRole: + if c.Access == nil { + return nil, trace.BadParameter("missing parameter Access") + } + collections.roles = &genericCollection[types.Role, roleGetter, roleExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.roles + case types.KindNamespace: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.namespaces = &genericCollection[*types.Namespace, namespaceGetter, namespaceExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.namespaces + case types.KindNode: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.nodes = &genericCollection[types.Server, nodeGetter, nodeExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.nodes + case types.KindProxy: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.proxies = &genericCollection[types.Server, services.ProxyGetter, proxyExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.proxies + case types.KindAuthServer: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.authServers = &genericCollection[types.Server, authServerGetter, authServerExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.authServers + case types.KindReverseTunnel: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.reverseTunnels = &genericCollection[types.ReverseTunnel, reverseTunnelGetter, reverseTunnelExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.reverseTunnels + case types.KindTunnelConnection: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.tunnelConnections = &genericCollection[types.TunnelConnection, tunnelConnectionGetter, tunnelConnectionExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.tunnelConnections + case types.KindRemoteCluster: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.remoteClusters = &genericCollection[types.RemoteCluster, remoteClusterGetter, remoteClusterExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.remoteClusters + case types.KindAppServer: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.appServers = &genericCollection[types.AppServer, appServerGetter, appServerExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.appServers + case types.KindWebSession: + switch watch.SubKind { + case types.KindAppSession: + if c.AppSession == nil { + return nil, trace.BadParameter("missing parameter AppSession") + } + collections.appSessions = &genericCollection[types.WebSession, appSessionGetter, appSessionExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.appSessions + case types.KindSnowflakeSession: + if c.SnowflakeSession == nil { + return nil, trace.BadParameter("missing parameter SnowflakeSession") + } + collections.snowflakeSessions = &genericCollection[types.WebSession, snowflakeSessionGetter, snowflakeSessionExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.snowflakeSessions + case types.KindSAMLIdPSession: + if c.SAMLIdPSession == nil { + return nil, trace.BadParameter("missing parameter SAMLIdPSession") + } + collections.samlIdPSessions = &genericCollection[types.WebSession, samlIdPSessionGetter, samlIdPSessionExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.samlIdPSessions + case types.KindWebSession: + if c.WebSession == nil { + return nil, trace.BadParameter("missing parameter WebSession") + } + collections.webSessions = &genericCollection[types.WebSession, webSessionGetter, webSessionExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.webSessions + } + case types.KindWebToken: + if c.WebToken == nil { + return nil, trace.BadParameter("missing parameter WebToken") + } + collections.webTokens = &genericCollection[types.WebToken, webTokenGetter, webTokenExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.webTokens + case types.KindKubeServer: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.kubeServers = &genericCollection[types.KubeServer, kubeServerGetter, kubeServerExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.kubeServers + case types.KindDatabaseServer: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.databaseServers = &genericCollection[types.DatabaseServer, databaseServerGetter, databaseServerExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.databaseServers + case types.KindDatabaseService: + if c.DatabaseServices == nil { + return nil, trace.BadParameter("missing parameter DatabaseServices") + } + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.byKind[resourceKind] = &genericCollection[types.DatabaseService, noReader, databaseServiceExecutor]{cache: c, watch: watch} + case types.KindApp: + if c.Apps == nil { + return nil, trace.BadParameter("missing parameter Apps") + } + collections.apps = &genericCollection[types.Application, services.AppGetter, appExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.apps + case types.KindDatabase: + if c.Databases == nil { + return nil, trace.BadParameter("missing parameter Databases") + } + collections.databases = &genericCollection[types.Database, services.DatabaseGetter, databaseExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.databases + case types.KindDatabaseObject: + if c.DatabaseObjects == nil { + return nil, trace.BadParameter("missing parameter DatabaseObject") + } + collections.databaseObjects = &genericCollection[*dbobjectv1.DatabaseObject, services.DatabaseObjectsGetter, databaseObjectExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.databaseObjects + case types.KindKubernetesCluster: + if c.Kubernetes == nil { + return nil, trace.BadParameter("missing parameter Kubernetes") + } + collections.kubeClusters = &genericCollection[types.KubeCluster, kubernetesClusterGetter, kubeClusterExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.kubeClusters + case types.KindCrownJewel: + if c.CrownJewels == nil { + return nil, trace.BadParameter("missing parameter crownjewels") + } + collections.crownJewels = &genericCollection[*crownjewelv1.CrownJewel, crownjewelsGetter, crownJewelsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.crownJewels + case types.KindNetworkRestrictions: + if c.Restrictions == nil { + return nil, trace.BadParameter("missing parameter Restrictions") + } + collections.networkRestrictions = &genericCollection[types.NetworkRestrictions, networkRestrictionGetter, networkRestrictionsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.networkRestrictions + case types.KindLock: + if c.Access == nil { + return nil, trace.BadParameter("missing parameter Access") + } + collections.locks = &genericCollection[types.Lock, services.LockGetter, lockExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.locks + case types.KindWindowsDesktopService: + if c.Presence == nil { + return nil, trace.BadParameter("missing parameter Presence") + } + collections.windowsDesktopServices = &genericCollection[types.WindowsDesktopService, windowsDesktopServiceGetter, windowsDesktopServicesExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.windowsDesktopServices + case types.KindWindowsDesktop: + if c.WindowsDesktops == nil { + return nil, trace.BadParameter("missing parameter WindowsDesktops") + } + collections.windowsDesktops = &genericCollection[types.WindowsDesktop, windowsDesktopsGetter, windowsDesktopsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.windowsDesktops + case types.KindDynamicWindowsDesktop: + if c.WindowsDesktops == nil { + return nil, trace.BadParameter("missing parameter DynamicWindowsDesktops") + } + collections.dynamicWindowsDesktops = &genericCollection[types.DynamicWindowsDesktop, dynamicWindowsDesktopsGetter, dynamicWindowsDesktopsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.dynamicWindowsDesktops + case types.KindSAMLIdPServiceProvider: + if c.SAMLIdPServiceProviders == nil { + return nil, trace.BadParameter("missing parameter SAMLIdPServiceProviders") + } + collections.samlIdPServiceProviders = &genericCollection[types.SAMLIdPServiceProvider, samlIdPServiceProviderGetter, samlIdPServiceProvidersExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.samlIdPServiceProviders + case types.KindUserGroup: + if c.UserGroups == nil { + return nil, trace.BadParameter("missing parameter UserGroups") + } + collections.userGroups = &genericCollection[types.UserGroup, userGroupGetter, userGroupsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.userGroups + case types.KindOktaImportRule: + if c.Okta == nil { + return nil, trace.BadParameter("missing parameter Okta") + } + collections.oktaImportRules = &genericCollection[types.OktaImportRule, oktaImportRuleGetter, oktaImportRulesExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.oktaImportRules + case types.KindOktaAssignment: + if c.Okta == nil { + return nil, trace.BadParameter("missing parameter Okta") + } + collections.oktaAssignments = &genericCollection[types.OktaAssignment, oktaAssignmentGetter, oktaAssignmentsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.oktaAssignments + case types.KindIntegration: + if c.Integrations == nil { + return nil, trace.BadParameter("missing parameter Integrations") + } + collections.integrations = &genericCollection[types.Integration, services.IntegrationsGetter, integrationsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.integrations + case types.KindUserTask: + if c.UserTasks == nil { + return nil, trace.BadParameter("missing parameter user tasks") + } + collections.userTasks = &genericCollection[*usertasksv1.UserTask, userTasksGetter, userTasksExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.userTasks + case types.KindDiscoveryConfig: + if c.DiscoveryConfigs == nil { + return nil, trace.BadParameter("missing parameter DiscoveryConfigs") + } + collections.discoveryConfigs = &genericCollection[*discoveryconfig.DiscoveryConfig, services.DiscoveryConfigsGetter, discoveryConfigExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.discoveryConfigs + case types.KindAuditQuery: + if c.SecReports == nil { + return nil, trace.BadParameter("missing parameter SecReports") + } + collections.auditQueries = &genericCollection[*secreports.AuditQuery, services.SecurityAuditQueryGetter, auditQueryExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.auditQueries + case types.KindSecurityReport: + if c.SecReports == nil { + return nil, trace.BadParameter("missing parameter KindSecurityReport") + } + collections.secReports = &genericCollection[*secreports.Report, services.SecurityReportGetter, secReportExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.secReports + case types.KindSecurityReportState: + if c.SecReports == nil { + return nil, trace.BadParameter("missing parameter KindSecurityReport") + } + collections.secReportsStates = &genericCollection[*secreports.ReportState, services.SecurityReportStateGetter, secReportStateExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.secReportsStates + case types.KindUserLoginState: + if c.UserLoginStates == nil { + return nil, trace.BadParameter("missing parameter UserLoginStates") + } + collections.userLoginStates = &genericCollection[*userloginstate.UserLoginState, services.UserLoginStatesGetter, userLoginStateExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.userLoginStates + case types.KindAccessList: + if c.AccessLists == nil { + return nil, trace.BadParameter("missing parameter AccessLists") + } + collections.accessLists = &genericCollection[*accesslist.AccessList, accessListsGetter, accessListExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.accessLists + case types.KindAccessListMember: + if c.AccessLists == nil { + return nil, trace.BadParameter("missing parameter AccessLists") + } + collections.accessListMembers = &genericCollection[*accesslist.AccessListMember, accessListMembersGetter, accessListMemberExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.accessListMembers + case types.KindAccessListReview: + if c.AccessLists == nil { + return nil, trace.BadParameter("missing parameter AccessLists") + } + collections.accessListReviews = &genericCollection[*accesslist.Review, accessListReviewsGetter, accessListReviewExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.accessListReviews + case types.KindKubeWaitingContainer: + if c.KubeWaitingContainers == nil { + return nil, trace.BadParameter("missing parameter KubeWaitingContainers") + } + collections.kubeWaitingContainers = &genericCollection[*kubewaitingcontainerpb.KubernetesWaitingContainer, kubernetesWaitingContainerGetter, kubeWaitingContainerExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.kubeWaitingContainers + case types.KindStaticHostUser: + if c.StaticHostUsers == nil { + return nil, trace.BadParameter("missing parameter StaticHostUsers") + } + collections.staticHostUsers = &genericCollection[*userprovisioningpb.StaticHostUser, staticHostUserGetter, staticHostUserExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.staticHostUsers + case types.KindNotification: + if c.Notifications == nil { + return nil, trace.BadParameter("missing parameter Notifications") + } + collections.userNotifications = &genericCollection[*notificationsv1.Notification, notificationGetter, userNotificationExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.userNotifications + case types.KindGlobalNotification: + if c.Notifications == nil { + return nil, trace.BadParameter("missing parameter Notifications") + } + collections.globalNotifications = &genericCollection[*notificationsv1.GlobalNotification, notificationGetter, globalNotificationExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.globalNotifications + case types.KindAccessMonitoringRule: + if c.AccessMonitoringRules == nil { + return nil, trace.BadParameter("missing parameter AccessMonitoringRule") + } + collections.accessMonitoringRules = &genericCollection[*accessmonitoringrulesv1.AccessMonitoringRule, accessMonitoringRuleGetter, accessMonitoringRulesExecutor]{cache: c, watch: watch} + collections.byKind[resourceKind] = collections.accessMonitoringRules + case types.KindAccessGraphSettings: + if c.ClusterConfig == nil { + return nil, trace.BadParameter("missing parameter ClusterConfig") + } + collections.accessGraphSettings = &genericCollection[*clusterconfigpb.AccessGraphSettings, accessGraphSettingsGetter, accessGraphSettingsExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.accessGraphSettings + case types.KindSPIFFEFederation: + if c.Config.SPIFFEFederations == nil { + return nil, trace.BadParameter("missing parameter SPIFFEFederations") + } + collections.spiffeFederations = &genericCollection[*machineidv1.SPIFFEFederation, SPIFFEFederationReader, spiffeFederationExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.spiffeFederations + case types.KindWorkloadIdentity: + if c.Config.WorkloadIdentity == nil { + return nil, trace.BadParameter("missing parameter WorkloadIdentity") + } + collections.workloadIdentity = &genericCollection[*workloadidentityv1pb.WorkloadIdentity, WorkloadIdentityReader, workloadIdentityExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.workloadIdentity + 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 + case types.KindAutoUpdateAgentRollout: + if c.AutoUpdateService == nil { + return nil, trace.BadParameter("missing parameter AutoUpdateService") + } + collections.autoUpdateAgentRollouts = &genericCollection[*autoupdate.AutoUpdateAgentRollout, autoUpdateAgentRolloutGetter, autoUpdateAgentRolloutExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.autoUpdateAgentRollouts + + case types.KindProvisioningPrincipalState: + if c.ProvisioningStates == nil { + return nil, trace.BadParameter("missing parameter KindProvisioningState") + } + collections.provisioningStates = &genericCollection[*provisioningv1.PrincipalState, provisioningStateGetter, provisioningStateExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.provisioningStates + + case types.KindIdentityCenterAccount: + if c.IdentityCenter == nil { + return nil, trace.BadParameter("missing upstream IdentityCenter collection") + } + collections.identityCenterAccounts = &genericCollection[ + services.IdentityCenterAccount, + identityCenterAccountGetter, + identityCenterAccountExecutor, + ]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.identityCenterAccounts + + case types.KindIdentityCenterPrincipalAssignment: + if c.IdentityCenter == nil { + return nil, trace.BadParameter("missing parameter IdentityCenter") + } + collections.identityCenterPrincipalAssignments = &genericCollection[ + *identitycenterv1.PrincipalAssignment, + identityCenterPrincipalAssignmentGetter, + identityCenterPrincipalAssignmentExecutor, + ]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.identityCenterPrincipalAssignments + + case types.KindIdentityCenterAccountAssignment: + if c.IdentityCenter == nil { + return nil, trace.BadParameter("missing parameter IdentityCenter") + } + collections.identityCenterAccountAssignments = &genericCollection[ + services.IdentityCenterAccountAssignment, + identityCenterAccountAssignmentGetter, + identityCenterAccountAssignmentExecutor, + ]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.identityCenterAccountAssignments + + case types.KindPluginStaticCredentials: + if c.PluginStaticCredentials == nil { + return nil, trace.BadParameter("missing parameter PluginStaticCredentials") + } + collections.pluginStaticCredentials = &genericCollection[ + types.PluginStaticCredentials, + pluginStaticCredentialsGetter, + pluginStaticCredentialsExecutor, + ]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.pluginStaticCredentials + + case types.KindGitServer: + if c.GitServers == nil { + return nil, trace.BadParameter("missing parameter GitServers") + } + collections.gitServers = &genericCollection[ + types.Server, + services.GitServerGetter, + gitServerExecutor, + ]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.gitServers + } + } + return collections, nil +} + +func resourceKindFromWatchKind(wk types.WatchKind) resourceKind { + switch wk.Kind { + case types.KindWebSession: + // Web sessions use subkind to differentiate between + // the types of sessions + return resourceKind{ + kind: wk.Kind, + subkind: wk.SubKind, + } + } + return resourceKind{ + kind: wk.Kind, + } +} + +func resourceKindFromResource(res types.Resource) resourceKind { + switch res.GetKind() { + case types.KindWebSession: + // Web sessions use subkind to differentiate between + // the types of sessions + return resourceKind{ + kind: res.GetKind(), + subkind: res.GetSubKind(), + } + } + return resourceKind{ + kind: res.GetKind(), + } +} + +type resourceKind struct { + kind string + subkind string +} + +func (r resourceKind) String() string { + if r.subkind == "" { + return r.kind + } + return fmt.Sprintf("%s/%s", r.kind, r.subkind) +} + +type tunnelConnectionExecutor struct{} + +func (tunnelConnectionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.TunnelConnection, error) { + return cache.Trust.GetAllTunnelConnections() +} + +func (tunnelConnectionExecutor) upsert(ctx context.Context, cache *Cache, resource types.TunnelConnection) error { + return cache.trustCache.UpsertTunnelConnection(resource) +} + +func (tunnelConnectionExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.trustCache.DeleteAllTunnelConnections() +} + +func (tunnelConnectionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.trustCache.DeleteTunnelConnection(resource.GetSubKind(), resource.GetName()) +} + +func (tunnelConnectionExecutor) isSingleton() bool { return false } + +func (tunnelConnectionExecutor) getReader(cache *Cache, cacheOK bool) tunnelConnectionGetter { + if cacheOK { + return cache.trustCache + } + return cache.Config.Trust +} + +type tunnelConnectionGetter interface { + GetAllTunnelConnections(opts ...services.MarshalOption) (conns []types.TunnelConnection, err error) + GetTunnelConnections(clusterName string, opts ...services.MarshalOption) ([]types.TunnelConnection, error) +} + +var _ executor[types.TunnelConnection, tunnelConnectionGetter] = tunnelConnectionExecutor{} + +type remoteClusterExecutor struct{} + +func (remoteClusterExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.RemoteCluster, error) { + return cache.Trust.GetRemoteClusters(ctx) +} + +func (remoteClusterExecutor) upsert(ctx context.Context, cache *Cache, resource types.RemoteCluster) error { + err := cache.trustCache.DeleteRemoteCluster(ctx, resource.GetName()) + if err != nil { + if !trace.IsNotFound(err) { + cache.Logger.WithError(err).Warnf("Failed to delete remote cluster %v.", resource.GetName()) + return trace.Wrap(err) + } + } + _, err = cache.trustCache.CreateRemoteCluster(ctx, resource) + return trace.Wrap(err) +} + +func (remoteClusterExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.trustCache.DeleteAllRemoteClusters(ctx) +} + +func (remoteClusterExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.trustCache.DeleteRemoteCluster(ctx, resource.GetName()) +} + +func (remoteClusterExecutor) isSingleton() bool { return false } + +func (remoteClusterExecutor) getReader(cache *Cache, cacheOK bool) remoteClusterGetter { + if cacheOK { + return cache.trustCache + } + return cache.Config.Trust +} + +type remoteClusterGetter interface { + GetRemoteClusters(ctx context.Context) ([]types.RemoteCluster, error) + GetRemoteCluster(ctx context.Context, clusterName string) (types.RemoteCluster, error) + ListRemoteClusters(ctx context.Context, pageSize int, pageToken string) ([]types.RemoteCluster, string, error) +} + +var _ executor[types.RemoteCluster, remoteClusterGetter] = remoteClusterExecutor{} + +type proxyExecutor struct{} + +func (proxyExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Server, error) { + return cache.Presence.GetProxies() +} + +func (proxyExecutor) upsert(ctx context.Context, cache *Cache, resource types.Server) error { + return cache.presenceCache.UpsertProxy(ctx, resource) +} + +func (proxyExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllProxies() +} + +func (proxyExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteProxy(ctx, resource.GetName()) +} + +func (proxyExecutor) isSingleton() bool { return false } + +func (proxyExecutor) getReader(cache *Cache, cacheOK bool) services.ProxyGetter { + if cacheOK { + return cache.presenceCache + } + return cache.Config.Presence +} + +var _ executor[types.Server, services.ProxyGetter] = proxyExecutor{} + +type authServerExecutor struct{} + +func (authServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Server, error) { + return cache.Presence.GetAuthServers() +} + +func (authServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.Server) error { + return cache.presenceCache.UpsertAuthServer(ctx, resource) +} + +func (authServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllAuthServers() +} + +func (authServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteAuthServer(resource.GetName()) +} + +func (authServerExecutor) isSingleton() bool { return false } + +func (authServerExecutor) getReader(cache *Cache, cacheOK bool) authServerGetter { + if cacheOK { + return cache.presenceCache + } + return cache.Config.Presence +} + +type authServerGetter interface { + GetAuthServers() ([]types.Server, error) +} + +var _ executor[types.Server, authServerGetter] = authServerExecutor{} + +type nodeExecutor struct{} + +func (nodeExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Server, error) { + return cache.Presence.GetNodes(ctx, apidefaults.Namespace) +} + +func (nodeExecutor) upsert(ctx context.Context, cache *Cache, resource types.Server) error { + _, err := cache.presenceCache.UpsertNode(ctx, resource) + return trace.Wrap(err) +} + +func (nodeExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllNodes(ctx, apidefaults.Namespace) +} + +func (nodeExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteNode(ctx, resource.GetMetadata().Namespace, resource.GetName()) +} + +func (nodeExecutor) isSingleton() bool { return false } + +func (nodeExecutor) getReader(cache *Cache, cacheOK bool) nodeGetter { + if cacheOK { + return cache.presenceCache + } + return cache.Config.Presence +} + +type nodeGetter interface { + GetNodes(ctx context.Context, namespace string) ([]types.Server, error) + GetNode(ctx context.Context, namespace, name string) (types.Server, error) +} + +var _ executor[types.Server, nodeGetter] = nodeExecutor{} + +type namespaceExecutor struct{} + +func (namespaceExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*types.Namespace, error) { + namespaces, err := cache.Presence.GetNamespaces() + if err != nil { + return nil, trace.Wrap(err) + } + + derefNamespaces := make([]*types.Namespace, len(namespaces)) + for i := range namespaces { + ns := namespaces[i] + derefNamespaces[i] = &ns + } + return derefNamespaces, nil +} + +func (namespaceExecutor) upsert(ctx context.Context, cache *Cache, resource *types.Namespace) error { + return cache.presenceCache.UpsertNamespace(*resource) +} + +func (namespaceExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllNamespaces() +} + +func (namespaceExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteNamespace(resource.GetName()) +} + +func (namespaceExecutor) isSingleton() bool { return false } + +func (namespaceExecutor) getReader(cache *Cache, cacheOK bool) namespaceGetter { + if cacheOK { + return cache.presenceCache + } + return cache.Config.Presence +} + +type namespaceGetter interface { + GetNamespaces() ([]types.Namespace, error) + GetNamespace(name string) (*types.Namespace, error) +} + +var _ executor[*types.Namespace, namespaceGetter] = namespaceExecutor{} + +type certAuthorityExecutor struct { + // extracted from watch.Filter, to avoid rebuilding on every event + filter types.CertAuthorityFilter +} + +// delete implements executor[types.CertAuthority] +func (certAuthorityExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + err := cache.trustCache.DeleteCertAuthority(ctx, types.CertAuthID{ + Type: types.CertAuthType(resource.GetSubKind()), + DomainName: resource.GetName(), + }) + return trace.Wrap(err) +} + +// deleteAll implements executor[types.CertAuthority] +func (certAuthorityExecutor) deleteAll(ctx context.Context, cache *Cache) error { + for _, caType := range types.CertAuthTypes { + if err := cache.trustCache.DeleteAllCertAuthorities(caType); err != nil { + return trace.Wrap(err) + } + } + return nil +} + +// getAll implements executor[types.CertAuthority] +func (e certAuthorityExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.CertAuthority, error) { + var authorities []types.CertAuthority + for _, caType := range types.CertAuthTypes { + cas, err := cache.Trust.GetCertAuthorities(ctx, caType, loadSecrets) + // if caType was added in this major version we might get a BadParameter + // error if we're connecting to an older upstream that doesn't know about it + if err != nil { + if !types.IsUnsupportedAuthorityErr(err) || !caType.NewlyAdded() { + return nil, trace.Wrap(err) + } + continue + } + + // this can be removed once we get the ability to fetch CAs with a filter, + // but it should be harmless, and it could be kept as additional safety + if !e.filter.IsEmpty() { + filtered := cas[:0] + for _, ca := range cas { + if e.filter.Match(ca) { + filtered = append(filtered, ca) + } + } + cas = filtered + } + + authorities = append(authorities, cas...) + } + + return authorities, nil +} + +// upsert implements executor[types.CertAuthority] +func (e certAuthorityExecutor) upsert(ctx context.Context, cache *Cache, value types.CertAuthority) error { + if !e.filter.Match(value) { + return nil + } + + return cache.trustCache.UpsertCertAuthority(ctx, value) +} + +func (certAuthorityExecutor) isSingleton() bool { return false } + +func (certAuthorityExecutor) getReader(cache *Cache, cacheOK bool) services.AuthorityGetter { + if cacheOK { + return cache.trustCache + } + return cache.Config.Trust +} + +var _ executor[types.CertAuthority, services.AuthorityGetter] = certAuthorityExecutor{} + +type staticTokensExecutor struct{} + +func (staticTokensExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.StaticTokens, error) { + token, err := cache.ClusterConfig.GetStaticTokens() + if err != nil { + return nil, trace.Wrap(err) + } + return []types.StaticTokens{token}, nil +} + +func (staticTokensExecutor) upsert(ctx context.Context, cache *Cache, resource types.StaticTokens) error { + return cache.clusterConfigCache.SetStaticTokens(resource) +} + +func (staticTokensExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteStaticTokens() +} + +func (staticTokensExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteStaticTokens() +} + +func (staticTokensExecutor) isSingleton() bool { return true } + +func (staticTokensExecutor) getReader(cache *Cache, cacheOK bool) staticTokensGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type staticTokensGetter interface { + GetStaticTokens() (types.StaticTokens, error) +} + +var _ executor[types.StaticTokens, staticTokensGetter] = staticTokensExecutor{} + +type provisionTokenExecutor struct{} + +func (provisionTokenExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ProvisionToken, error) { + return cache.Provisioner.GetTokens(ctx) +} + +func (provisionTokenExecutor) upsert(ctx context.Context, cache *Cache, resource types.ProvisionToken) error { + return cache.provisionerCache.UpsertToken(ctx, resource) +} + +func (provisionTokenExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.provisionerCache.DeleteAllTokens() +} + +func (provisionTokenExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.provisionerCache.DeleteToken(ctx, resource.GetName()) +} + +func (provisionTokenExecutor) isSingleton() bool { return false } + +func (provisionTokenExecutor) getReader(cache *Cache, cacheOK bool) tokenGetter { + if cacheOK { + return cache.provisionerCache + } + return cache.Config.Provisioner +} + +type tokenGetter interface { + GetTokens(ctx context.Context) ([]types.ProvisionToken, error) + GetToken(ctx context.Context, token string) (types.ProvisionToken, error) +} + +var _ executor[types.ProvisionToken, tokenGetter] = provisionTokenExecutor{} + +type clusterNameExecutor struct{} + +func (clusterNameExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ClusterName, error) { + name, err := cache.ClusterConfig.GetClusterName() + return []types.ClusterName{name}, trace.Wrap(err) +} + +func (clusterNameExecutor) upsert(ctx context.Context, cache *Cache, resource types.ClusterName) error { + return cache.clusterConfigCache.UpsertClusterName(resource) +} + +func (clusterNameExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteClusterName() +} + +func (clusterNameExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteClusterName() +} + +func (clusterNameExecutor) isSingleton() bool { return true } + +func (clusterNameExecutor) getReader(cache *Cache, cacheOK bool) clusterNameGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +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 autoUpdateAgentRolloutExecutor struct{} + +func (autoUpdateAgentRolloutExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*autoupdate.AutoUpdateAgentRollout, error) { + plan, err := cache.AutoUpdateService.GetAutoUpdateAgentRollout(ctx) + return []*autoupdate.AutoUpdateAgentRollout{plan}, trace.Wrap(err) +} + +func (autoUpdateAgentRolloutExecutor) upsert(ctx context.Context, cache *Cache, resource *autoupdate.AutoUpdateAgentRollout) error { + _, err := cache.autoUpdateCache.UpsertAutoUpdateAgentRollout(ctx, resource) + return trace.Wrap(err) +} + +func (autoUpdateAgentRolloutExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.autoUpdateCache.DeleteAutoUpdateAgentRollout(ctx) +} + +func (autoUpdateAgentRolloutExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.autoUpdateCache.DeleteAutoUpdateAgentRollout(ctx) +} + +func (autoUpdateAgentRolloutExecutor) isSingleton() bool { return true } + +func (autoUpdateAgentRolloutExecutor) getReader(cache *Cache, cacheOK bool) autoUpdateAgentRolloutGetter { + if cacheOK { + return cache.autoUpdateCache + } + return cache.Config.AutoUpdateService +} + +type autoUpdateAgentRolloutGetter interface { + GetAutoUpdateAgentRollout(ctx context.Context) (*autoupdate.AutoUpdateAgentRollout, error) +} + +var _ executor[*autoupdate.AutoUpdateAgentRollout, autoUpdateAgentRolloutGetter] = autoUpdateAgentRolloutExecutor{} + +type userExecutor struct{} + +func (userExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.User, error) { + return cache.Users.GetUsers(ctx, loadSecrets) +} + +func (userExecutor) upsert(ctx context.Context, cache *Cache, resource types.User) error { + _, err := cache.usersCache.UpsertUser(ctx, resource) + return err +} + +func (userExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.usersCache.DeleteAllUsers(ctx) +} + +func (userExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.usersCache.DeleteUser(ctx, resource.GetName()) +} + +func (userExecutor) isSingleton() bool { return false } + +func (userExecutor) getReader(cache *Cache, cacheOK bool) userGetter { + if cacheOK { + return cache.usersCache + } + return cache.Config.Users +} + +type userGetter interface { + GetUser(ctx context.Context, user string, withSecrets bool) (types.User, error) + GetUsers(ctx context.Context, withSecrets bool) ([]types.User, error) + ListUsers(ctx context.Context, req *userspb.ListUsersRequest) (*userspb.ListUsersResponse, error) +} + +var _ executor[types.User, userGetter] = userExecutor{} + +type roleExecutor struct{} + +func (roleExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Role, error) { + return cache.Access.GetRoles(ctx) +} + +func (roleExecutor) upsert(ctx context.Context, cache *Cache, resource types.Role) error { + _, err := cache.accessCache.UpsertRole(ctx, resource) + return err +} + +func (roleExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.accessCache.DeleteAllRoles(ctx) +} + +func (roleExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.accessCache.DeleteRole(ctx, resource.GetName()) +} + +func (roleExecutor) isSingleton() bool { return false } + +func (roleExecutor) getReader(cache *Cache, cacheOK bool) roleGetter { + if cacheOK { + return cache.accessCache + } + return cache.Config.Access +} + +type roleGetter interface { + GetRoles(ctx context.Context) ([]types.Role, error) + GetRole(ctx context.Context, name string) (types.Role, error) + ListRoles(ctx context.Context, req *proto.ListRolesRequest) (*proto.ListRolesResponse, error) +} + +var _ executor[types.Role, roleGetter] = roleExecutor{} + +type databaseServerExecutor struct{} + +func (databaseServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.DatabaseServer, error) { + return cache.Presence.GetDatabaseServers(ctx, apidefaults.Namespace) +} + +func (databaseServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.DatabaseServer) error { + _, err := cache.presenceCache.UpsertDatabaseServer(ctx, resource) + return trace.Wrap(err) +} + +func (databaseServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllDatabaseServers(ctx, apidefaults.Namespace) +} + +func (databaseServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteDatabaseServer(ctx, + resource.GetMetadata().Namespace, + resource.GetMetadata().Description, // Cache passes host ID via description field. + resource.GetName()) +} + +func (databaseServerExecutor) isSingleton() bool { return false } + +func (databaseServerExecutor) getReader(cache *Cache, cacheOK bool) databaseServerGetter { + if cacheOK { + return cache.presenceCache + } + return cache.Config.Presence +} + +type databaseServerGetter interface { + GetDatabaseServers(context.Context, string, ...services.MarshalOption) ([]types.DatabaseServer, error) +} + +var _ executor[types.DatabaseServer, databaseServerGetter] = databaseServerExecutor{} + +type databaseServiceExecutor struct{} + +func (databaseServiceExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.DatabaseService, error) { + resources, err := client.GetResourcesWithFilters(ctx, cache.Presence, proto.ListResourcesRequest{ResourceType: types.KindDatabaseService}) + if err != nil { + return nil, trace.Wrap(err) + } + + dbsvcs := make([]types.DatabaseService, len(resources)) + for i, resource := range resources { + dbsvc, ok := resource.(types.DatabaseService) + if !ok { + return nil, trace.BadParameter("unexpected resource %T", resource) + } + dbsvcs[i] = dbsvc + } + + return dbsvcs, nil +} + +func (databaseServiceExecutor) upsert(ctx context.Context, cache *Cache, resource types.DatabaseService) error { + _, err := cache.databaseServicesCache.UpsertDatabaseService(ctx, resource) + return trace.Wrap(err) +} + +func (databaseServiceExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.databaseServicesCache.DeleteAllDatabaseServices(ctx) +} + +func (databaseServiceExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.databaseServicesCache.DeleteDatabaseService(ctx, resource.GetName()) +} + +func (databaseServiceExecutor) isSingleton() bool { return false } + +func (databaseServiceExecutor) getReader(_ *Cache, _ bool) noReader { + return noReader{} +} + +var _ executor[types.DatabaseService, noReader] = databaseServiceExecutor{} + +type databaseExecutor struct{} + +func (databaseExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Database, error) { + return cache.Databases.GetDatabases(ctx) +} + +func (databaseExecutor) upsert(ctx context.Context, cache *Cache, resource types.Database) error { + if err := cache.databasesCache.CreateDatabase(ctx, resource); err != nil { + if !trace.IsAlreadyExists(err) { + return trace.Wrap(err) + } + return trace.Wrap(cache.databasesCache.UpdateDatabase(ctx, resource)) + } + + return nil +} + +func (databaseExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.databasesCache.DeleteAllDatabases(ctx) +} + +func (databaseExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.databasesCache.DeleteDatabase(ctx, resource.GetName()) +} + +func (databaseExecutor) isSingleton() bool { return false } + +func (databaseExecutor) getReader(cache *Cache, cacheOK bool) services.DatabaseGetter { + if cacheOK { + return cache.databasesCache + } + return cache.Config.Databases +} + +var _ executor[types.Database, services.DatabaseGetter] = databaseExecutor{} + +type databaseObjectExecutor struct{} + +func (databaseObjectExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*dbobjectv1.DatabaseObject, error) { + var out []*dbobjectv1.DatabaseObject + var nextToken string + for { + var page []*dbobjectv1.DatabaseObject + var err error + + page, nextToken, err = cache.DatabaseObjects.ListDatabaseObjects(ctx, 0, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + out = append(out, page...) + if nextToken == "" { + break + } + } + return out, nil +} + +func (databaseObjectExecutor) upsert(ctx context.Context, cache *Cache, resource *dbobjectv1.DatabaseObject) error { + _, err := cache.databaseObjectsCache.UpsertDatabaseObject(ctx, resource) + return trace.Wrap(err) +} + +func (databaseObjectExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.databaseObjectsCache.DeleteAllDatabaseObjects(ctx)) +} + +func (databaseObjectExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return trace.Wrap(cache.databaseObjectsCache.DeleteDatabaseObject(ctx, resource.GetName())) +} + +func (databaseObjectExecutor) isSingleton() bool { return false } + +func (databaseObjectExecutor) getReader(cache *Cache, cacheOK bool) services.DatabaseObjectsGetter { + if cacheOK { + return cache.databaseObjectsCache + } + return cache.Config.DatabaseObjects +} + +var _ executor[*dbobjectv1.DatabaseObject, services.DatabaseObjectsGetter] = databaseObjectExecutor{} + +type appExecutor struct{} + +func (appExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Application, error) { + return cache.Apps.GetApps(ctx) +} + +func (appExecutor) upsert(ctx context.Context, cache *Cache, resource types.Application) error { + if err := cache.appsCache.CreateApp(ctx, resource); err != nil { + if !trace.IsAlreadyExists(err) { + return trace.Wrap(err) + } + return trace.Wrap(cache.appsCache.UpdateApp(ctx, resource)) + } + + return nil +} + +func (appExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.appsCache.DeleteAllApps(ctx) +} + +func (appExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.appsCache.DeleteApp(ctx, resource.GetName()) +} + +func (appExecutor) getReader(cache *Cache, cacheOK bool) services.AppGetter { + if cacheOK { + return cache.appsCache + } + return cache.Apps +} + +func (appExecutor) isSingleton() bool { return false } + +var _ executor[types.Application, services.AppGetter] = appExecutor{} + +type appServerExecutor struct{} + +func (appServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.AppServer, error) { + return cache.Presence.GetApplicationServers(ctx, apidefaults.Namespace) +} + +func (appServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.AppServer) error { + _, err := cache.presenceCache.UpsertApplicationServer(ctx, resource) + return trace.Wrap(err) +} + +func (appServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllApplicationServers(ctx, apidefaults.Namespace) +} + +func (appServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteApplicationServer(ctx, + resource.GetMetadata().Namespace, + resource.GetMetadata().Description, // Cache passes host ID via description field. + resource.GetName()) +} + +func (appServerExecutor) isSingleton() bool { return false } + +func (appServerExecutor) getReader(cache *Cache, cacheOK bool) appServerGetter { + if cacheOK { + return cache.presenceCache + } + return cache.Config.Presence +} + +type appServerGetter interface { + GetApplicationServers(context.Context, string) ([]types.AppServer, error) +} + +var _ executor[types.AppServer, appServerGetter] = appServerExecutor{} + +type appSessionExecutor struct{} + +func (appSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { + var ( + startKey string + sessions []types.WebSession + ) + for { + webSessions, nextKey, err := cache.AppSession.ListAppSessions(ctx, 0, startKey, "") + if err != nil { + return nil, trace.Wrap(err) + } + + if !loadSecrets { + for i := 0; i < len(webSessions); i++ { + webSessions[i] = webSessions[i].WithoutSecrets() + } + } + + sessions = append(sessions, webSessions...) + + if nextKey == "" { + break + } + startKey = nextKey + } + return sessions, nil +} + +func (appSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { + return cache.appSessionCache.UpsertAppSession(ctx, resource) +} + +func (appSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.appSessionCache.DeleteAllAppSessions(ctx) +} + +func (appSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.appSessionCache.DeleteAppSession(ctx, types.DeleteAppSessionRequest{ + SessionID: resource.GetName(), + }) +} + +func (appSessionExecutor) isSingleton() bool { return false } + +func (appSessionExecutor) getReader(cache *Cache, cacheOK bool) appSessionGetter { + if cacheOK { + return cache.appSessionCache + } + return cache.Config.AppSession +} + +type appSessionGetter interface { + GetAppSession(ctx context.Context, req types.GetAppSessionRequest) (types.WebSession, error) + ListAppSessions(ctx context.Context, pageSize int, pageToken, user string) ([]types.WebSession, string, error) +} + +var _ executor[types.WebSession, appSessionGetter] = appSessionExecutor{} + +type snowflakeSessionExecutor struct{} + +func (snowflakeSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { + webSessions, err := cache.SnowflakeSession.GetSnowflakeSessions(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if !loadSecrets { + for i := 0; i < len(webSessions); i++ { + webSessions[i] = webSessions[i].WithoutSecrets() + } + } + + return webSessions, nil +} + +func (snowflakeSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { + return cache.snowflakeSessionCache.UpsertSnowflakeSession(ctx, resource) +} + +func (snowflakeSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.snowflakeSessionCache.DeleteAllSnowflakeSessions(ctx) +} + +func (snowflakeSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.snowflakeSessionCache.DeleteSnowflakeSession(ctx, types.DeleteSnowflakeSessionRequest{ + SessionID: resource.GetName(), + }) +} + +func (snowflakeSessionExecutor) isSingleton() bool { return false } + +func (snowflakeSessionExecutor) getReader(cache *Cache, cacheOK bool) snowflakeSessionGetter { + if cacheOK { + return cache.snowflakeSessionCache + } + return cache.Config.SnowflakeSession +} + +type snowflakeSessionGetter interface { + GetSnowflakeSession(context.Context, types.GetSnowflakeSessionRequest) (types.WebSession, error) +} + +var _ executor[types.WebSession, snowflakeSessionGetter] = snowflakeSessionExecutor{} + +//nolint:revive // Because we want this to be IdP. +type samlIdPSessionExecutor struct{} + +func (samlIdPSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { + var ( + startKey string + sessions []types.WebSession + ) + for { + webSessions, nextKey, err := cache.SAMLIdPSession.ListSAMLIdPSessions(ctx, 0, startKey, "") + if err != nil { + return nil, trace.Wrap(err) + } + + if !loadSecrets { + for i := 0; i < len(webSessions); i++ { + webSessions[i] = webSessions[i].WithoutSecrets() + } + } + + sessions = append(sessions, webSessions...) + + if nextKey == "" { + break + } + startKey = nextKey + } + return sessions, nil +} + +func (samlIdPSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { + return cache.samlIdPSessionCache.UpsertSAMLIdPSession(ctx, resource) +} + +func (samlIdPSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.samlIdPSessionCache.DeleteAllSAMLIdPSessions(ctx) +} + +func (samlIdPSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.samlIdPSessionCache.DeleteSAMLIdPSession(ctx, types.DeleteSAMLIdPSessionRequest{ + SessionID: resource.GetName(), + }) +} + +func (samlIdPSessionExecutor) isSingleton() bool { return false } + +func (samlIdPSessionExecutor) getReader(cache *Cache, cacheOK bool) samlIdPSessionGetter { + if cacheOK { + return cache.samlIdPSessionCache + } + return cache.Config.SAMLIdPSession +} + +type samlIdPSessionGetter interface { + GetSAMLIdPSession(context.Context, types.GetSAMLIdPSessionRequest) (types.WebSession, error) +} + +var _ executor[types.WebSession, samlIdPSessionGetter] = samlIdPSessionExecutor{} + +type webSessionExecutor struct{} + +func (webSessionExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebSession, error) { + webSessions, err := cache.WebSession.List(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + if !loadSecrets { + for i := 0; i < len(webSessions); i++ { + webSessions[i] = webSessions[i].WithoutSecrets() + } + } + + return webSessions, nil +} + +func (webSessionExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebSession) error { + return cache.webSessionCache.Upsert(ctx, resource) +} + +func (webSessionExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.webSessionCache.DeleteAll(ctx) +} + +func (webSessionExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.webSessionCache.Delete(ctx, types.DeleteWebSessionRequest{ + SessionID: resource.GetName(), + }) +} + +func (webSessionExecutor) isSingleton() bool { return false } + +func (webSessionExecutor) getReader(cache *Cache, cacheOK bool) webSessionGetter { + if cacheOK { + return cache.webSessionCache + } + return cache.Config.WebSession +} + +type webSessionGetter interface { + Get(ctx context.Context, req types.GetWebSessionRequest) (types.WebSession, error) +} + +var _ executor[types.WebSession, webSessionGetter] = webSessionExecutor{} + +type webTokenExecutor struct{} + +func (webTokenExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WebToken, error) { + return cache.WebToken.List(ctx) +} + +func (webTokenExecutor) upsert(ctx context.Context, cache *Cache, resource types.WebToken) error { + return cache.webTokenCache.Upsert(ctx, resource) +} + +func (webTokenExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.webTokenCache.DeleteAll(ctx) +} + +func (webTokenExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.webTokenCache.Delete(ctx, types.DeleteWebTokenRequest{ + Token: resource.GetName(), + }) +} + +func (webTokenExecutor) isSingleton() bool { return false } + +func (webTokenExecutor) getReader(cache *Cache, cacheOK bool) webTokenGetter { + if cacheOK { + return cache.webTokenCache + } + return cache.Config.WebToken +} + +type webTokenGetter interface { + Get(ctx context.Context, req types.GetWebTokenRequest) (types.WebToken, error) +} + +var _ executor[types.WebToken, webTokenGetter] = webTokenExecutor{} + +type kubeServerExecutor struct{} + +func (kubeServerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.KubeServer, error) { + return cache.Presence.GetKubernetesServers(ctx) +} + +func (kubeServerExecutor) upsert(ctx context.Context, cache *Cache, resource types.KubeServer) error { + _, err := cache.presenceCache.UpsertKubernetesServer(ctx, resource) + return trace.Wrap(err) +} + +func (kubeServerExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllKubernetesServers(ctx) +} + +func (kubeServerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteKubernetesServer( + ctx, + resource.GetMetadata().Description, // Cache passes host ID via description field. + resource.GetName(), + ) +} + +func (kubeServerExecutor) isSingleton() bool { return false } + +func (kubeServerExecutor) getReader(cache *Cache, cacheOK bool) kubeServerGetter { + if cacheOK { + return cache.presenceCache + } + return cache.Config.Presence +} + +type kubeServerGetter interface { + GetKubernetesServers(context.Context) ([]types.KubeServer, error) +} + +var _ executor[types.KubeServer, kubeServerGetter] = kubeServerExecutor{} + +type authPreferenceExecutor struct{} + +func (authPreferenceExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.AuthPreference, error) { + authPref, err := cache.ClusterConfig.GetAuthPreference(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.AuthPreference{authPref}, nil +} + +func (authPreferenceExecutor) upsert(ctx context.Context, cache *Cache, resource types.AuthPreference) error { + _, err := cache.clusterConfigCache.UpsertAuthPreference(ctx, resource) + return trace.Wrap(err) +} + +func (authPreferenceExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteAuthPreference(ctx) +} + +func (authPreferenceExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteAuthPreference(ctx) +} + +func (authPreferenceExecutor) isSingleton() bool { return true } + +func (authPreferenceExecutor) getReader(cache *Cache, cacheOK bool) authPreferenceGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type authPreferenceGetter interface { + GetAuthPreference(ctx context.Context) (types.AuthPreference, error) +} + +var _ executor[types.AuthPreference, authPreferenceGetter] = authPreferenceExecutor{} + +type clusterAuditConfigExecutor struct{} + +func (clusterAuditConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ClusterAuditConfig, error) { + auditConfig, err := cache.ClusterConfig.GetClusterAuditConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.ClusterAuditConfig{auditConfig}, nil +} + +func (clusterAuditConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.ClusterAuditConfig) error { + return cache.clusterConfigCache.SetClusterAuditConfig(ctx, resource) +} + +func (clusterAuditConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteClusterAuditConfig(ctx) +} + +func (clusterAuditConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteClusterAuditConfig(ctx) +} + +func (clusterAuditConfigExecutor) isSingleton() bool { return true } + +func (clusterAuditConfigExecutor) getReader(cache *Cache, cacheOK bool) clusterAuditConfigGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type clusterAuditConfigGetter interface { + GetClusterAuditConfig(context.Context) (types.ClusterAuditConfig, error) +} + +var _ executor[types.ClusterAuditConfig, clusterAuditConfigGetter] = clusterAuditConfigExecutor{} + +type clusterNetworkingConfigExecutor struct{} + +func (clusterNetworkingConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.ClusterNetworkingConfig, error) { + networkingConfig, err := cache.ClusterConfig.GetClusterNetworkingConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.ClusterNetworkingConfig{networkingConfig}, nil +} + +func (clusterNetworkingConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.ClusterNetworkingConfig) error { + _, err := cache.clusterConfigCache.UpsertClusterNetworkingConfig(ctx, resource) + return trace.Wrap(err) +} + +func (clusterNetworkingConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteClusterNetworkingConfig(ctx) +} + +func (clusterNetworkingConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteClusterNetworkingConfig(ctx) +} + +func (clusterNetworkingConfigExecutor) isSingleton() bool { return true } + +func (clusterNetworkingConfigExecutor) getReader(cache *Cache, cacheOK bool) clusterNetworkingConfigGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type clusterNetworkingConfigGetter interface { + GetClusterNetworkingConfig(context.Context) (types.ClusterNetworkingConfig, error) +} + +var _ executor[types.ClusterNetworkingConfig, clusterNetworkingConfigGetter] = clusterNetworkingConfigExecutor{} + +type uiConfigExecutor struct{} + +func (uiConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.UIConfig, error) { + uiConfig, err := cache.ClusterConfig.GetUIConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.UIConfig{uiConfig}, nil +} + +func (uiConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.UIConfig) error { + return cache.clusterConfigCache.SetUIConfig(ctx, resource) +} + +func (uiConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteUIConfig(ctx) +} + +func (uiConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteUIConfig(ctx) +} + +func (uiConfigExecutor) isSingleton() bool { return true } + +func (uiConfigExecutor) getReader(cache *Cache, cacheOK bool) uiConfigGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type uiConfigGetter interface { + GetUIConfig(context.Context) (types.UIConfig, error) +} + +var _ executor[types.UIConfig, uiConfigGetter] = uiConfigExecutor{} + +type sessionRecordingConfigExecutor struct{} + +func (sessionRecordingConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.SessionRecordingConfig, error) { + sessionRecordingConfig, err := cache.ClusterConfig.GetSessionRecordingConfig(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.SessionRecordingConfig{sessionRecordingConfig}, nil +} + +func (sessionRecordingConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.SessionRecordingConfig) error { + _, err := cache.clusterConfigCache.UpsertSessionRecordingConfig(ctx, resource) + return trace.Wrap(err) +} + +func (sessionRecordingConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteSessionRecordingConfig(ctx) +} + +func (sessionRecordingConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteSessionRecordingConfig(ctx) +} + +func (sessionRecordingConfigExecutor) isSingleton() bool { return true } + +func (sessionRecordingConfigExecutor) getReader(cache *Cache, cacheOK bool) sessionRecordingConfigGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type sessionRecordingConfigGetter interface { + GetSessionRecordingConfig(ctx context.Context) (types.SessionRecordingConfig, error) +} + +var _ executor[types.SessionRecordingConfig, sessionRecordingConfigGetter] = sessionRecordingConfigExecutor{} + +type installerConfigExecutor struct{} + +func (installerConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Installer, error) { + return cache.ClusterConfig.GetInstallers(ctx) +} + +func (installerConfigExecutor) upsert(ctx context.Context, cache *Cache, resource types.Installer) error { + return cache.clusterConfigCache.SetInstaller(ctx, resource) +} + +func (installerConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.clusterConfigCache.DeleteAllInstallers(ctx) +} + +func (installerConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.clusterConfigCache.DeleteInstaller(ctx, resource.GetName()) +} + +func (installerConfigExecutor) isSingleton() bool { return false } + +func (installerConfigExecutor) getReader(cache *Cache, cacheOK bool) installerGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type installerGetter interface { + GetInstallers(context.Context) ([]types.Installer, error) + GetInstaller(ctx context.Context, name string) (types.Installer, error) +} + +var _ executor[types.Installer, installerGetter] = installerConfigExecutor{} + +type networkRestrictionsExecutor struct{} + +func (networkRestrictionsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.NetworkRestrictions, error) { + restrictions, err := cache.Restrictions.GetNetworkRestrictions(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + return []types.NetworkRestrictions{restrictions}, nil +} + +func (networkRestrictionsExecutor) upsert(ctx context.Context, cache *Cache, resource types.NetworkRestrictions) error { + return cache.restrictionsCache.SetNetworkRestrictions(ctx, resource) +} + +func (networkRestrictionsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.restrictionsCache.DeleteNetworkRestrictions(ctx) +} + +func (networkRestrictionsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.restrictionsCache.DeleteNetworkRestrictions(ctx) +} + +func (networkRestrictionsExecutor) isSingleton() bool { return true } + +func (networkRestrictionsExecutor) getReader(cache *Cache, cacheOK bool) networkRestrictionGetter { + if cacheOK { + return cache.restrictionsCache + } + return cache.Config.Restrictions +} + +type networkRestrictionGetter interface { + GetNetworkRestrictions(context.Context) (types.NetworkRestrictions, error) +} + +var _ executor[types.NetworkRestrictions, networkRestrictionGetter] = networkRestrictionsExecutor{} + +type lockExecutor struct{} + +func (lockExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Lock, error) { + return cache.Access.GetLocks(ctx, false) +} + +func (lockExecutor) upsert(ctx context.Context, cache *Cache, resource types.Lock) error { + return cache.accessCache.UpsertLock(ctx, resource) +} + +func (lockExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.accessCache.DeleteAllLocks(ctx) +} + +func (lockExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.accessCache.DeleteLock(ctx, resource.GetName()) +} + +func (lockExecutor) isSingleton() bool { return false } + +func (lockExecutor) getReader(cache *Cache, cacheOK bool) services.LockGetter { + if cacheOK { + return cache.accessCache + } + return cache.Config.Access +} + +var _ executor[types.Lock, services.LockGetter] = lockExecutor{} + +type windowsDesktopServicesExecutor struct{} + +func (windowsDesktopServicesExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WindowsDesktopService, error) { + return cache.Presence.GetWindowsDesktopServices(ctx) +} + +func (windowsDesktopServicesExecutor) upsert(ctx context.Context, cache *Cache, resource types.WindowsDesktopService) error { + _, err := cache.presenceCache.UpsertWindowsDesktopService(ctx, resource) + return trace.Wrap(err) +} + +func (windowsDesktopServicesExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.presenceCache.DeleteAllWindowsDesktopServices(ctx) +} + +func (windowsDesktopServicesExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.presenceCache.DeleteWindowsDesktopService(ctx, resource.GetName()) +} + +func (windowsDesktopServicesExecutor) isSingleton() bool { return false } + +func (windowsDesktopServicesExecutor) getReader(cache *Cache, cacheOK bool) windowsDesktopServiceGetter { + if cacheOK { + return windowsDesktopServiceAggregate{ + Presence: cache.presenceCache, + WindowsDesktops: cache.windowsDesktopsCache, + } + } + return windowsDesktopServiceAggregate{ + Presence: cache.Config.Presence, + WindowsDesktops: cache.Config.WindowsDesktops, + } +} + +type windowsDesktopServiceAggregate struct { + services.Presence + services.WindowsDesktops +} + +type windowsDesktopServiceGetter interface { + GetWindowsDesktopServices(ctx context.Context) ([]types.WindowsDesktopService, error) + GetWindowsDesktopService(ctx context.Context, name string) (types.WindowsDesktopService, error) + ListWindowsDesktopServices(ctx context.Context, req types.ListWindowsDesktopServicesRequest) (*types.ListWindowsDesktopServicesResponse, error) +} + +var _ executor[types.WindowsDesktopService, windowsDesktopServiceGetter] = windowsDesktopServicesExecutor{} + +type windowsDesktopsExecutor struct{} + +func (windowsDesktopsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.WindowsDesktop, error) { + return cache.WindowsDesktops.GetWindowsDesktops(ctx, types.WindowsDesktopFilter{}) +} + +func (windowsDesktopsExecutor) upsert(ctx context.Context, cache *Cache, resource types.WindowsDesktop) error { + return cache.windowsDesktopsCache.UpsertWindowsDesktop(ctx, resource) +} + +func (windowsDesktopsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.windowsDesktopsCache.DeleteAllWindowsDesktops(ctx) +} + +func (windowsDesktopsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.windowsDesktopsCache.DeleteWindowsDesktop(ctx, + resource.GetMetadata().Description, // Cache passes host ID via description field. + resource.GetName(), + ) +} + +func (windowsDesktopsExecutor) isSingleton() bool { return false } + +func (windowsDesktopsExecutor) getReader(cache *Cache, cacheOK bool) windowsDesktopsGetter { + if cacheOK { + return cache.windowsDesktopsCache + } + return cache.Config.WindowsDesktops +} + +type windowsDesktopsGetter interface { + GetWindowsDesktops(context.Context, types.WindowsDesktopFilter) ([]types.WindowsDesktop, error) + ListWindowsDesktops(ctx context.Context, req types.ListWindowsDesktopsRequest) (*types.ListWindowsDesktopsResponse, error) +} + +var _ executor[types.WindowsDesktop, windowsDesktopsGetter] = windowsDesktopsExecutor{} + +type dynamicWindowsDesktopsExecutor struct{} + +func (dynamicWindowsDesktopsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.DynamicWindowsDesktop, error) { + var desktops []types.DynamicWindowsDesktop + next := "" + for { + d, token, err := cache.Config.DynamicWindowsDesktops.ListDynamicWindowsDesktops(ctx, defaults.MaxIterationLimit, next) + if err != nil { + return nil, err + } + desktops = append(desktops, d...) + if token == "" { + break + } + next = token + } + return desktops, nil +} + +func (dynamicWindowsDesktopsExecutor) upsert(ctx context.Context, cache *Cache, resource types.DynamicWindowsDesktop) error { + _, err := cache.dynamicWindowsDesktopsCache.UpsertDynamicWindowsDesktop(ctx, resource) + return err +} + +func (dynamicWindowsDesktopsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.dynamicWindowsDesktopsCache.DeleteAllDynamicWindowsDesktops(ctx) +} + +func (dynamicWindowsDesktopsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.dynamicWindowsDesktopsCache.DeleteDynamicWindowsDesktop(ctx, resource.GetName()) +} + +func (dynamicWindowsDesktopsExecutor) isSingleton() bool { return false } + +func (dynamicWindowsDesktopsExecutor) getReader(cache *Cache, cacheOK bool) dynamicWindowsDesktopsGetter { + if cacheOK { + return cache.dynamicWindowsDesktopsCache + } + return cache.Config.DynamicWindowsDesktops +} + +type dynamicWindowsDesktopsGetter interface { + GetDynamicWindowsDesktop(ctx context.Context, name string) (types.DynamicWindowsDesktop, error) + ListDynamicWindowsDesktops(ctx context.Context, pageSize int, nextPage string) ([]types.DynamicWindowsDesktop, string, error) +} + +var _ executor[types.DynamicWindowsDesktop, dynamicWindowsDesktopsGetter] = dynamicWindowsDesktopsExecutor{} + +type kubeClusterExecutor struct{} + +func (kubeClusterExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.KubeCluster, error) { + return cache.Kubernetes.GetKubernetesClusters(ctx) +} + +func (kubeClusterExecutor) upsert(ctx context.Context, cache *Cache, resource types.KubeCluster) error { + if err := cache.kubernetesCache.CreateKubernetesCluster(ctx, resource); err != nil { + if !trace.IsAlreadyExists(err) { + return trace.Wrap(err) + } + return trace.Wrap(cache.kubernetesCache.UpdateKubernetesCluster(ctx, resource)) + } + + return nil +} + +func (kubeClusterExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.kubernetesCache.DeleteAllKubernetesClusters(ctx) +} + +func (kubeClusterExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.kubernetesCache.DeleteKubernetesCluster(ctx, resource.GetName()) +} + +func (kubeClusterExecutor) isSingleton() bool { return false } + +func (kubeClusterExecutor) getReader(cache *Cache, cacheOK bool) kubernetesClusterGetter { + if cacheOK { + return cache.kubernetesCache + } + return cache.Config.Kubernetes +} + +type kubernetesClusterGetter interface { + GetKubernetesClusters(ctx context.Context) ([]types.KubeCluster, error) + GetKubernetesCluster(ctx context.Context, name string) (types.KubeCluster, error) +} + +type kubeWaitingContainerExecutor struct{} + +func (kubeWaitingContainerExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*kubewaitingcontainerpb.KubernetesWaitingContainer, error) { + var ( + startKey string + allConts []*kubewaitingcontainerpb.KubernetesWaitingContainer + ) + for { + conts, nextKey, err := cache.KubeWaitingContainers.ListKubernetesWaitingContainers(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + allConts = append(allConts, conts...) + + if nextKey == "" { + break + } + startKey = nextKey + } + return allConts, nil +} + +func (kubeWaitingContainerExecutor) upsert(ctx context.Context, cache *Cache, resource *kubewaitingcontainerpb.KubernetesWaitingContainer) error { + _, err := cache.kubeWaitingContsCache.UpsertKubernetesWaitingContainer(ctx, resource) + return trace.Wrap(err) +} + +func (kubeWaitingContainerExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.kubeWaitingContsCache.DeleteAllKubernetesWaitingContainers(ctx)) +} + +func (kubeWaitingContainerExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + switch r := resource.(type) { + case types.Resource153Unwrapper: + switch wc := r.Unwrap().(type) { + case *kubewaitingcontainerpb.KubernetesWaitingContainer: + err := cache.kubeWaitingContsCache.DeleteKubernetesWaitingContainer(ctx, &kubewaitingcontainerpb.DeleteKubernetesWaitingContainerRequest{ + Username: wc.Spec.Username, + Cluster: wc.Spec.Cluster, + Namespace: wc.Spec.Namespace, + PodName: wc.Spec.PodName, + ContainerName: wc.Spec.ContainerName, + }) + return trace.Wrap(err) + } + } + + return trace.BadParameter("unknown KubeWaitingContainer type, expected *kubewaitingcontainerpb.KubernetesWaitingContainer, got %T", resource) +} + +func (kubeWaitingContainerExecutor) isSingleton() bool { return false } + +func (kubeWaitingContainerExecutor) getReader(cache *Cache, cacheOK bool) kubernetesWaitingContainerGetter { + if cacheOK { + return cache.kubeWaitingContsCache + } + return cache.Config.KubeWaitingContainers +} + +type kubernetesWaitingContainerGetter interface { + ListKubernetesWaitingContainers(ctx context.Context, pageSize int, pageToken string) ([]*kubewaitingcontainerpb.KubernetesWaitingContainer, string, error) + GetKubernetesWaitingContainer(ctx context.Context, req *kubewaitingcontainerpb.GetKubernetesWaitingContainerRequest) (*kubewaitingcontainerpb.KubernetesWaitingContainer, error) +} + +var _ executor[*kubewaitingcontainerpb.KubernetesWaitingContainer, kubernetesWaitingContainerGetter] = kubeWaitingContainerExecutor{} + +type staticHostUserExecutor struct{} + +func (staticHostUserExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*userprovisioningpb.StaticHostUser, error) { + var ( + startKey string + allUsers []*userprovisioningpb.StaticHostUser + ) + for { + users, nextKey, err := cache.StaticHostUsers.ListStaticHostUsers(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + allUsers = append(allUsers, users...) + + if nextKey == "" { + break + } + startKey = nextKey + } + return allUsers, nil +} + +func (staticHostUserExecutor) upsert(ctx context.Context, cache *Cache, resource *userprovisioningpb.StaticHostUser) error { + _, err := cache.staticHostUsersCache.UpsertStaticHostUser(ctx, resource) + return trace.Wrap(err) +} + +func (staticHostUserExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.staticHostUsersCache.DeleteAllStaticHostUsers(ctx)) +} + +func (staticHostUserExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return trace.Wrap(cache.staticHostUsersCache.DeleteStaticHostUser(ctx, resource.GetName())) +} + +func (staticHostUserExecutor) isSingleton() bool { return false } + +func (staticHostUserExecutor) getReader(cache *Cache, cacheOK bool) staticHostUserGetter { + if cacheOK { + return cache.staticHostUsersCache + } + return cache.Config.StaticHostUsers +} + +type staticHostUserGetter interface { + ListStaticHostUsers(ctx context.Context, pageSize int, pageToken string) ([]*userprovisioningpb.StaticHostUser, string, error) + GetStaticHostUser(ctx context.Context, name string) (*userprovisioningpb.StaticHostUser, error) +} + +type crownJewelsExecutor struct{} + +func (crownJewelsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*crownjewelv1.CrownJewel, error) { + var resources []*crownjewelv1.CrownJewel + var nextToken string + for { + var page []*crownjewelv1.CrownJewel + var err error + page, nextToken, err = cache.CrownJewels.ListCrownJewels(ctx, 0 /* page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + resources = append(resources, page...) + + if nextToken == "" { + break + } + } + return resources, nil +} + +func (crownJewelsExecutor) upsert(ctx context.Context, cache *Cache, resource *crownjewelv1.CrownJewel) error { + _, err := cache.crownJewelsCache.UpsertCrownJewel(ctx, resource) + return trace.Wrap(err) +} + +func (crownJewelsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.crownJewelsCache.DeleteAllCrownJewels(ctx) +} + +func (crownJewelsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.crownJewelsCache.DeleteCrownJewel(ctx, resource.GetName()) +} + +func (crownJewelsExecutor) isSingleton() bool { return false } + +func (crownJewelsExecutor) getReader(cache *Cache, cacheOK bool) crownjewelsGetter { + if cacheOK { + return cache.crownJewelsCache + } + return cache.Config.CrownJewels +} + +var _ executor[*crownjewelv1.CrownJewel, crownjewelsGetter] = crownJewelsExecutor{} + +type userTasksExecutor struct{} + +func (userTasksExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*usertasksv1.UserTask, error) { + var resources []*usertasksv1.UserTask + var nextToken string + for { + var page []*usertasksv1.UserTask + var err error + page, nextToken, err = cache.UserTasks.ListUserTasks(ctx, 0 /* page size */, nextToken, &usertasksv1.ListUserTasksFilters{}) + if err != nil { + return nil, trace.Wrap(err) + } + resources = append(resources, page...) + + if nextToken == "" { + break + } + } + return resources, nil +} + +func (userTasksExecutor) upsert(ctx context.Context, cache *Cache, resource *usertasksv1.UserTask) error { + _, err := cache.userTasksCache.UpsertUserTask(ctx, resource) + return trace.Wrap(err) +} + +func (userTasksExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.userTasksCache.DeleteAllUserTasks(ctx) +} + +func (userTasksExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.userTasksCache.DeleteUserTask(ctx, resource.GetName()) +} + +func (userTasksExecutor) isSingleton() bool { return false } + +func (userTasksExecutor) getReader(cache *Cache, cacheOK bool) userTasksGetter { + if cacheOK { + return cache.userTasksCache + } + return cache.Config.UserTasks +} + +var _ executor[*usertasksv1.UserTask, userTasksGetter] = userTasksExecutor{} + +//nolint:revive // Because we want this to be IdP. +type samlIdPServiceProvidersExecutor struct{} + +func (samlIdPServiceProvidersExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.SAMLIdPServiceProvider, error) { + var ( + startKey string + sps []types.SAMLIdPServiceProvider + ) + for { + var samlProviders []types.SAMLIdPServiceProvider + var err error + samlProviders, startKey, err = cache.SAMLIdPServiceProviders.ListSAMLIdPServiceProviders(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + sps = append(sps, samlProviders...) + + if startKey == "" { + break + } + } + + return sps, nil +} + +func (samlIdPServiceProvidersExecutor) upsert(ctx context.Context, cache *Cache, resource types.SAMLIdPServiceProvider) error { + err := cache.samlIdPServiceProvidersCache.CreateSAMLIdPServiceProvider(ctx, resource) + if trace.IsAlreadyExists(err) { + err = cache.samlIdPServiceProvidersCache.UpdateSAMLIdPServiceProvider(ctx, resource) + } + return trace.Wrap(err) +} + +func (samlIdPServiceProvidersExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.samlIdPServiceProvidersCache.DeleteAllSAMLIdPServiceProviders(ctx) +} + +func (samlIdPServiceProvidersExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.samlIdPServiceProvidersCache.DeleteSAMLIdPServiceProvider(ctx, resource.GetName()) +} + +func (samlIdPServiceProvidersExecutor) isSingleton() bool { return false } + +func (samlIdPServiceProvidersExecutor) getReader(cache *Cache, cacheOK bool) samlIdPServiceProviderGetter { + if cacheOK { + return cache.samlIdPServiceProvidersCache + } + return cache.Config.SAMLIdPServiceProviders +} + +type samlIdPServiceProviderGetter interface { + ListSAMLIdPServiceProviders(context.Context, int, string) ([]types.SAMLIdPServiceProvider, string, error) + GetSAMLIdPServiceProvider(ctx context.Context, name string) (types.SAMLIdPServiceProvider, error) +} + +var _ executor[types.SAMLIdPServiceProvider, samlIdPServiceProviderGetter] = samlIdPServiceProvidersExecutor{} + +type userGroupsExecutor struct{} + +func (userGroupsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.UserGroup, error) { + var ( + startKey string + resources []types.UserGroup + ) + for { + var userGroups []types.UserGroup + var err error + userGroups, startKey, err = cache.UserGroups.ListUserGroups(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, userGroups...) + + if startKey == "" { + break + } + } + + return resources, nil +} + +func (userGroupsExecutor) upsert(ctx context.Context, cache *Cache, resource types.UserGroup) error { + err := cache.userGroupsCache.CreateUserGroup(ctx, resource) + if trace.IsAlreadyExists(err) { + err = cache.userGroupsCache.UpdateUserGroup(ctx, resource) + } + return trace.Wrap(err) +} + +func (userGroupsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.userGroupsCache.DeleteAllUserGroups(ctx) +} + +func (userGroupsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.userGroupsCache.DeleteUserGroup(ctx, resource.GetName()) +} + +func (userGroupsExecutor) isSingleton() bool { return false } + +func (userGroupsExecutor) getReader(cache *Cache, cacheOK bool) userGroupGetter { + if cacheOK { + return cache.userGroupsCache + } + return cache.Config.UserGroups +} + +type userGroupGetter interface { + GetUserGroup(ctx context.Context, name string) (types.UserGroup, error) + ListUserGroups(context.Context, int, string) ([]types.UserGroup, string, error) +} + +var _ executor[types.UserGroup, userGroupGetter] = userGroupsExecutor{} + +type oktaImportRulesExecutor struct{} + +func (oktaImportRulesExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.OktaImportRule, error) { + var ( + startKey string + resources []types.OktaImportRule + ) + for { + var importRules []types.OktaImportRule + var err error + importRules, startKey, err = cache.Okta.ListOktaImportRules(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, importRules...) + + if startKey == "" { + break + } + } + + return resources, nil +} + +func (oktaImportRulesExecutor) upsert(ctx context.Context, cache *Cache, resource types.OktaImportRule) error { + _, err := cache.oktaCache.CreateOktaImportRule(ctx, resource) + if trace.IsAlreadyExists(err) { + _, err = cache.oktaCache.UpdateOktaImportRule(ctx, resource) + } + return trace.Wrap(err) +} + +func (oktaImportRulesExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.oktaCache.DeleteAllOktaImportRules(ctx) +} + +func (oktaImportRulesExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.oktaCache.DeleteOktaImportRule(ctx, resource.GetName()) +} + +func (oktaImportRulesExecutor) isSingleton() bool { return false } + +func (oktaImportRulesExecutor) getReader(cache *Cache, cacheOK bool) oktaImportRuleGetter { + if cacheOK { + return cache.oktaCache + } + return cache.Config.Okta +} + +type oktaImportRuleGetter interface { + ListOktaImportRules(context.Context, int, string) ([]types.OktaImportRule, string, error) + GetOktaImportRule(ctx context.Context, name string) (types.OktaImportRule, error) +} + +var _ executor[types.OktaImportRule, oktaImportRuleGetter] = oktaImportRulesExecutor{} + +type oktaAssignmentsExecutor struct{} + +func (oktaAssignmentsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.OktaAssignment, error) { + var ( + startKey string + resources []types.OktaAssignment + ) + for { + var assignments []types.OktaAssignment + var err error + assignments, startKey, err = cache.Okta.ListOktaAssignments(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, assignments...) + + if startKey == "" { + break + } + } + + return resources, nil +} + +func (oktaAssignmentsExecutor) upsert(ctx context.Context, cache *Cache, resource types.OktaAssignment) error { + _, err := cache.oktaCache.CreateOktaAssignment(ctx, resource) + if trace.IsAlreadyExists(err) { + _, err = cache.oktaCache.UpdateOktaAssignment(ctx, resource) + } + return trace.Wrap(err) +} + +func (oktaAssignmentsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.oktaCache.DeleteAllOktaAssignments(ctx) +} + +func (oktaAssignmentsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.oktaCache.DeleteOktaAssignment(ctx, resource.GetName()) +} + +func (oktaAssignmentsExecutor) isSingleton() bool { return false } + +func (oktaAssignmentsExecutor) getReader(cache *Cache, cacheOK bool) oktaAssignmentGetter { + if cacheOK { + return cache.oktaCache + } + return cache.Config.Okta +} + +type oktaAssignmentGetter interface { + GetOktaAssignment(ctx context.Context, name string) (types.OktaAssignment, error) + ListOktaAssignments(context.Context, int, string) ([]types.OktaAssignment, string, error) +} + +var _ executor[types.OktaAssignment, oktaAssignmentGetter] = oktaAssignmentsExecutor{} + +// collectionReader extends the collection interface, adding routing capabilities. +type collectionReader[R any] interface { + legacyCollection + + // getReader returns the appropriate reader type T based on the health status of the cache. + // Reader type R provides getter methods related to the collection, e.g. GetNodes(), GetRoles(). + // Note that cacheOK set to true means that cache is overall healthy and the collection was confirmed as supported. + getReader(cacheOK bool) R +} + +type resourceGetter interface { + ListResources(ctx context.Context, req proto.ListResourcesRequest) (*types.ListResourcesResponse, error) +} + +type integrationsExecutor struct{} + +func (integrationsExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]types.Integration, error) { + var ( + startKey string + resources []types.Integration + ) + for { + var igs []types.Integration + var err error + igs, startKey, err = cache.Integrations.ListIntegrations(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, igs...) + + if startKey == "" { + break + } + } + + return resources, nil +} + +func (integrationsExecutor) upsert(ctx context.Context, cache *Cache, resource types.Integration) error { + _, err := cache.integrationsCache.CreateIntegration(ctx, resource) + if trace.IsAlreadyExists(err) { + _, err = cache.integrationsCache.UpdateIntegration(ctx, resource) + } + return trace.Wrap(err) +} + +func (integrationsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.integrationsCache.DeleteAllIntegrations(ctx) +} + +func (integrationsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.integrationsCache.DeleteIntegration(ctx, resource.GetName()) +} + +func (integrationsExecutor) isSingleton() bool { return false } + +func (integrationsExecutor) getReader(cache *Cache, cacheOK bool) services.IntegrationsGetter { + if cacheOK { + return cache.integrationsCache + } + return cache.Config.Integrations +} + +var _ executor[types.Integration, services.IntegrationsGetter] = integrationsExecutor{} + +type discoveryConfigExecutor struct{} + +func (discoveryConfigExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*discoveryconfig.DiscoveryConfig, error) { + var discoveryConfigs []*discoveryconfig.DiscoveryConfig + var nextToken string + for { + var page []*discoveryconfig.DiscoveryConfig + var err error + + page, nextToken, err = cache.DiscoveryConfigs.ListDiscoveryConfigs(ctx, 0 /* default page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + + discoveryConfigs = append(discoveryConfigs, page...) + + if nextToken == "" { + break + } + } + return discoveryConfigs, nil +} + +func (discoveryConfigExecutor) upsert(ctx context.Context, cache *Cache, resource *discoveryconfig.DiscoveryConfig) error { + _, err := cache.discoveryConfigsCache.UpsertDiscoveryConfig(ctx, resource) + return trace.Wrap(err) +} + +func (discoveryConfigExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.discoveryConfigsCache.DeleteAllDiscoveryConfigs(ctx) +} + +func (discoveryConfigExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.discoveryConfigsCache.DeleteDiscoveryConfig(ctx, resource.GetName()) +} + +func (discoveryConfigExecutor) isSingleton() bool { return false } + +func (discoveryConfigExecutor) getReader(cache *Cache, cacheOK bool) services.DiscoveryConfigsGetter { + if cacheOK { + return cache.discoveryConfigsCache + } + return cache.Config.DiscoveryConfigs +} + +var _ executor[*discoveryconfig.DiscoveryConfig, services.DiscoveryConfigsGetter] = discoveryConfigExecutor{} + +type auditQueryExecutor struct{} + +func (auditQueryExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*secreports.AuditQuery, error) { + var out []*secreports.AuditQuery + var nextToken string + for { + var page []*secreports.AuditQuery + var err error + + page, nextToken, err = cache.secReportsCache.ListSecurityAuditQueries(ctx, 0 /* default page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + out = append(out, page...) + if nextToken == "" { + break + } + } + return out, nil +} + +func (auditQueryExecutor) upsert(ctx context.Context, cache *Cache, resource *secreports.AuditQuery) error { + err := cache.secReportsCache.UpsertSecurityAuditQuery(ctx, resource) + return trace.Wrap(err) +} + +func (auditQueryExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.secReportsCache.DeleteAllSecurityReports(ctx)) +} + +func (auditQueryExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return trace.Wrap(cache.secReportsCache.DeleteSecurityAuditQuery(ctx, resource.GetName())) +} + +func (auditQueryExecutor) isSingleton() bool { return false } + +func (auditQueryExecutor) getReader(cache *Cache, cacheOK bool) services.SecurityAuditQueryGetter { + if cacheOK { + return cache.secReportsCache + } + return cache.Config.SecReports +} + +var _ executor[*secreports.AuditQuery, services.SecurityAuditQueryGetter] = auditQueryExecutor{} + +type secReportExecutor struct{} + +func (secReportExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*secreports.Report, error) { + var out []*secreports.Report + var nextToken string + for { + var page []*secreports.Report + var err error + + page, nextToken, err = cache.secReportsCache.ListSecurityReports(ctx, 0 /* default page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + out = append(out, page...) + if nextToken == "" { + break + } + } + return out, nil +} + +func (secReportExecutor) upsert(ctx context.Context, cache *Cache, resource *secreports.Report) error { + err := cache.secReportsCache.UpsertSecurityReport(ctx, resource) + return trace.Wrap(err) +} + +func (secReportExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.secReportsCache.DeleteAllSecurityReports(ctx)) +} + +func (secReportExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return trace.Wrap(cache.secReportsCache.DeleteSecurityReport(ctx, resource.GetName())) +} + +func (secReportExecutor) isSingleton() bool { return false } + +func (secReportExecutor) getReader(cache *Cache, cacheOK bool) services.SecurityReportGetter { + if cacheOK { + return cache.secReportsCache + } + return cache.Config.SecReports +} + +var _ executor[*secreports.Report, services.SecurityReportGetter] = secReportExecutor{} + +type secReportStateExecutor struct{} + +func (secReportStateExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*secreports.ReportState, error) { + var out []*secreports.ReportState + var nextToken string + for { + var page []*secreports.ReportState + var err error + + page, nextToken, err = cache.secReportsCache.ListSecurityReportsStates(ctx, 0 /* default page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + out = append(out, page...) + if nextToken == "" { + break + } + } + return out, nil +} + +func (secReportStateExecutor) upsert(ctx context.Context, cache *Cache, resource *secreports.ReportState) error { + err := cache.secReportsCache.UpsertSecurityReportsState(ctx, resource) + return trace.Wrap(err) +} + +func (secReportStateExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.secReportsCache.DeleteAllSecurityReportsStates(ctx)) +} + +func (secReportStateExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return trace.Wrap(cache.secReportsCache.DeleteSecurityReportsState(ctx, resource.GetName())) +} + +func (secReportStateExecutor) isSingleton() bool { return false } + +func (secReportStateExecutor) getReader(cache *Cache, cacheOK bool) services.SecurityReportStateGetter { + if cacheOK { + return cache.secReportsCache + } + return cache.Config.SecReports +} + +var _ executor[*secreports.ReportState, services.SecurityReportStateGetter] = secReportStateExecutor{} + +type userLoginStateExecutor struct{} + +func (userLoginStateExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*userloginstate.UserLoginState, error) { + resources, err := cache.UserLoginStates.GetUserLoginStates(ctx) + return resources, trace.Wrap(err) +} + +func (userLoginStateExecutor) upsert(ctx context.Context, cache *Cache, resource *userloginstate.UserLoginState) error { + _, err := cache.userLoginStateCache.UpsertUserLoginState(ctx, resource) + return trace.Wrap(err) +} + +func (userLoginStateExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.userLoginStateCache.DeleteAllUserLoginStates(ctx) +} + +func (userLoginStateExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.userLoginStateCache.DeleteUserLoginState(ctx, resource.GetName()) +} + +func (userLoginStateExecutor) isSingleton() bool { return false } + +func (userLoginStateExecutor) getReader(cache *Cache, cacheOK bool) services.UserLoginStatesGetter { + if cacheOK { + return cache.userLoginStateCache + } + return cache.Config.UserLoginStates +} + +var _ executor[*userloginstate.UserLoginState, services.UserLoginStatesGetter] = userLoginStateExecutor{} + +type accessListExecutor struct{} + +func (accessListExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accesslist.AccessList, error) { + var resources []*accesslist.AccessList + var nextToken string + for { + var page []*accesslist.AccessList + var err error + page, nextToken, err = cache.AccessLists.ListAccessLists(ctx, 0 /* page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, page...) + + if nextToken == "" { + break + } + } + return resources, nil +} + +func (accessListExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.AccessList) error { + _, err := cache.accessListCache.UnconditionalUpsertAccessList(ctx, resource) + return trace.Wrap(err) +} + +func (accessListExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.accessListCache.DeleteAllAccessLists(ctx) +} + +func (accessListExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.accessListCache.UnconditionalDeleteAccessList(ctx, resource.GetName()) +} + +func (accessListExecutor) isSingleton() bool { return false } + +func (accessListExecutor) getReader(cache *Cache, cacheOK bool) accessListsGetter { + if cacheOK { + return cache.accessListCache + } + return cache.Config.AccessLists +} + +type accessListsGetter interface { + GetAccessLists(ctx context.Context) ([]*accesslist.AccessList, error) + ListAccessLists(ctx context.Context, pageSize int, nextToken string) ([]*accesslist.AccessList, string, error) + GetAccessList(ctx context.Context, name string) (*accesslist.AccessList, error) +} + +type accessListMemberExecutor struct{} + +func (accessListMemberExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accesslist.AccessListMember, error) { + var resources []*accesslist.AccessListMember + var nextToken string + for { + var page []*accesslist.AccessListMember + var err error + page, nextToken, err = cache.AccessLists.ListAllAccessListMembers(ctx, 0 /* page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, page...) + + if nextToken == "" { + break + } + } + return resources, nil +} + +func (accessListMemberExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.AccessListMember) error { + _, err := cache.accessListCache.UnconditionalUpsertAccessListMember(ctx, resource) + return trace.Wrap(err) +} + +func (accessListMemberExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.accessListCache.DeleteAllAccessListMembers(ctx) +} + +func (accessListMemberExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.accessListCache.UnconditionalDeleteAccessListMember(ctx, + resource.GetMetadata().Description, // Cache passes access ID via description field. + resource.GetName()) +} + +func (accessListMemberExecutor) isSingleton() bool { return false } + +func (accessListMemberExecutor) getReader(cache *Cache, cacheOK bool) accessListMembersGetter { + if cacheOK { + return cache.accessListCache + } + return cache.Config.AccessLists +} + +type accessListMembersGetter interface { + CountAccessListMembers(ctx context.Context, accessListName string) (uint32, uint32, error) + ListAccessListMembers(ctx context.Context, accessListName string, pageSize int, nextToken string) ([]*accesslist.AccessListMember, string, error) + GetAccessListMember(ctx context.Context, accessList string, memberName string) (*accesslist.AccessListMember, error) + ListAllAccessListMembers(ctx context.Context, pageSize int, pageToken string) ([]*accesslist.AccessListMember, string, error) +} + +type accessListReviewExecutor struct{} + +func (accessListReviewExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accesslist.Review, error) { + var resources []*accesslist.Review + var nextToken string + for { + var page []*accesslist.Review + var err error + page, nextToken, err = cache.AccessLists.ListAllAccessListReviews(ctx, 0 /* page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, page...) + + if nextToken == "" { + break + } + } + return resources, nil +} + +func (accessListReviewExecutor) upsert(ctx context.Context, cache *Cache, resource *accesslist.Review) error { + if _, _, err := cache.accessListCache.CreateAccessListReview(ctx, resource); err != nil { + if !trace.IsAlreadyExists(err) { + return trace.Wrap(err) + } + + if err := cache.accessListCache.DeleteAccessListReview(ctx, resource.Spec.AccessList, resource.GetName()); err != nil { + return trace.Wrap(err) + } + + if _, _, err := cache.accessListCache.CreateAccessListReview(ctx, resource); err != nil { + return trace.Wrap(err) + } + } + return nil +} + +func (accessListReviewExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.accessListCache.DeleteAllAccessListReviews(ctx) +} + +func (accessListReviewExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.accessListCache.DeleteAccessListReview(ctx, + resource.GetMetadata().Description, // Cache passes access ID via description field. + resource.GetName()) +} + +func (accessListReviewExecutor) isSingleton() bool { return false } + +func (accessListReviewExecutor) getReader(cache *Cache, cacheOK bool) accessListReviewsGetter { + if cacheOK { + return cache.accessListCache + } + return cache.Config.AccessLists +} + +type accessListReviewsGetter interface { + ListAccessListReviews(ctx context.Context, accessList string, pageSize int, pageToken string) (reviews []*accesslist.Review, nextToken string, err error) +} + +type notificationGetter interface { + ListUserNotifications(ctx context.Context, pageSize int, startKey string) ([]*notificationsv1.Notification, string, error) + ListGlobalNotifications(ctx context.Context, pageSize int, startKey string) ([]*notificationsv1.GlobalNotification, string, error) +} + +type userNotificationExecutor struct{} + +func (userNotificationExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*notificationsv1.Notification, error) { + var notifications []*notificationsv1.Notification + var startKey string + for { + notifs, nextKey, err := cache.notificationsCache.ListUserNotifications(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + notifications = append(notifications, notifs...) + + if nextKey == "" { + break + } + startKey = nextKey + } + + return notifications, nil +} + +func (userNotificationExecutor) upsert(ctx context.Context, cache *Cache, notification *notificationsv1.Notification) error { + _, err := cache.notificationsCache.UpsertUserNotification(ctx, notification) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + +func (userNotificationExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.notificationsCache.DeleteAllUserNotifications(ctx) +} + +func (userNotificationExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + r, ok := resource.(types.Resource153Unwrapper) + if !ok { + return trace.BadParameter("unknown resource type, expected types.Resource153Unwrapper, got %T", resource) + } + + notification, ok := r.Unwrap().(*notificationsv1.Notification) + if !ok { + return trace.BadParameter("unknown Notification type, expected *notificationsv1.Notification, got %T", resource) + } + + username := notification.GetSpec().GetUsername() + notificationId := notification.GetMetadata().GetName() + + err := cache.notificationsCache.DeleteUserNotification(ctx, username, notificationId) + return trace.Wrap(err) +} + +func (userNotificationExecutor) isSingleton() bool { return false } + +func (userNotificationExecutor) getReader(cache *Cache, cacheOK bool) notificationGetter { + if cacheOK { + return cache.notificationsCache + } + return cache.Config.Notifications +} + +var _ executor[*notificationsv1.Notification, notificationGetter] = userNotificationExecutor{} + +type globalNotificationExecutor struct{} + +func (globalNotificationExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*notificationsv1.GlobalNotification, error) { + var notifications []*notificationsv1.GlobalNotification + var startKey string + for { + notifs, nextKey, err := cache.notificationsCache.ListGlobalNotifications(ctx, 0, startKey) + if err != nil { + return nil, trace.Wrap(err) + } + + notifications = append(notifications, notifs...) + + if nextKey == "" { + break + } + startKey = nextKey + } + + return notifications, nil +} + +func (globalNotificationExecutor) upsert(ctx context.Context, cache *Cache, notification *notificationsv1.GlobalNotification) error { + if _, err := cache.notificationsCache.UpsertGlobalNotification(ctx, notification); err != nil { + return trace.Wrap(err) + } + + return nil +} + +func (globalNotificationExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.notificationsCache.DeleteAllGlobalNotifications(ctx) +} + +func (globalNotificationExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + + r, ok := resource.(types.Resource153Unwrapper) + if !ok { + return trace.BadParameter("unknown resource type, expected types.Resource153Unwrapper, got %T", resource) + } + + globalNotification, ok := r.Unwrap().(*notificationsv1.GlobalNotification) + if !ok { + return trace.BadParameter("unknown Notification type, expected *notificationsv1.GlobalNotification, got %T", resource) + } + + notificationId := globalNotification.GetMetadata().GetName() + + err := cache.notificationsCache.DeleteGlobalNotification(ctx, notificationId) + return trace.Wrap(err) +} + +func (globalNotificationExecutor) isSingleton() bool { return false } + +func (globalNotificationExecutor) getReader(cache *Cache, cacheOK bool) notificationGetter { + if cacheOK { + return cache.notificationsCache + } + return cache.Config.Notifications +} + +var _ executor[*notificationsv1.GlobalNotification, notificationGetter] = globalNotificationExecutor{} + +type accessMonitoringRulesExecutor struct{} + +func (accessMonitoringRulesExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*accessmonitoringrulesv1.AccessMonitoringRule, error) { + var resources []*accessmonitoringrulesv1.AccessMonitoringRule + var nextToken string + for { + var page []*accessmonitoringrulesv1.AccessMonitoringRule + var err error + page, nextToken, err = cache.AccessMonitoringRules.ListAccessMonitoringRules(ctx, 0 /* page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + resources = append(resources, page...) + + if nextToken == "" { + break + } + } + return resources, nil +} + +func (accessMonitoringRulesExecutor) upsert(ctx context.Context, cache *Cache, resource *accessmonitoringrulesv1.AccessMonitoringRule) error { + _, err := cache.accessMontoringRuleCache.UpsertAccessMonitoringRule(ctx, resource) + return trace.Wrap(err) +} + +func (accessMonitoringRulesExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return cache.accessMontoringRuleCache.DeleteAllAccessMonitoringRules(ctx) +} + +func (accessMonitoringRulesExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return cache.accessMontoringRuleCache.DeleteAccessMonitoringRule(ctx, resource.GetName()) +} + +func (accessMonitoringRulesExecutor) isSingleton() bool { return false } + +func (accessMonitoringRulesExecutor) getReader(cache *Cache, cacheOK bool) accessMonitoringRuleGetter { + if cacheOK { + return cache.accessMontoringRuleCache + } + return cache.Config.AccessMonitoringRules +} + +type accessMonitoringRuleGetter interface { + GetAccessMonitoringRule(ctx context.Context, name string) (*accessmonitoringrulesv1.AccessMonitoringRule, error) + ListAccessMonitoringRules(ctx context.Context, limit int, startKey string) ([]*accessmonitoringrulesv1.AccessMonitoringRule, string, error) + ListAccessMonitoringRulesWithFilter(ctx context.Context, pageSize int, nextToken string, subjects []string, notificationName string) ([]*accessmonitoringrulesv1.AccessMonitoringRule, string, error) +} + +type accessGraphSettingsExecutor struct{} + +func (accessGraphSettingsExecutor) getAll(ctx context.Context, cache *Cache, _ bool) ([]*clusterconfigpb.AccessGraphSettings, error) { + set, err := cache.ClusterConfig.GetAccessGraphSettings(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + + return []*clusterconfigpb.AccessGraphSettings{set}, nil +} + +func (accessGraphSettingsExecutor) upsert(ctx context.Context, cache *Cache, resource *clusterconfigpb.AccessGraphSettings) error { + _, err := cache.clusterConfigCache.UpsertAccessGraphSettings(ctx, resource) + return trace.Wrap(err) +} + +func (accessGraphSettingsExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.clusterConfigCache.DeleteAccessGraphSettings(ctx)) +} + +func (accessGraphSettingsExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return trace.Wrap(cache.clusterConfigCache.DeleteAccessGraphSettings(ctx)) +} + +func (accessGraphSettingsExecutor) isSingleton() bool { return false } + +func (accessGraphSettingsExecutor) getReader(cache *Cache, cacheOK bool) accessGraphSettingsGetter { + if cacheOK { + return cache.clusterConfigCache + } + return cache.Config.ClusterConfig +} + +type accessGraphSettingsGetter interface { + GetAccessGraphSettings(context.Context) (*clusterconfigpb.AccessGraphSettings, error) +} + +var _ executor[*clusterconfigpb.AccessGraphSettings, accessGraphSettingsGetter] = accessGraphSettingsExecutor{} diff --git a/lib/cache/plugin_static_credentials.go b/lib/cache/plugin_static_credentials.go index a756eb419ce35..a6cb1ee161a81 100644 --- a/lib/cache/plugin_static_credentials.go +++ b/lib/cache/plugin_static_credentials.go @@ -28,7 +28,7 @@ func (c *Cache) GetPluginStaticCredentials(ctx context.Context, name string) (ty ctx, span := c.Tracer.Start(ctx, "cache/GetPluginStaticCredentials") defer span.End() - rg, err := readCollectionCache(c, c.collections.pluginStaticCredentials) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.pluginStaticCredentials) if err != nil { return nil, trace.Wrap(err) } @@ -40,7 +40,7 @@ func (c *Cache) GetPluginStaticCredentialsByLabels(ctx context.Context, labels m ctx, span := c.Tracer.Start(ctx, "cache/GetPluginStaticCredentialsByLabels") defer span.End() - rg, err := readCollectionCache(c, c.collections.pluginStaticCredentials) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.pluginStaticCredentials) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/cache/resource_reverse_tunnel.go b/lib/cache/resource_reverse_tunnel.go index 34e963ca970c0..799750a8467e3 100644 --- a/lib/cache/resource_reverse_tunnel.go +++ b/lib/cache/resource_reverse_tunnel.go @@ -31,7 +31,7 @@ func (c *Cache) GetReverseTunnels(ctx context.Context) ([]types.ReverseTunnel, e ctx, span := c.Tracer.Start(ctx, "cache/GetReverseTunnels") defer span.End() - rg, err := readCollectionCache(c, c.collections.reverseTunnels) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.reverseTunnels) if err != nil { return nil, trace.Wrap(err) } @@ -44,7 +44,7 @@ func (c *Cache) ListReverseTunnels(ctx context.Context, pageSize int, pageToken ctx, span := c.Tracer.Start(ctx, "cache/ListReverseTunnels") defer span.End() - rg, err := readCollectionCache(c, c.collections.reverseTunnels) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.reverseTunnels) if err != nil { return nil, "", trace.Wrap(err) } diff --git a/lib/cache/resource_spiffe_federation.go b/lib/cache/resource_spiffe_federation.go index 8e6a1b38adb10..9c47e3943fefb 100644 --- a/lib/cache/resource_spiffe_federation.go +++ b/lib/cache/resource_spiffe_federation.go @@ -94,7 +94,7 @@ func (c *Cache) ListSPIFFEFederations(ctx context.Context, pageSize int, nextTok ctx, span := c.Tracer.Start(ctx, "cache/ListSPIFFEFederations") defer span.End() - rg, err := readCollectionCache(c, c.collections.spiffeFederations) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.spiffeFederations) if err != nil { return nil, "", trace.Wrap(err) } @@ -108,7 +108,7 @@ func (c *Cache) GetSPIFFEFederation(ctx context.Context, name string) (*machinei ctx, span := c.Tracer.Start(ctx, "cache/GetSPIFFEFederation") defer span.End() - rg, err := readCollectionCache(c, c.collections.spiffeFederations) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.spiffeFederations) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/cache/resource_workload_identity.go b/lib/cache/resource_workload_identity.go index 75efb50fedbd5..fe29e09a30b05 100644 --- a/lib/cache/resource_workload_identity.go +++ b/lib/cache/resource_workload_identity.go @@ -95,7 +95,7 @@ func (c *Cache) ListWorkloadIdentities(ctx context.Context, pageSize int, nextTo ctx, span := c.Tracer.Start(ctx, "cache/ListWorkloadIdentities") defer span.End() - rg, err := readCollectionCache(c, c.collections.workloadIdentity) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.workloadIdentity) if err != nil { return nil, "", trace.Wrap(err) } @@ -109,7 +109,7 @@ func (c *Cache) GetWorkloadIdentity(ctx context.Context, name string) (*workload ctx, span := c.Tracer.Start(ctx, "cache/GetWorkloadIdentity") defer span.End() - rg, err := readCollectionCache(c, c.collections.workloadIdentity) + rg, err := readLegacyCollectionCache(c, c.legacyCacheCollections.workloadIdentity) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/cache/store.go b/lib/cache/store.go new file mode 100644 index 0000000000000..a605ab75c21d5 --- /dev/null +++ b/lib/cache/store.go @@ -0,0 +1,103 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cache + +import ( + "iter" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/lib/utils/sortcache" +) + +// store persists cached resources directly in memory. +type store[T any, I comparable] struct { + cache *sortcache.SortCache[T, I] + clone func(T) T + indexes map[I]func(T) string +} + +// newStore creates a store that will index the resource +// based on the provided indexes. +func newStore[T any, I comparable](clone func(T) T, indexes map[I]func(T) string) *store[T, I] { + return &store[T, I]{ + clone: clone, + indexes: indexes, + cache: sortcache.New(sortcache.Config[T, I]{ + Indexes: indexes, + }), + } +} + +// clear removes all items from the store. +func (s *store[T, I]) clear() error { + s.cache.Clear() + return nil +} + +// put adds a new item, or updates an existing item. +func (s *store[T, I]) put(t T) error { + s.cache.Put(s.clone(t)) + return nil +} + +// delete removes the provided item if any of the indexes match. +func (s *store[T, I]) delete(t T) error { + for idx, transform := range s.indexes { + s.cache.Delete(idx, transform(t)) + } + + return nil +} + +// len returns the number of values currently stored. +func (s *store[T, I]) len() int { + return s.cache.Len() +} + +// get returns the item matching the provided index and item, +// or a [trace.NotFoundError] if no match was found. +// +// It is the responsibility of the caller to clone the resource +// before propagating it further. +func (s *store[T, I]) get(index I, key string) (T, error) { + t, ok := s.cache.Get(index, key) + if !ok { + return t, trace.NotFound("no value for key %q in index %v", key, index) + } + + return t, nil +} + +// resources returns an iterator over all items in the provided range +// for the given index in ascending order. +// +// It is the responsibility of the caller to clone the resource +// before propagating it further. +func (s *store[T, I]) resources(index I, start, stop string) iter.Seq[T] { + return s.cache.Ascend(index, start, stop) +} + +// count returns the number of items that exist in the provided range. +func (s *store[T, I]) count(index I, start, stop string) int { + var n int + for range s.cache.Ascend(index, start, stop) { + n++ + } + + return n +} diff --git a/lib/cache/store_test.go b/lib/cache/store_test.go new file mode 100644 index 0000000000000..0a5d956f9d65c --- /dev/null +++ b/lib/cache/store_test.go @@ -0,0 +1,71 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package cache + +import ( + "slices" + "strconv" + "testing" + + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" +) + +func TestResourceStore(t *testing.T) { + store := newStore( + func(i int) int { return i }, + map[string]func(i int) string{ + "numbers": strconv.Itoa, + "characters": func(i int) string { return strconv.FormatUint(uint64(i), 16) }, + }) + + for i := 0; i < 100; i++ { + require.NoError(t, store.put(i)) + } + require.Equal(t, 100, store.len()) + + zero, err := store.get("numbers", "0") + require.NoError(t, err) + require.Equal(t, 0, zero) + + n, err := store.get("numbers", "1000") + require.ErrorIs(t, err, &trace.NotFoundError{Message: `no value for key "1000" in index numbers`}) + require.Equal(t, 0, n) + require.Equal(t, 2, store.count("numbers", "1", "100")) + + v, err := store.get("characters", "1c") + require.NoError(t, err) + require.Equal(t, 28, v) + + out := slices.Collect(store.resources("numbers", "", "")) + require.Len(t, out, 100) + + out = slices.Collect(store.resources("characters", "", "")) + require.Len(t, out, 100) + + require.NoError(t, store.delete(0)) + _, err = store.get("numbers", "0") + require.ErrorIs(t, err, &trace.NotFoundError{Message: `no value for key "0" in index numbers`}) + + require.NoError(t, store.clear()) + + _, err = store.get("numbers", "0") + require.ErrorIs(t, err, &trace.NotFoundError{Message: `no value for key "0" in index numbers`}) + + require.Zero(t, store.len()) + require.Zero(t, store.count("numbers", "1", "100")) +}