mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-01 16:03:55 +08:00
foo added to the cache
This commit is contained in:
@@ -122,6 +122,7 @@ type Config struct {
|
||||
AppAuthConfig services.AppAuthConfigReader
|
||||
Summarizer services.Summarizer
|
||||
SubCAService services.SubCAServiceGetter
|
||||
FooUpstream services.FooUpstream
|
||||
}
|
||||
|
||||
func (c *Config) CheckAndSetDefaults() error {
|
||||
@@ -215,6 +216,7 @@ func NewCache(cfg Config) (*cache.Cache, error) {
|
||||
AppAuthConfig: cfg.AppAuthConfig,
|
||||
Summarizer: cfg.Summarizer,
|
||||
SubCAService: cfg.SubCAService,
|
||||
FooUpstream: cfg.FooUpstream,
|
||||
}
|
||||
|
||||
return cache.New(cfg.Setup(cacheCfg))
|
||||
|
||||
@@ -686,6 +686,13 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (as *Server, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.FooService == nil {
|
||||
cfg.FooService, err = local.NewFooService(cfg.Backend)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating FooService")
|
||||
}
|
||||
}
|
||||
|
||||
services := &Services{
|
||||
TrustInternal: cfg.Trust,
|
||||
PresenceInternal: cfg.Presence,
|
||||
@@ -753,6 +760,7 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (as *Server, err error) {
|
||||
BeamsConfigService: cfg.BeamsConfigService,
|
||||
SubCAService: cfg.SubCAService,
|
||||
EnrollPairing: cfg.EnrollPairing,
|
||||
FooService: cfg.FooService,
|
||||
}
|
||||
|
||||
if cfg.FakePasswordHash == nil {
|
||||
|
||||
@@ -1587,6 +1587,9 @@ type Cache interface {
|
||||
|
||||
// SubCAServiceGetter reads CertAuthorityOverride resources.
|
||||
services.SubCAServiceGetter
|
||||
|
||||
// FooReader reads foo resources.
|
||||
services.FooReader
|
||||
}
|
||||
|
||||
type NodeWrapper struct {
|
||||
|
||||
@@ -658,6 +658,7 @@ func InitAuthCache(p AuthCacheParams) error {
|
||||
StaticScopedToken: p.AuthServer.Services.ClusterConfigurationInternal,
|
||||
Summarizer: p.AuthServer.Services.Summarizer,
|
||||
SubCAService: p.AuthServer.Services.SubCAService,
|
||||
FooUpstream: p.AuthServer.Services.FooService,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
||||
@@ -6288,14 +6288,10 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {
|
||||
}
|
||||
scopedjoiningv1.RegisterScopedJoiningServiceServer(server, scopedJoining)
|
||||
|
||||
fooStorageService, err := local.NewFooService(cfg.AuthServer.bk)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "creating foo service")
|
||||
}
|
||||
fooService := foo.NewService(&foo.Config{
|
||||
ScopedAuthorizer: cfg.ScopedAuthorizer,
|
||||
Reader: fooStorageService,
|
||||
Writer: fooStorageService,
|
||||
Reader: cfg.AuthServer.Cache,
|
||||
Writer: cfg.AuthServer.FooService,
|
||||
})
|
||||
foov1.RegisterFooServiceServer(server, fooService)
|
||||
|
||||
|
||||
@@ -482,6 +482,9 @@ type InitConfig struct {
|
||||
|
||||
// EnrollPairing manages mobile device enrollment pairings.
|
||||
EnrollPairing services.EnrollPairing
|
||||
|
||||
// FooService is a service for interacting with Foo resources.
|
||||
FooService services.FooService
|
||||
}
|
||||
|
||||
// Init instantiates and configures an instance of AuthServer
|
||||
|
||||
@@ -102,6 +102,7 @@ type Services struct {
|
||||
services.BeamsConfigService
|
||||
services.SubCAService
|
||||
services.EnrollPairing
|
||||
services.FooService
|
||||
}
|
||||
|
||||
// MFAService defines the interface for managing MFA resources in the backend.
|
||||
|
||||
Vendored
+4
@@ -47,6 +47,7 @@ import (
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/backend/backendmetrics"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/foos"
|
||||
"github.com/gravitational/teleport/lib/observability/metrics"
|
||||
"github.com/gravitational/teleport/lib/observability/tracing"
|
||||
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
|
||||
@@ -228,6 +229,7 @@ func ForAuth(cfg Config) Config {
|
||||
{Kind: types.KindClassifier},
|
||||
{Kind: types.KindRetrievalModel},
|
||||
{Kind: types.KindValidatedMFAChallenge},
|
||||
{Kind: foos.Kind},
|
||||
}
|
||||
cfg.QueueSize = defaults.AuthQueueSize
|
||||
// We don't want to enable partial health for auth cache because auth uses an event stream
|
||||
@@ -849,6 +851,8 @@ type Config struct {
|
||||
Summarizer services.Summarizer
|
||||
// SubCAService reads CertAuthorityOverride resources.
|
||||
SubCAService services.SubCAServiceGetter
|
||||
// FooUpstream reads foos.
|
||||
FooUpstream services.FooUpstream
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults checks parameters and sets default values
|
||||
|
||||
Vendored
+7
@@ -187,6 +187,7 @@ type testPack struct {
|
||||
appAuthConfigs *local.AppAuthConfigService
|
||||
summarizer *local.SummarizerService
|
||||
subCA *local.SubCAService
|
||||
foos *local.FooService
|
||||
}
|
||||
|
||||
// resourceOps contains helpers to modify the state of either types.Resource or types.Resource153 which
|
||||
@@ -563,6 +564,11 @@ func newPackWithoutCache(dir string, opts ...packOption) (*testPack, error) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
p.foos, err = local.NewFooService(p.backend)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -632,6 +638,7 @@ func newPack(t testing.TB, setupConfig func(c Config) Config, opts ...packOption
|
||||
StaticScopedToken: p.clusterConfigS,
|
||||
Summarizer: p.summarizer,
|
||||
SubCAService: p.subCA,
|
||||
FooUpstream: p.foos,
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
|
||||
Vendored
+10
@@ -29,6 +29,7 @@ import (
|
||||
clusterconfigv1 "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"
|
||||
foov1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/foo/v1"
|
||||
healthcheckconfigv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/healthcheckconfig/v1"
|
||||
identitycenterv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/identitycenter/v1"
|
||||
kubewaitingcontainerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1"
|
||||
@@ -49,6 +50,7 @@ import (
|
||||
"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/foos"
|
||||
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
|
||||
)
|
||||
|
||||
@@ -161,6 +163,7 @@ type collections struct {
|
||||
classifiers *collection[*summarizerv1.Classifier, classifierIndex]
|
||||
retrievalModels *collection[*summarizerv1.RetrievalModel, retrievalModelIndex]
|
||||
certAuthorityOverrides *collection[*subcav1.CertAuthorityOverride, certAuthorityOverrideIndex]
|
||||
foos *collection[*foov1.Foo, fooIndex]
|
||||
}
|
||||
|
||||
// isKnownUncollectedKind is true if a resource kind is not stored in
|
||||
@@ -871,6 +874,13 @@ func setupCollections(c Config) (*collections, error) {
|
||||
}
|
||||
out.certAuthorityOverrides = collect
|
||||
out.byKind[resourceKind] = out.certAuthorityOverrides
|
||||
case foos.Kind:
|
||||
collect, err := newFooCollection(c.FooUpstream, watch)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
out.foos = collect
|
||||
out.byKind[resourceKind] = out.foos
|
||||
default:
|
||||
if _, ok := out.byKind[resourceKind]; !ok {
|
||||
return nil, trace.BadParameter("resource %q is not supported", watch.Kind)
|
||||
|
||||
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
foov1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/foo/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/clientutils"
|
||||
"github.com/gravitational/teleport/lib/foos"
|
||||
"github.com/gravitational/teleport/lib/itertools/stream"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
)
|
||||
|
||||
type fooIndex string
|
||||
|
||||
const (
|
||||
fooNameIndex fooIndex = "name"
|
||||
)
|
||||
|
||||
func fooNameIndexKey(foo *foov1.Foo) string {
|
||||
cursor, err := foos.Cursor(foo)
|
||||
if err != nil {
|
||||
// TODO: figure out how to handle fallible index key creation.
|
||||
panic(err)
|
||||
}
|
||||
return cursor
|
||||
}
|
||||
|
||||
func newFooCollection(upstream services.FooUpstream, w types.WatchKind) (*collection[*foov1.Foo, fooIndex], error) {
|
||||
if upstream == nil {
|
||||
return nil, trace.BadParameter("missing parameter FooUpstream")
|
||||
}
|
||||
|
||||
return &collection[*foov1.Foo, fooIndex]{
|
||||
store: newStore(
|
||||
foos.Kind,
|
||||
proto.CloneOf[*foov1.Foo],
|
||||
map[fooIndex]func(*foov1.Foo) string{
|
||||
// sorted by name
|
||||
fooNameIndex: fooNameIndexKey,
|
||||
}),
|
||||
fetcher: func(ctx context.Context, loadSecrets bool) ([]*foov1.Foo, error) {
|
||||
return stream.Collect(clientutils.Resources(ctx, func(ctx context.Context, pageSize int, pageToken string) ([]*foov1.Foo, string, error) {
|
||||
return upstream.ListFoos(ctx, foov1.ListFoosRequest_builder{
|
||||
PageSize: int32(pageSize),
|
||||
PageToken: pageToken,
|
||||
// TODO: propagate filter from WatchKind.
|
||||
ScopeFilter: nil,
|
||||
}.Build())
|
||||
}))
|
||||
},
|
||||
watch: w,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Cache) GetFoo(ctx context.Context, req *foov1.GetFooRequest) (*foov1.Foo, error) {
|
||||
ctx, span := c.Tracer.Start(ctx, "cache/GetFoo")
|
||||
defer span.End()
|
||||
|
||||
fooCursor, err := scopes.MakeResourceCursor(req.GetScope(), req.GetName())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
getter := genericGetter[*foov1.Foo, fooIndex]{
|
||||
cache: c,
|
||||
collection: c.collections.foos,
|
||||
index: fooNameIndex,
|
||||
upstreamGet: func(ctx context.Context, _ string) (*foov1.Foo, error) {
|
||||
return c.FooUpstream.GetFoo(ctx, req)
|
||||
},
|
||||
}
|
||||
|
||||
out, err := getter.get(ctx, fooCursor)
|
||||
return out, trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *Cache) RangeFoos(ctx context.Context, req *foov1.ListFoosRequest, startKey, endKey string) iter.Seq2[*foov1.Foo, error] {
|
||||
ctx, span := c.Tracer.Start(ctx, "cache/RangeFoos")
|
||||
defer span.End()
|
||||
|
||||
scopeFilter := req.GetScopeFilter()
|
||||
if err := scopes.ValidateFilter(scopeFilter); err != nil {
|
||||
return stream.Fail[*foov1.Foo](trace.Wrap(err))
|
||||
}
|
||||
|
||||
lister := genericLister[*foov1.Foo, fooIndex]{
|
||||
cache: c,
|
||||
collection: c.collections.foos,
|
||||
index: fooNameIndex,
|
||||
upstreamList: func(ctx context.Context, pageSize int, pageToken string) ([]*foov1.Foo, string, error) {
|
||||
return c.FooUpstream.ListFoos(ctx, foov1.ListFoosRequest_builder{
|
||||
PageSize: int32(pageSize),
|
||||
PageToken: pageToken,
|
||||
ScopeFilter: scopeFilter,
|
||||
}.Build())
|
||||
},
|
||||
filter: func(foo *foov1.Foo) bool {
|
||||
return scopes.MatchScope(scopeFilter, foo.GetScope())
|
||||
},
|
||||
nextToken: fooNameIndexKey,
|
||||
}
|
||||
|
||||
return lister.Range(ctx, startKey, endKey)
|
||||
}
|
||||
Vendored
+175
@@ -0,0 +1,175 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/testing/protocmp"
|
||||
|
||||
foov1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/foo/v1"
|
||||
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
|
||||
scopesv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/scopes/v1"
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/foos"
|
||||
"github.com/gravitational/teleport/lib/itertools/stream"
|
||||
)
|
||||
|
||||
func TestFoos(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := newTestPack(t, ForAuth)
|
||||
t.Cleanup(p.Close)
|
||||
|
||||
ctx := t.Context()
|
||||
unscoped := newFoo("foo-1", "", "unscoped")
|
||||
scoped := newFoo("foo-1", "/security", "scoped")
|
||||
|
||||
_, err := p.foos.CreateFoo(ctx, unscoped)
|
||||
require.NoError(t, err)
|
||||
_, err = p.foos.CreateFoo(ctx, scoped)
|
||||
require.NoError(t, err)
|
||||
|
||||
cmpOpts := []cmp.Option{
|
||||
protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"),
|
||||
protocmp.Transform(),
|
||||
cmpopts.EquateEmpty(),
|
||||
}
|
||||
|
||||
assertCacheFoos := func(expected []*foov1.Foo) {
|
||||
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
got, err := stream.Collect(p.cache.RangeFoos(ctx, &foov1.ListFoosRequest{}, "", ""))
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, cmp.Diff(expected, got, cmpOpts...))
|
||||
}, 2*time.Second, 10*time.Millisecond)
|
||||
}
|
||||
|
||||
assertCacheFoos([]*foov1.Foo{unscoped, scoped})
|
||||
|
||||
got, err := p.cache.GetFoo(ctx, foov1.GetFooRequest_builder{Name: "foo-1"}.Build())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(unscoped, got, cmpOpts...))
|
||||
|
||||
got, err = p.cache.GetFoo(ctx, foov1.GetFooRequest_builder{Name: "foo-1", Scope: "/security"}.Build())
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff(scoped, got, cmpOpts...))
|
||||
|
||||
scopedFoos, err := stream.Collect(p.cache.RangeFoos(ctx, foov1.ListFoosRequest_builder{
|
||||
ScopeFilter: scopesv1.Filter_builder{Scope: "/security", Mode: scopesv1.Mode_MODE_EXACT}.Build(),
|
||||
}.Build(), "", ""))
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, cmp.Diff([]*foov1.Foo{scoped}, scopedFoos, cmpOpts...))
|
||||
|
||||
require.NoError(t, p.foos.DeleteFoo(ctx, foov1.DeleteFooRequest_builder{Name: "foo-1", Scope: "/security"}.Build()))
|
||||
assertCacheFoos([]*foov1.Foo{unscoped})
|
||||
|
||||
_, err = p.cache.GetFoo(ctx, foov1.GetFooRequest_builder{Name: "foo-1", Scope: "/security"}.Build())
|
||||
require.True(t, trace.IsNotFound(err), "expected NotFound after delete, got %v", err)
|
||||
}
|
||||
|
||||
func TestFoos153(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
p := newTestPack(t, ForAuth)
|
||||
t.Cleanup(p.Close)
|
||||
|
||||
testResources153(t, p, testFuncs[*foov1.Foo]{
|
||||
newResource: func(name string) (*foov1.Foo, error) {
|
||||
return newFoo(name, "", name), nil
|
||||
},
|
||||
create: func(ctx context.Context, foo *foov1.Foo) error {
|
||||
_, err := p.foos.CreateFoo(ctx, foo)
|
||||
return err
|
||||
},
|
||||
list: func(ctx context.Context, pageSize int, pageToken string) ([]*foov1.Foo, string, error) {
|
||||
return p.foos.ListFoos(ctx, foov1.ListFoosRequest_builder{
|
||||
PageSize: int32(pageSize),
|
||||
PageToken: pageToken,
|
||||
}.Build())
|
||||
},
|
||||
cacheGet: func(ctx context.Context, name string) (*foov1.Foo, error) {
|
||||
return p.cache.GetFoo(ctx, foov1.GetFooRequest_builder{
|
||||
Name: name,
|
||||
}.Build())
|
||||
},
|
||||
cacheList: func(ctx context.Context, pageSize int, pageToken string) ([]*foov1.Foo, string, error) {
|
||||
var out []*foov1.Foo
|
||||
for foo, err := range p.cache.RangeFoos(ctx, nil, pageToken, "") {
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err)
|
||||
}
|
||||
if len(out) == pageSize {
|
||||
nextToken, err := foos.Cursor(foo)
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err)
|
||||
}
|
||||
return out, nextToken, nil
|
||||
}
|
||||
out = append(out, foo)
|
||||
}
|
||||
return out, "", nil
|
||||
},
|
||||
cacheRange: func(ctx context.Context, startKey, endKey string) iter.Seq2[*foov1.Foo, error] {
|
||||
return p.cache.RangeFoos(ctx, nil, startKey, endKey)
|
||||
},
|
||||
update: func(ctx context.Context, foo *foov1.Foo) error {
|
||||
_, err := p.foos.UpdateFoo(ctx, foo)
|
||||
return err
|
||||
},
|
||||
delete: func(ctx context.Context, name string) error {
|
||||
return p.foos.DeleteFoo(ctx, foov1.DeleteFooRequest_builder{
|
||||
Name: name,
|
||||
}.Build())
|
||||
},
|
||||
deleteAll: func(ctx context.Context) error {
|
||||
for foo, err := range p.foos.RangeFoos(ctx, nil, "", "") {
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if err := p.foos.DeleteFoo(ctx, foov1.DeleteFooRequest_builder{
|
||||
Scope: foo.GetScope(),
|
||||
Name: foo.GetMetadata().GetName(),
|
||||
}.Build()); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func newFoo(name, scope, value string) *foov1.Foo {
|
||||
return foov1.Foo_builder{
|
||||
Kind: foos.Kind,
|
||||
Version: types.V1,
|
||||
Metadata: headerv1.Metadata_builder{
|
||||
Name: name,
|
||||
}.Build(),
|
||||
Scope: scope,
|
||||
Spec: foov1.FooSpec_builder{
|
||||
Value: value,
|
||||
}.Build(),
|
||||
}.Build()
|
||||
}
|
||||
+4
@@ -232,6 +232,9 @@ func setupTestCache(t *testing.T, setupConfig cache.SetupConfigFn) (*testCache,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
fooService, err := local.NewFooService(bkWrapper)
|
||||
require.NoError(t, err)
|
||||
|
||||
c, err := cache.New(setupConfig(cache.Config{
|
||||
Context: ctx,
|
||||
Events: eventsS,
|
||||
@@ -288,6 +291,7 @@ func setupTestCache(t *testing.T, setupConfig cache.SetupConfigFn) (*testCache,
|
||||
EventsC: eventsC,
|
||||
Summarizer: summaries,
|
||||
SubCAService: subCA,
|
||||
FooUpstream: fooService,
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -3190,6 +3190,7 @@ func (process *TeleportProcess) newAccessCacheForServices(cfg accesspoint.Config
|
||||
cfg.AppAuthConfig = services.AppAuthConfig
|
||||
cfg.Summarizer = services.Summarizer
|
||||
cfg.SubCAService = services.SubCAService
|
||||
cfg.FooUpstream = services.FooService
|
||||
|
||||
return accesspoint.NewCache(cfg)
|
||||
}
|
||||
@@ -3241,6 +3242,7 @@ func (process *TeleportProcess) newAccessCacheForClient(cfg accesspoint.Config,
|
||||
cfg.HealthCheckConfig = client
|
||||
cfg.AppAuthConfig = client
|
||||
cfg.SubCAService = client
|
||||
cfg.FooUpstream = services.NewFooClientAdapter(client.FooClient())
|
||||
|
||||
return accesspoint.NewCache(cfg)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
foov1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/foo/v1"
|
||||
)
|
||||
|
||||
// FooService is a service for interacting with Foo resources, implemented only
|
||||
// by the backend storage service.
|
||||
//
|
||||
// It should be included in [lib/auth.InitConfig] and embedded in [lib/auth.Services].
|
||||
type FooService interface {
|
||||
CreateFoo(ctx context.Context, foo *foov1.Foo) (*foov1.Foo, error)
|
||||
UpdateFoo(ctx context.Context, foo *foov1.Foo) (*foov1.Foo, error)
|
||||
UpsertFoo(ctx context.Context, foo *foov1.Foo) (*foov1.Foo, error)
|
||||
DeleteFoo(ctx context.Context, req *foov1.DeleteFooRequest) error
|
||||
GetFoo(ctx context.Context, req *foov1.GetFooRequest) (*foov1.Foo, error)
|
||||
ListFoos(ctx context.Context, req *foov1.ListFoosRequest) ([]*foov1.Foo, string, error)
|
||||
RangeFoos(ctx context.Context, req *foov1.ListFoosRequest, startKey, endKey string) iter.Seq2[*foov1.Foo, error]
|
||||
}
|
||||
|
||||
// FooReader is a read interface for reading Foos from a backend storage
|
||||
// service _or_ a cache.
|
||||
//
|
||||
// It should be embedded in [lib/auth/authclient.Cache] and consumed by the
|
||||
// gRPC API layer.
|
||||
type FooReader interface {
|
||||
GetFoo(ctx context.Context, req *foov1.GetFooRequest) (*foov1.Foo, error)
|
||||
RangeFoos(ctx context.Context, req *foov1.ListFoosRequest, startKey, endKey string) iter.Seq2[*foov1.Foo, error]
|
||||
}
|
||||
|
||||
// FooUpstream is a read interface for reading Foos from a backend storage
|
||||
// service _or_ an API client.
|
||||
//
|
||||
// It should be included in [lib/cache.Config] to be consumed by the cache.
|
||||
type FooUpstream interface {
|
||||
GetFoo(ctx context.Context, req *foov1.GetFooRequest) (*foov1.Foo, error)
|
||||
ListFoos(ctx context.Context, req *foov1.ListFoosRequest) ([]*foov1.Foo, string, error)
|
||||
}
|
||||
|
||||
// fooClientAdapter adapts a plain gRPC client to implement FooUpstream to be
|
||||
// consumed by the cache.
|
||||
//
|
||||
// It is only necessary if the resource needs to be cached on proxies or agents.
|
||||
type fooClientAdapter struct {
|
||||
grpcClient foov1.FooServiceClient
|
||||
}
|
||||
|
||||
// NewFooClientAdapter adapts a plain gRPC client to implement FooUpstream.
|
||||
func NewFooClientAdapter(grpcClient foov1.FooServiceClient) FooUpstream {
|
||||
return fooClientAdapter{
|
||||
grpcClient: grpcClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c fooClientAdapter) GetFoo(ctx context.Context, req *foov1.GetFooRequest) (*foov1.Foo, error) {
|
||||
resp, err := c.grpcClient.GetFoo(ctx, req)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return resp.GetFoo(), nil
|
||||
}
|
||||
|
||||
func (c fooClientAdapter) ListFoos(ctx context.Context, req *foov1.ListFoosRequest) ([]*foov1.Foo, string, error) {
|
||||
resp, err := c.grpcClient.ListFoos(ctx, req)
|
||||
if err != nil {
|
||||
return nil, "", trace.Wrap(err)
|
||||
}
|
||||
return resp.GetFoos(), resp.GetNextPageToken(), nil
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import (
|
||||
accessgraphsecretsv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/accessgraph/v1"
|
||||
"github.com/gravitational/teleport/api/gen/proto/go/teleport/autoupdate/v1"
|
||||
dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1"
|
||||
foov1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/foo/v1"
|
||||
headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1"
|
||||
kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1"
|
||||
machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1"
|
||||
@@ -44,6 +45,8 @@ import (
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/devicetrust"
|
||||
"github.com/gravitational/teleport/lib/foos"
|
||||
"github.com/gravitational/teleport/lib/scopes"
|
||||
scopedaccess "github.com/gravitational/teleport/lib/scopes/access"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/services/local/generic"
|
||||
@@ -301,6 +304,8 @@ func (e *EventsService) NewWatcher(ctx context.Context, watch types.Watch) (type
|
||||
parser = newCertAuthorityOverrideParser()
|
||||
case types.KindValidatedMFAChallenge:
|
||||
parser = newValidatedMFAChallengeParser()
|
||||
case foos.Kind:
|
||||
parser = newFooParser()
|
||||
default:
|
||||
if watch.AllowPartialSuccess {
|
||||
continue
|
||||
@@ -1087,6 +1092,79 @@ func (p *roleParser) parse(event backend.Event) (types.Resource, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func newFooParser() *fooParser {
|
||||
return &fooParser{
|
||||
baseParser: newBaseParser(
|
||||
fooUnscopedWatchPrefix(),
|
||||
fooScopedWatchPrefix(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
type fooParser struct {
|
||||
baseParser
|
||||
}
|
||||
|
||||
func (p *fooParser) parse(event backend.Event) (types.Resource, error) {
|
||||
switch event.Type {
|
||||
case types.OpDelete:
|
||||
sqn, err := fooNameFromKey(event.Item.Key)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
foo := foov1.Foo_builder{
|
||||
Kind: foos.Kind,
|
||||
Version: types.V1,
|
||||
Metadata: headerv1.Metadata_builder{
|
||||
Name: sqn.Name,
|
||||
}.Build(),
|
||||
Scope: sqn.Scope,
|
||||
}.Build()
|
||||
return types.Resource153ToLegacy(foo), nil
|
||||
case types.OpPut:
|
||||
foo, err := services.UnmarshalProtoResource[*foov1.Foo](
|
||||
event.Item.Value,
|
||||
services.WithExpires(event.Item.Expires),
|
||||
services.WithRevision(event.Item.Revision),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return types.Resource153ToLegacy(foo), nil
|
||||
default:
|
||||
return nil, trace.BadParameter("event %v is not supported", event.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func fooNameFromKey(key backend.Key) (scopes.QualifiedName, error) {
|
||||
switch {
|
||||
case key.HasPrefix(fooScopedWatchPrefix()):
|
||||
components := key.TrimPrefix(fooScopedWatchPrefix()).Components()
|
||||
if len(components) != 2 {
|
||||
return scopes.QualifiedName{}, trace.NotFound("failed parsing %v", key.String())
|
||||
}
|
||||
encodedScope, name := components[0], components[1]
|
||||
scope, err := scopes.DecodeFromKey(encodedScope)
|
||||
if err != nil {
|
||||
return scopes.QualifiedName{}, trace.Wrap(err)
|
||||
}
|
||||
return scopes.QualifiedName{
|
||||
Scope: scope,
|
||||
Name: name,
|
||||
}, nil
|
||||
case key.HasPrefix(fooUnscopedWatchPrefix()):
|
||||
components := key.TrimPrefix(fooUnscopedWatchPrefix()).Components()
|
||||
if len(components) != 1 {
|
||||
return scopes.QualifiedName{}, trace.NotFound("failed parsing %v", key.String())
|
||||
}
|
||||
return scopes.QualifiedName{
|
||||
Name: components[0],
|
||||
}, nil
|
||||
default:
|
||||
return scopes.QualifiedName{}, trace.NotFound("failed parsing %v", key.String())
|
||||
}
|
||||
}
|
||||
|
||||
func newScopedRoleParser() *scopedRoleParser {
|
||||
return &scopedRoleParser{
|
||||
baseParser: newBaseParser(scopedRoleWatchPrefix()),
|
||||
|
||||
@@ -31,6 +31,14 @@ import (
|
||||
"github.com/gravitational/teleport/lib/services/local/generic"
|
||||
)
|
||||
|
||||
func fooUnscopedWatchPrefix() backend.Key {
|
||||
return backend.ExactKey("foo")
|
||||
}
|
||||
|
||||
func fooScopedWatchPrefix() backend.Key {
|
||||
return backend.ExactKey("scoped", "foo")
|
||||
}
|
||||
|
||||
// FooService is a storage service for Foos.
|
||||
type FooService struct {
|
||||
service *generic.ScopeAwareServiceWrapper[*foov1.Foo]
|
||||
|
||||
Reference in New Issue
Block a user