diff --git a/lib/srv/discovery/common/interfaces.go b/lib/srv/discovery/common/interfaces.go index 41db467e31eb0..b8565cb6c7bde 100644 --- a/lib/srv/discovery/common/interfaces.go +++ b/lib/srv/discovery/common/interfaces.go @@ -36,10 +36,10 @@ type Fetcher interface { // IntegrationName identifies the integration name whose credentials were used to fetch the resources. // Might be empty when the fetcher is using ambient credentials. IntegrationName() string - // DiscoveryConfigName is the name of the discovery config which originated the resource. + // GetDiscoveryConfigName is the name of the discovery config which originated the resource. // Might be empty when the fetcher is using static matchers: // ie teleport.yaml/discovery_service.. - DiscoveryConfigName() string + GetDiscoveryConfigName() string // Cloud returns the cloud the fetcher is operating. Cloud() string } diff --git a/lib/srv/discovery/common/watcher.go b/lib/srv/discovery/common/watcher.go index fd182b12418c1..a08ff8a4f5b7f 100644 --- a/lib/srv/discovery/common/watcher.go +++ b/lib/srv/discovery/common/watcher.go @@ -177,9 +177,9 @@ func (w *Watcher) fetchAndSend() { // Add the integration name to the static labels for each resource. fetcherLabels[types.TeleportInternalDiscoveryIntegrationName] = lFetcher.IntegrationName() } - if lFetcher.DiscoveryConfigName() != "" { + if lFetcher.GetDiscoveryConfigName() != "" { // Add the discovery config name to the static labels of each resource. - fetcherLabels[types.TeleportInternalDiscoveryConfigName] = lFetcher.DiscoveryConfigName() + fetcherLabels[types.TeleportInternalDiscoveryConfigName] = lFetcher.GetDiscoveryConfigName() } if w.cfg.DiscoveryGroup != "" { diff --git a/lib/srv/discovery/common/watcher_test.go b/lib/srv/discovery/common/watcher_test.go index 3f5828a204b9f..2f780d90ee628 100644 --- a/lib/srv/discovery/common/watcher_test.go +++ b/lib/srv/discovery/common/watcher_test.go @@ -178,7 +178,7 @@ func (m *mockFetcher) IntegrationName() string { return "" } -func (m *mockFetcher) DiscoveryConfigName() string { +func (m *mockFetcher) GetDiscoveryConfigName() string { return "" } func (m *mockFetcher) Cloud() string { diff --git a/lib/srv/discovery/database_watcher.go b/lib/srv/discovery/database_watcher.go index 19971903c9014..4560e6e0ffe58 100644 --- a/lib/srv/discovery/database_watcher.go +++ b/lib/srv/discovery/database_watcher.go @@ -30,6 +30,7 @@ import ( "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/srv/discovery/common" "github.com/gravitational/teleport/lib/utils" + "github.com/gravitational/teleport/lib/utils/slices" ) const databaseEventPrefix = "db/" @@ -74,6 +75,14 @@ func (s *Server) startDatabaseWatchers() error { Origin: types.OriginCloud, Clock: s.clock, PreFetchHookFn: func() { + discoveryConfigs := slices.FilterMapUnique( + s.getAllDatabaseFetchers(), + func(f common.Fetcher) (s string, include bool) { + return f.GetDiscoveryConfigName(), f.GetDiscoveryConfigName() != "" + }, + ) + s.updateDiscoveryConfigStatus(discoveryConfigs...) + s.awsRDSResourcesStatus.reset() }, }, @@ -99,7 +108,7 @@ func (s *Server) startDatabaseWatchers() error { resourceGroup := awsResourceGroupFromLabels(db.GetStaticLabels()) resourcesFoundByGroup[resourceGroup] += 1 - discoveryConfigsChanged[resourceGroup.discoveryConfig] = struct{}{} + discoveryConfigsChanged[resourceGroup.discoveryConfigName] = struct{}{} dbs = append(dbs, db) } diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index 141e2cd6d613a..078ab234faca4 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -65,11 +65,14 @@ import ( "github.com/gravitational/teleport/lib/srv/discovery/fetchers/db" "github.com/gravitational/teleport/lib/srv/server" logutils "github.com/gravitational/teleport/lib/utils/log" + libslices "github.com/gravitational/teleport/lib/utils/slices" "github.com/gravitational/teleport/lib/utils/spreadwork" ) var errNoInstances = errors.New("all fetched nodes already enrolled") +const noDiscoveryConfig = "" + // Matchers contains all matchers used by discovery service type Matchers struct { // AWS is a list of AWS EC2 matchers. @@ -439,7 +442,7 @@ func New(ctx context.Context, cfg *Config) (*Server, error) { return nil, trace.Wrap(err) } - databaseFetchers, err := s.databaseFetchersFromMatchers(cfg.Matchers, "" /* discovery config */) + databaseFetchers, err := s.databaseFetchersFromMatchers(cfg.Matchers, noDiscoveryConfig) if err != nil { return nil, trace.Wrap(err) } @@ -449,11 +452,11 @@ func New(ctx context.Context, cfg *Config) (*Server, error) { return nil, trace.Wrap(err) } - if err := s.initAzureWatchers(s.ctx, cfg.Matchers.Azure); err != nil { + if err := s.initAzureWatchers(s.ctx, cfg.Matchers.Azure, noDiscoveryConfig); err != nil { return nil, trace.Wrap(err) } - if err := s.initGCPWatchers(s.ctx, cfg.Matchers.GCP); err != nil { + if err := s.initGCPWatchers(s.ctx, cfg.Matchers.GCP, noDiscoveryConfig); err != nil { return nil, trace.Wrap(err) } @@ -518,7 +521,6 @@ func (s *Server) initAWSWatchers(matchers []types.AWSMatcher) error { return matcherType == types.AWSMatcherEC2 }) - const noDiscoveryConfig = "" s.staticServerAWSFetchers, err = server.MatchersToEC2InstanceFetchers(s.ctx, ec2Matchers, s.GetEC2Client, noDiscoveryConfig) if err != nil { return trace.Wrap(err) @@ -529,6 +531,14 @@ func (s *Server) initAWSWatchers(matchers []types.AWSMatcher) error { server.WithPollInterval(s.PollInterval), server.WithTriggerFetchC(s.newDiscoveryConfigChangedSub()), server.WithPreFetchHookFn(func() { + discoveryConfigs := libslices.FilterMapUnique( + s.getAllAWSServerFetchers(), + func(f server.Fetcher) (s string, include bool) { + return f.GetDiscoveryConfigName(), f.GetDiscoveryConfigName() != "" + }, + ) + s.updateDiscoveryConfigStatus(discoveryConfigs...) + s.awsEC2ResourcesStatus.reset() s.awsEC2Tasks.reset() }), @@ -563,7 +573,7 @@ func (s *Server) initAWSWatchers(matchers []types.AWSMatcher) error { _, otherMatchers = splitMatchers(otherMatchers, db.IsAWSMatcherType) // Add non-integration kube fetchers. - kubeFetchers, err := fetchers.MakeEKSFetchersFromAWSMatchers(s.LegacyLogger, s.CloudClients, otherMatchers, "" /* discovery config */) + kubeFetchers, err := fetchers.MakeEKSFetchersFromAWSMatchers(s.LegacyLogger, s.CloudClients, otherMatchers, noDiscoveryConfig) if err != nil { return trace.Wrap(err) } @@ -604,12 +614,12 @@ func (s *Server) initKubeAppWatchers(matchers []types.KubernetesMatcher) error { } // awsServerFetchersFromMatchers converts Matchers into a set of AWS EC2 Fetchers. -func (s *Server) awsServerFetchersFromMatchers(ctx context.Context, matchers []types.AWSMatcher, discoveryConfig string) ([]server.Fetcher, error) { +func (s *Server) awsServerFetchersFromMatchers(ctx context.Context, matchers []types.AWSMatcher, discoveryConfigName string) ([]server.Fetcher, error) { serverMatchers, _ := splitMatchers(matchers, func(matcherType string) bool { return matcherType == types.AWSMatcherEC2 }) - fetchers, err := server.MatchersToEC2InstanceFetchers(ctx, serverMatchers, s.GetEC2Client, discoveryConfig) + fetchers, err := server.MatchersToEC2InstanceFetchers(ctx, serverMatchers, s.GetEC2Client, discoveryConfigName) if err != nil { return nil, trace.Wrap(err) } @@ -618,16 +628,16 @@ func (s *Server) awsServerFetchersFromMatchers(ctx context.Context, matchers []t } // azureServerFetchersFromMatchers converts Matchers into a set of Azure Servers Fetchers. -func (s *Server) azureServerFetchersFromMatchers(matchers []types.AzureMatcher) []server.Fetcher { +func (s *Server) azureServerFetchersFromMatchers(matchers []types.AzureMatcher, discoveryConfigName string) []server.Fetcher { serverMatchers, _ := splitMatchers(matchers, func(matcherType string) bool { return matcherType == types.AzureMatcherVM }) - return server.MatchersToAzureInstanceFetchers(serverMatchers, s.CloudClients) + return server.MatchersToAzureInstanceFetchers(serverMatchers, s.CloudClients, discoveryConfigName) } // gcpServerFetchersFromMatchers converts Matchers into a set of GCP Servers Fetchers. -func (s *Server) gcpServerFetchersFromMatchers(ctx context.Context, matchers []types.GCPMatcher) ([]server.Fetcher, error) { +func (s *Server) gcpServerFetchersFromMatchers(ctx context.Context, matchers []types.GCPMatcher, discoveryConfigName string) ([]server.Fetcher, error) { serverMatchers, _ := splitMatchers(matchers, func(matcherType string) bool { return matcherType == types.GCPMatcherCompute }) @@ -648,17 +658,17 @@ func (s *Server) gcpServerFetchersFromMatchers(ctx context.Context, matchers []t return nil, trace.Wrap(err) } - return server.MatchersToGCPInstanceFetchers(serverMatchers, client, projectsClient), nil + return server.MatchersToGCPInstanceFetchers(serverMatchers, client, projectsClient, discoveryConfigName), nil } // databaseFetchersFromMatchers converts Matchers into a set of Database Fetchers. -func (s *Server) databaseFetchersFromMatchers(matchers Matchers, discoveryConfig string) ([]common.Fetcher, error) { +func (s *Server) databaseFetchersFromMatchers(matchers Matchers, discoveryConfigName string) ([]common.Fetcher, error) { var fetchers []common.Fetcher // AWS awsDatabaseMatchers, _ := splitMatchers(matchers.AWS, db.IsAWSMatcherType) if len(awsDatabaseMatchers) > 0 { - databaseFetchers, err := db.MakeAWSFetchers(s.ctx, s.CloudClients, awsDatabaseMatchers, discoveryConfig) + databaseFetchers, err := db.MakeAWSFetchers(s.ctx, s.CloudClients, awsDatabaseMatchers, discoveryConfigName) if err != nil { return nil, trace.Wrap(err) } @@ -668,7 +678,7 @@ func (s *Server) databaseFetchersFromMatchers(matchers Matchers, discoveryConfig // Azure azureDatabaseMatchers, _ := splitMatchers(matchers.Azure, db.IsAzureMatcherType) if len(azureDatabaseMatchers) > 0 { - databaseFetchers, err := db.MakeAzureFetchers(s.CloudClients, azureDatabaseMatchers, discoveryConfig) + databaseFetchers, err := db.MakeAzureFetchers(s.CloudClients, azureDatabaseMatchers, discoveryConfigName) if err != nil { return nil, trace.Wrap(err) } @@ -681,7 +691,7 @@ func (s *Server) databaseFetchersFromMatchers(matchers Matchers, discoveryConfig return fetchers, nil } -func (s *Server) kubeFetchersFromMatchers(matchers Matchers, discoveryConfig string) ([]common.Fetcher, error) { +func (s *Server) kubeFetchersFromMatchers(matchers Matchers, discoveryConfigName string) ([]common.Fetcher, error) { var result []common.Fetcher // AWS @@ -689,7 +699,7 @@ func (s *Server) kubeFetchersFromMatchers(matchers Matchers, discoveryConfig str return matcherType == types.AWSMatcherEKS }) if len(awsKubeMatchers) > 0 { - eksFetchers, err := fetchers.MakeEKSFetchersFromAWSMatchers(s.LegacyLogger, s.CloudClients, awsKubeMatchers, discoveryConfig) + eksFetchers, err := fetchers.MakeEKSFetchersFromAWSMatchers(s.LegacyLogger, s.CloudClients, awsKubeMatchers, discoveryConfigName) if err != nil { return nil, trace.Wrap(err) } @@ -702,12 +712,12 @@ func (s *Server) kubeFetchersFromMatchers(matchers Matchers, discoveryConfig str } // initAzureWatchers starts Azure resource watchers based on types provided. -func (s *Server) initAzureWatchers(ctx context.Context, matchers []types.AzureMatcher) error { +func (s *Server) initAzureWatchers(ctx context.Context, matchers []types.AzureMatcher, discoveryConfigName string) error { vmMatchers, otherMatchers := splitMatchers(matchers, func(matcherType string) bool { return matcherType == types.AzureMatcherVM }) - s.staticServerAzureFetchers = server.MatchersToAzureInstanceFetchers(vmMatchers, s.CloudClients) + s.staticServerAzureFetchers = server.MatchersToAzureInstanceFetchers(vmMatchers, s.CloudClients, discoveryConfigName) // VM watcher. var err error @@ -715,6 +725,15 @@ func (s *Server) initAzureWatchers(ctx context.Context, matchers []types.AzureMa s.ctx, s.getAllAzureServerFetchers, server.WithPollInterval(s.PollInterval), server.WithTriggerFetchC(s.newDiscoveryConfigChangedSub()), + server.WithPreFetchHookFn(func() { + discoveryConfigs := libslices.FilterMapUnique( + s.getAllAzureServerFetchers(), + func(f server.Fetcher) (s string, include bool) { + return f.GetDiscoveryConfigName(), f.GetDiscoveryConfigName() != "" + }, + ) + s.updateDiscoveryConfigStatus(discoveryConfigs...) + }), ) if err != nil { return trace.Wrap(err) @@ -743,11 +762,12 @@ func (s *Server) initAzureWatchers(ctx context.Context, matchers []types.AzureMa return trace.Wrap(err) } fetcher, err := fetchers.NewAKSFetcher(fetchers.AKSFetcherConfig{ - Client: kubeClient, - Regions: matcher.Regions, - FilterLabels: matcher.ResourceTags, - ResourceGroups: matcher.ResourceGroups, - Log: s.LegacyLogger, + Client: kubeClient, + Regions: matcher.Regions, + FilterLabels: matcher.ResourceTags, + ResourceGroups: matcher.ResourceGroups, + Log: s.LegacyLogger, + DiscoveryConfigName: discoveryConfigName, }) if err != nil { return trace.Wrap(err) @@ -760,10 +780,10 @@ func (s *Server) initAzureWatchers(ctx context.Context, matchers []types.AzureMa return nil } -func (s *Server) initGCPServerWatcher(ctx context.Context, vmMatchers []types.GCPMatcher) error { +func (s *Server) initGCPServerWatcher(ctx context.Context, vmMatchers []types.GCPMatcher, discoveryConfigName string) error { var err error - s.staticServerGCPFetchers, err = s.gcpServerFetchersFromMatchers(ctx, vmMatchers) + s.staticServerGCPFetchers, err = s.gcpServerFetchersFromMatchers(ctx, vmMatchers, discoveryConfigName) if err != nil { return trace.Wrap(err) } @@ -772,6 +792,15 @@ func (s *Server) initGCPServerWatcher(ctx context.Context, vmMatchers []types.GC s.ctx, s.getAllGCPServerFetchers, server.WithPollInterval(s.PollInterval), server.WithTriggerFetchC(s.newDiscoveryConfigChangedSub()), + server.WithPreFetchHookFn(func() { + discoveryConfigs := libslices.FilterMapUnique( + s.getAllGCPServerFetchers(), + func(f server.Fetcher) (s string, include bool) { + return f.GetDiscoveryConfigName(), f.GetDiscoveryConfigName() != "" + }, + ) + s.updateDiscoveryConfigStatus(discoveryConfigs...) + }), ) if err != nil { return trace.Wrap(err) @@ -787,7 +816,7 @@ func (s *Server) initGCPServerWatcher(ctx context.Context, vmMatchers []types.GC } // initGCPWatchers starts GCP resource watchers based on types provided. -func (s *Server) initGCPWatchers(ctx context.Context, matchers []types.GCPMatcher) error { +func (s *Server) initGCPWatchers(ctx context.Context, matchers []types.GCPMatcher, discoveryConfigName string) error { // return early if there are no matchers as GetGCPGKEClient causes // an error if there are no credentials present @@ -795,7 +824,7 @@ func (s *Server) initGCPWatchers(ctx context.Context, matchers []types.GCPMatche return matcherType == types.GCPMatcherCompute }) - if err := s.initGCPServerWatcher(ctx, vmMatchers); err != nil { + if err := s.initGCPServerWatcher(ctx, vmMatchers, discoveryConfigName); err != nil { return trace.Wrap(err) } @@ -926,8 +955,8 @@ func (s *Server) handleEC2Instances(instances *server.EC2Instances) error { instancesAlreadyEnrolled := totalInstancesFound - len(instances.Instances) s.awsEC2ResourcesStatus.incrementEnrolled(awsResourceGroup{ - discoveryConfig: instances.DiscoveryConfig, - integration: instances.Integration, + discoveryConfigName: instances.DiscoveryConfigName, + integration: instances.Integration, }, instancesAlreadyEnrolled) if len(instances.Instances) == 0 { @@ -969,8 +998,8 @@ func (s *Server) heartbeatEICEInstance(instances *server.EC2Instances) { s.Log.WarnContext(s.ctx, "Error converting to Teleport EICE Node", "error", err, "instance_id", ec2Instance.InstanceID) s.awsEC2ResourcesStatus.incrementFailed(awsResourceGroup{ - discoveryConfig: instances.DiscoveryConfig, - integration: instances.Integration, + discoveryConfigName: instances.DiscoveryConfigName, + integration: instances.Integration, }, 1) continue } @@ -1023,8 +1052,8 @@ func (s *Server) heartbeatEICEInstance(instances *server.EC2Instances) { instanceID := eiceNode.GetAWSInstanceID() s.Log.WarnContext(s.ctx, "Error upserting EC2 instance", "instance_id", instanceID, "error", err) s.awsEC2ResourcesStatus.incrementFailed(awsResourceGroup{ - discoveryConfig: instances.DiscoveryConfig, - integration: instances.Integration, + discoveryConfigName: instances.DiscoveryConfigName, + integration: instances.Integration, }, 1) } }) @@ -1046,19 +1075,19 @@ func (s *Server) handleEC2RemoteInstallation(instances *server.EC2Instances) err s.Log.DebugContext(s.ctx, "Running Teleport installation on instances", "account_id", instances.AccountID, "instances", genEC2InstancesLogStr(instances.Instances)) req := server.SSMRunRequest{ - DocumentName: instances.DocumentName, - SSM: ssmClient, - Instances: instances.Instances, - Params: instances.Parameters, - Region: instances.Region, - AccountID: instances.AccountID, - IntegrationName: instances.Integration, - DiscoveryConfig: instances.DiscoveryConfig, + DocumentName: instances.DocumentName, + SSM: ssmClient, + Instances: instances.Instances, + Params: instances.Parameters, + Region: instances.Region, + AccountID: instances.AccountID, + IntegrationName: instances.Integration, + DiscoveryConfigName: instances.DiscoveryConfigName, } if err := s.ec2Installer.Run(s.ctx, req); err != nil { s.awsEC2ResourcesStatus.incrementFailed(awsResourceGroup{ - discoveryConfig: instances.DiscoveryConfig, - integration: instances.Integration, + discoveryConfigName: instances.DiscoveryConfigName, + integration: instances.Integration, }, len(req.Instances)) for _, instance := range req.Instances { @@ -1072,7 +1101,7 @@ func (s *Server) handleEC2RemoteInstallation(instances *server.EC2Instances) err installerScript: req.InstallerScriptName(), }, &usertasksv1.DiscoverEC2Instance{ - DiscoveryConfig: instances.DiscoveryConfig, + DiscoveryConfig: instances.DiscoveryConfigName, DiscoveryGroup: s.DiscoveryGroup, InstanceId: instance.InstanceID, Name: instance.InstanceName, @@ -1191,15 +1220,15 @@ func (s *Server) handleEC2Discovery() { s.Log.DebugContext(s.ctx, "EC2 instances discovered, starting installation", "account_id", ec2Instances.AccountID, "instances", genEC2InstancesLogStr(ec2Instances.Instances)) s.awsEC2ResourcesStatus.incrementFound(awsResourceGroup{ - discoveryConfig: instances.EC2.DiscoveryConfig, - integration: instances.EC2.Integration, + discoveryConfigName: instances.EC2.DiscoveryConfigName, + integration: instances.EC2.Integration, }, len(instances.EC2.Instances)) if err := s.handleEC2Instances(ec2Instances); err != nil { s.logHandleInstancesErr(err) } - s.updateDiscoveryConfigStatus(instances.EC2.DiscoveryConfig) + s.updateDiscoveryConfigStatus(instances.EC2.DiscoveryConfigName) s.upsertTasksForAWSEC2FailedEnrollments() case <-s.ctx.Done(): s.ec2Watcher.Stop() @@ -1622,7 +1651,9 @@ func (s *Server) startDynamicWatcherUpdater() { s.Log.WarnContext(s.ctx, "Skipping unknown event type %s", "got", event.Type) } case <-s.dynamicMatcherWatcher.Done(): - s.Log.WarnContext(s.ctx, "Dynamic matcher watcher error", "error", s.dynamicMatcherWatcher.Error()) + if err := s.dynamicMatcherWatcher.Error(); err != nil { + s.Log.WarnContext(s.ctx, "Dynamic matcher watcher error", "error", err) + } return } } @@ -1697,12 +1728,12 @@ func (s *Server) upsertDynamicMatchers(ctx context.Context, dc *discoveryconfig. s.dynamicServerAWSFetchers[dc.GetName()] = awsServerFetchers s.muDynamicServerAWSFetchers.Unlock() - azureServerFetchers := s.azureServerFetchersFromMatchers(matchers.Azure) + azureServerFetchers := s.azureServerFetchersFromMatchers(matchers.Azure, dc.GetName()) s.muDynamicServerAzureFetchers.Lock() s.dynamicServerAzureFetchers[dc.GetName()] = azureServerFetchers s.muDynamicServerAzureFetchers.Unlock() - gcpServerFetchers, err := s.gcpServerFetchersFromMatchers(s.ctx, matchers.GCP) + gcpServerFetchers, err := s.gcpServerFetchersFromMatchers(s.ctx, matchers.GCP, dc.GetName()) if err != nil { return trace.Wrap(err) } diff --git a/lib/srv/discovery/discovery_test.go b/lib/srv/discovery/discovery_test.go index e2a187357d084..42516c9cf7491 100644 --- a/lib/srv/discovery/discovery_test.go +++ b/lib/srv/discovery/discovery_test.go @@ -1945,18 +1945,18 @@ func TestDiscoveryDatabase(t *testing.T) { ) awsRedshiftResource, awsRedshiftDB := makeRedshiftCluster(t, "aws-redshift", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup}) _, awsRedshiftDBWithIntegration := makeRedshiftCluster(t, "aws-redshift", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, integration: integrationName}) - _, awsRedshiftDBWithIntegrationAndDiscoveryConfig := makeRedshiftCluster(t, "aws-redshift", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, integration: integrationName, discoveryConfig: discoveryConfigName}) - _, awsRedshiftDBWithDiscoveryConfig := makeRedshiftCluster(t, "aws-redshift", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, discoveryConfig: discoveryConfigName}) + _, awsRedshiftDBWithIntegrationAndDiscoveryConfig := makeRedshiftCluster(t, "aws-redshift", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, integration: integrationName, discoveryConfigName: discoveryConfigName}) + _, awsRedshiftDBWithDiscoveryConfig := makeRedshiftCluster(t, "aws-redshift", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, discoveryConfigName: discoveryConfigName}) awsRDSInstance, awsRDSDB := makeRDSInstance(t, "aws-rds", "us-west-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup}) azRedisResource, azRedisDB := makeAzureRedisServer(t, "az-redis", "sub1", "group1", "East US", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup}) - _, azRedisDBWithDiscoveryConfig := makeAzureRedisServer(t, "az-redis", "sub1", "group1", "East US", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, discoveryConfig: discoveryConfigName}) + _, azRedisDBWithDiscoveryConfig := makeAzureRedisServer(t, "az-redis", "sub1", "group1", "East US", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, discoveryConfigName: discoveryConfigName}) role := types.AssumeRole{RoleARN: "arn:aws:iam::123456789012:role/test-role", ExternalID: "test123"} awsRDSDBWithRole := awsRDSDB.Copy() awsRDSDBWithRole.SetAWSAssumeRole("arn:aws:iam::123456789012:role/test-role") awsRDSDBWithRole.SetAWSExternalID("test123") - eksAWSResource, _ := makeEKSCluster(t, "aws-eks", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, integration: integrationName, discoveryConfig: discoveryConfigName}) + eksAWSResource, _ := makeEKSCluster(t, "aws-eks", "us-east-1", rewriteDiscoveryLabelsParams{discoveryGroup: mainDiscoveryGroup, integration: integrationName, discoveryConfigName: discoveryConfigName}) matcherForDiscoveryConfigFn := func(t *testing.T, discoveryGroup string, m Matchers) *discoveryconfig.DiscoveryConfig { dc, err := discoveryconfig.NewDiscoveryConfig( @@ -1982,6 +1982,7 @@ func TestDiscoveryDatabase(t *testing.T) { {Engine: aws.String(services.RDSEnginePostgres)}, }, }, + MemoryDB: &mocks.MemoryDBMock{}, Redshift: &mocks.RedshiftMock{ Clusters: []*redshift.Cluster{awsRedshiftResource}, }, @@ -2244,6 +2245,27 @@ func TestDiscoveryDatabase(t *testing.T) { require.Zero(t, s.IntegrationDiscoveredResources[integrationName].AwsEks.Enrolled) }, }, + { + name: "discovery config status must be updated even when there are no resources", + discoveryConfigs: func(t *testing.T) []*discoveryconfig.DiscoveryConfig { + dc1 := matcherForDiscoveryConfigFn(t, mainDiscoveryGroup, Matchers{ + AWS: []types.AWSMatcher{{ + // MemoryDB mock client returns no resources. + Types: []string{types.AWSMatcherMemoryDB}, + Tags: map[string]utils.Strings{types.Wildcard: {types.Wildcard}}, + Regions: []string{"us-east-1"}, + Integration: integrationName, + }}, + }) + return []*discoveryconfig.DiscoveryConfig{dc1} + }, + expectDatabases: []types.Database{}, + wantEvents: 0, + discoveryConfigStatusCheck: func(t *testing.T, s discoveryconfig.Status) { + require.Equal(t, uint64(0), s.DiscoveredResources) + require.Equal(t, "DISCOVERY_CONFIG_STATE_SYNCING", s.State) + }, + }, } for _, tc := range tcs { @@ -2362,7 +2384,7 @@ func TestDiscoveryDatabaseRemovingDiscoveryConfigs(t *testing.T) { dc1Name := uuid.NewString() dc2Name := uuid.NewString() - awsRDSInstance, awsRDSDB := makeRDSInstance(t, "aws-rds", "us-west-1", rewriteDiscoveryLabelsParams{discoveryConfig: dc2Name, discoveryGroup: mainDiscoveryGroup}) + awsRDSInstance, awsRDSDB := makeRDSInstance(t, "aws-rds", "us-west-1", rewriteDiscoveryLabelsParams{discoveryConfigName: dc2Name, discoveryGroup: mainDiscoveryGroup}) testCloudClients := &cloud.TestCloudClients{ STS: &mocks.STSMock{}, @@ -3532,10 +3554,10 @@ func (m fakeWatcher) Error() error { } type rewriteDiscoveryLabelsParams struct { - matcherType string - discoveryConfig string - discoveryGroup string - integration string + matcherType string + discoveryConfigName string + discoveryGroup string + integration string } // rewriteCloudResource is a test helper func that rewrites an expected cloud @@ -3546,8 +3568,8 @@ func rewriteCloudResource(t *testing.T, r types.ResourceWithLabels, discoveryPar if discoveryParams.matcherType != "" { staticLabels[types.DiscoveryTypeLabel] = discoveryParams.matcherType } - if discoveryParams.discoveryConfig != "" { - staticLabels[types.TeleportInternalDiscoveryConfigName] = discoveryParams.discoveryConfig + if discoveryParams.discoveryConfigName != "" { + staticLabels[types.TeleportInternalDiscoveryConfigName] = discoveryParams.discoveryConfigName } if discoveryParams.discoveryGroup != "" { staticLabels[types.TeleportInternalDiscoveryGroupName] = discoveryParams.discoveryGroup diff --git a/lib/srv/discovery/fetchers/aks.go b/lib/srv/discovery/fetchers/aks.go index f2c251d4fe754..fd918e6bb7170 100644 --- a/lib/srv/discovery/fetchers/aks.go +++ b/lib/srv/discovery/fetchers/aks.go @@ -49,6 +49,8 @@ type AKSFetcherConfig struct { FilterLabels types.Labels // Log is the logger. Log logrus.FieldLogger + // DiscoveryConfigName is the name of the DiscoveryConfig that created this Fetcher. + DiscoveryConfigName string } // CheckAndSetDefaults validates and sets the defaults values. @@ -156,8 +158,8 @@ func (a *aksFetcher) IntegrationName() string { return "" } -func (a *aksFetcher) DiscoveryConfigName() string { - return "" +func (a *aksFetcher) GetDiscoveryConfigName() string { + return a.DiscoveryConfigName } func (a *aksFetcher) FetcherType() string { diff --git a/lib/srv/discovery/fetchers/db/aws.go b/lib/srv/discovery/fetchers/db/aws.go index b9009150fa1af..789cac7ec4990 100644 --- a/lib/srv/discovery/fetchers/db/aws.go +++ b/lib/srv/discovery/fetchers/db/aws.go @@ -67,7 +67,7 @@ type awsFetcherConfig struct { // DiscoveryConfigName is the name of the discovery config which originated the resource. // Might be empty when the fetcher is using static matchers: // ie teleport.yaml/discovery_service.. - DiscoveryConfig string + DiscoveryConfigName string } // CheckAndSetDefaults validates the config and sets defaults. @@ -176,9 +176,9 @@ func (f *awsFetcher) IntegrationName() string { return f.cfg.Integration } -// DiscoveryConfigName returns the discovery config name whose matchers are used to fetch the resources. -func (f *awsFetcher) DiscoveryConfigName() string { - return f.cfg.DiscoveryConfig +// GetDiscoveryConfigName returns the discovery config name whose matchers are used to fetch the resources. +func (f *awsFetcher) GetDiscoveryConfigName() string { + return f.cfg.DiscoveryConfigName } // ResourceType identifies the resource type the fetcher is returning. diff --git a/lib/srv/discovery/fetchers/db/azure.go b/lib/srv/discovery/fetchers/db/azure.go index 21b485e298dfb..a0a8a600760b3 100644 --- a/lib/srv/discovery/fetchers/db/azure.go +++ b/lib/srv/discovery/fetchers/db/azure.go @@ -91,8 +91,8 @@ type azureFetcherConfig struct { Regions []string // regionSet is a set of regions, used for efficient region match lookup. regionSet map[string]struct{} - // DiscoveryConfig is the name of the discovery config which originated the resource. - DiscoveryConfig string + // DiscoveryConfigName is the name of the discovery config which originated the resource. + DiscoveryConfigName string } // regionMatches returns whether a given region matches the configured Regions selector @@ -158,12 +158,12 @@ func (f *azureFetcher[DBType, ListClient]) IntegrationName() string { return "" } -// DiscoveryConfigName is the name of the discovery config which originated the resource. +// GetDiscoveryConfigName is the name of the discovery config which originated the resource. // It is used to report stats for a given discovery config. // Might be empty when the fetcher is using static matchers: // ie teleport.yaml/discovery_service.. -func (f *azureFetcher[DBType, ListClient]) DiscoveryConfigName() string { - return f.cfg.DiscoveryConfig +func (f *azureFetcher[DBType, ListClient]) GetDiscoveryConfigName() string { + return f.cfg.DiscoveryConfigName } // Get returns Azure DB servers matching the watcher's selectors. diff --git a/lib/srv/discovery/fetchers/db/db.go b/lib/srv/discovery/fetchers/db/db.go index 2e7f48d485b77..d1307a1e97444 100644 --- a/lib/srv/discovery/fetchers/db/db.go +++ b/lib/srv/discovery/fetchers/db/db.go @@ -65,7 +65,7 @@ func IsAzureMatcherType(matcherType string) bool { } // MakeAWSFetchers creates new AWS database fetchers. -func MakeAWSFetchers(ctx context.Context, clients cloud.AWSClients, matchers []types.AWSMatcher, discoveryConfig string) (result []common.Fetcher, err error) { +func MakeAWSFetchers(ctx context.Context, clients cloud.AWSClients, matchers []types.AWSMatcher, discoveryConfigName string) (result []common.Fetcher, err error) { for _, matcher := range matchers { assumeRole := types.AssumeRole{} if matcher.AssumeRole != nil { @@ -80,13 +80,13 @@ func MakeAWSFetchers(ctx context.Context, clients cloud.AWSClients, matchers []t for _, makeFetcher := range makeFetchers { for _, region := range matcher.Regions { fetcher, err := makeFetcher(awsFetcherConfig{ - AWSClients: clients, - Type: matcherType, - AssumeRole: assumeRole, - Labels: matcher.Tags, - Region: region, - Integration: matcher.Integration, - DiscoveryConfig: discoveryConfig, + AWSClients: clients, + Type: matcherType, + AssumeRole: assumeRole, + Labels: matcher.Tags, + Region: region, + Integration: matcher.Integration, + DiscoveryConfigName: discoveryConfigName, }) if err != nil { return nil, trace.Wrap(err) @@ -100,7 +100,7 @@ func MakeAWSFetchers(ctx context.Context, clients cloud.AWSClients, matchers []t } // MakeAzureFetchers creates new Azure database fetchers. -func MakeAzureFetchers(clients cloud.AzureClients, matchers []types.AzureMatcher, discoveryConfig string) (result []common.Fetcher, err error) { +func MakeAzureFetchers(clients cloud.AzureClients, matchers []types.AzureMatcher, discoveryConfigName string) (result []common.Fetcher, err error) { for _, matcher := range services.SimplifyAzureMatchers(matchers) { for _, matcherType := range matcher.Types { makeFetchers, found := makeAzureFetcherFuncs[matcherType] @@ -112,13 +112,13 @@ func MakeAzureFetchers(clients cloud.AzureClients, matchers []types.AzureMatcher for _, sub := range matcher.Subscriptions { for _, group := range matcher.ResourceGroups { fetcher, err := makeFetcher(azureFetcherConfig{ - AzureClients: clients, - Type: matcherType, - Subscription: sub, - ResourceGroup: group, - Labels: matcher.ResourceTags, - Regions: matcher.Regions, - DiscoveryConfig: discoveryConfig, + AzureClients: clients, + Type: matcherType, + Subscription: sub, + ResourceGroup: group, + Labels: matcher.ResourceTags, + Regions: matcher.Regions, + DiscoveryConfigName: discoveryConfigName, }) if err != nil { return nil, trace.Wrap(err) diff --git a/lib/srv/discovery/fetchers/db/helpers_test.go b/lib/srv/discovery/fetchers/db/helpers_test.go index 95a33f221c2b0..6063198b71e6d 100644 --- a/lib/srv/discovery/fetchers/db/helpers_test.go +++ b/lib/srv/discovery/fetchers/db/helpers_test.go @@ -53,10 +53,10 @@ func makeAWSMatchersForType(matcherType, region string, tags map[string]string) }} } -func mustMakeAWSFetchers(t *testing.T, clients cloud.AWSClients, matchers []types.AWSMatcher, discoveryConfig string) []common.Fetcher { +func mustMakeAWSFetchers(t *testing.T, clients cloud.AWSClients, matchers []types.AWSMatcher, discoveryConfigName string) []common.Fetcher { t.Helper() - fetchers, err := MakeAWSFetchers(context.Background(), clients, matchers, discoveryConfig) + fetchers, err := MakeAWSFetchers(context.Background(), clients, matchers, discoveryConfigName) require.NoError(t, err) require.NotEmpty(t, fetchers) diff --git a/lib/srv/discovery/fetchers/eks.go b/lib/srv/discovery/fetchers/eks.go index a9be660a5601e..eb02b838804e9 100644 --- a/lib/srv/discovery/fetchers/eks.go +++ b/lib/srv/discovery/fetchers/eks.go @@ -87,10 +87,10 @@ type EKSFetcherConfig struct { // Integration is the integration name to be used to fetch credentials. // When present, it will use this integration and discard any local credentials. Integration string - // DiscoveryConfig is the name of the discovery config which originated the resource. + // DiscoveryConfigName is the name of the discovery config which originated the resource. // Might be empty when the fetcher is using static matchers: // ie teleport.yaml/discovery_service.. - DiscoveryConfig string + DiscoveryConfigName string // KubeAppDiscovery specifies if Kubernetes App Discovery should be enabled for the // discovered cluster. We don't use this information for fetching itself, but we need it for // correct enrollment of the clusters returned from this fetcher. @@ -133,7 +133,7 @@ func (c *EKSFetcherConfig) CheckAndSetDefaults() error { // MakeEKSFetchersFromAWSMatchers creates fetchers from the provided matchers. Returned fetchers are separated // by their reliance on the integration. -func MakeEKSFetchersFromAWSMatchers(log logrus.FieldLogger, clients cloud.AWSClients, matchers []types.AWSMatcher, discoveryConfig string) (kubeFetchers []common.Fetcher, _ error) { +func MakeEKSFetchersFromAWSMatchers(log logrus.FieldLogger, clients cloud.AWSClients, matchers []types.AWSMatcher, discoveryConfigName string) (kubeFetchers []common.Fetcher, _ error) { for _, matcher := range matchers { var matcherAssumeRole types.AssumeRole if matcher.AssumeRole != nil { @@ -146,15 +146,15 @@ func MakeEKSFetchersFromAWSMatchers(log logrus.FieldLogger, clients cloud.AWSCli case types.AWSMatcherEKS: fetcher, err := NewEKSFetcher( EKSFetcherConfig{ - ClientGetter: clients, - AssumeRole: matcherAssumeRole, - Region: region, - Integration: matcher.Integration, - KubeAppDiscovery: matcher.KubeAppDiscovery, - FilterLabels: matcher.Tags, - Log: log, - SetupAccessForARN: matcher.SetupAccessForARN, - DiscoveryConfig: discoveryConfig, + ClientGetter: clients, + AssumeRole: matcherAssumeRole, + Region: region, + Integration: matcher.Integration, + KubeAppDiscovery: matcher.KubeAppDiscovery, + FilterLabels: matcher.Tags, + Log: log, + SetupAccessForARN: matcher.SetupAccessForARN, + DiscoveryConfigName: discoveryConfigName, }, ) if err != nil { @@ -329,8 +329,8 @@ func (a *eksFetcher) IntegrationName() string { return a.Integration } -func (a *eksFetcher) DiscoveryConfigName() string { - return a.DiscoveryConfig +func (a *eksFetcher) GetDiscoveryConfigName() string { + return a.DiscoveryConfigName } func (a *eksFetcher) String() string { diff --git a/lib/srv/discovery/fetchers/gke.go b/lib/srv/discovery/fetchers/gke.go index 32f02863b6e6a..9a94a663c2a47 100644 --- a/lib/srv/discovery/fetchers/gke.go +++ b/lib/srv/discovery/fetchers/gke.go @@ -48,8 +48,8 @@ type GKEFetcherConfig struct { FilterLabels types.Labels // Log is the logger. Log logrus.FieldLogger - // DiscoveryConfig is the name of the discovery config which originated the resource. - DiscoveryConfig string + // DiscoveryConfigName is the name of the discovery config which originated the resource. + DiscoveryConfigName string } // CheckAndSetDefaults validates and sets the defaults values. @@ -154,8 +154,8 @@ func (a *gkeFetcher) IntegrationName() string { // There is currently no integration that supports Auto Discover for GCP resources. return "" } -func (a *gkeFetcher) DiscoveryConfigName() string { - return a.DiscoveryConfig +func (a *gkeFetcher) GetDiscoveryConfigName() string { + return a.DiscoveryConfigName } func (a *gkeFetcher) String() string { diff --git a/lib/srv/discovery/fetchers/kube_services.go b/lib/srv/discovery/fetchers/kube_services.go index 3574e0a31a851..bc44a9c5cc153 100644 --- a/lib/srv/discovery/fetchers/kube_services.go +++ b/lib/srv/discovery/fetchers/kube_services.go @@ -54,8 +54,8 @@ type KubeAppsFetcherConfig struct { Log logrus.FieldLogger // ProtocolChecker inspects port to find your whether they are HTTP/HTTPS or not. ProtocolChecker ProtocolChecker - // DiscoveryConfig is the name of the discovery config which originated the resource. - DiscoveryConfig string + // DiscoveryConfigName is the name of the discovery config which originated the resource. + DiscoveryConfigName string } // CheckAndSetDefaults validates and sets the defaults values. @@ -240,8 +240,8 @@ func (f *KubeAppFetcher) IntegrationName() string { return "" } -func (f *KubeAppFetcher) DiscoveryConfigName() string { - return f.DiscoveryConfig +func (f *KubeAppFetcher) GetDiscoveryConfigName() string { + return f.DiscoveryConfigName } func (f *KubeAppFetcher) FetcherType() string { diff --git a/lib/srv/discovery/kube_integration_watcher.go b/lib/srv/discovery/kube_integration_watcher.go index f16cc549d6727..3ecb000a8edad 100644 --- a/lib/srv/discovery/kube_integration_watcher.go +++ b/lib/srv/discovery/kube_integration_watcher.go @@ -35,6 +35,7 @@ import ( "github.com/gravitational/teleport/lib/automaticupgrades" kubeutils "github.com/gravitational/teleport/lib/kube/utils" "github.com/gravitational/teleport/lib/srv/discovery/common" + libslices "github.com/gravitational/teleport/lib/utils/slices" ) // startKubeIntegrationWatchers starts kube watchers that use integration for the credentials. Currently only @@ -76,6 +77,14 @@ func (s *Server) startKubeIntegrationWatchers() error { Origin: types.OriginCloud, TriggerFetchC: s.newDiscoveryConfigChangedSub(), PreFetchHookFn: func() { + discoveryConfigs := libslices.FilterMapUnique( + s.getKubeIntegrationFetchers(), + func(f common.Fetcher) (s string, include bool) { + return f.GetDiscoveryConfigName(), f.GetDiscoveryConfigName() != "" + }, + ) + s.updateDiscoveryConfigStatus(discoveryConfigs...) + s.awsEKSResourcesStatus.reset() s.awsEKSTasks.reset() }, @@ -125,7 +134,7 @@ func (s *Server) startKubeIntegrationWatchers() error { resourceGroup := awsResourceGroupFromLabels(newCluster.GetStaticLabels()) resourcesFoundByGroup[resourceGroup] += 1 - discoveryConfigsChanged[resourceGroup.discoveryConfig] = struct{}{} + discoveryConfigsChanged[resourceGroup.discoveryConfigName] = struct{}{} if enrollingClusters[newCluster.GetAWSConfig().Name] || slices.ContainsFunc(existingServers, func(c types.KubeServer) bool { return c.GetName() == newCluster.GetName() }) || @@ -149,16 +158,16 @@ func (s *Server) startKubeIntegrationWatchers() error { // When enrolling EKS clusters, client for enrollment depends on the region and integration used. type regionIntegrationMapKey struct { - region string - integration string - discoveryConfig string + region string + integration string + discoveryConfigName string } clustersByRegionAndIntegration := map[regionIntegrationMapKey][]types.DiscoveredEKSCluster{} for _, c := range newClusters { mapKey := regionIntegrationMapKey{ - region: c.GetAWSConfig().Region, - integration: c.GetIntegration(), - discoveryConfig: c.GetStaticLabels()[types.TeleportInternalDiscoveryConfigName], + region: c.GetAWSConfig().Region, + integration: c.GetIntegration(), + discoveryConfigName: c.GetStaticLabels()[types.TeleportInternalDiscoveryConfigName], } clustersByRegionAndIntegration[mapKey] = append(clustersByRegionAndIntegration[mapKey], c) @@ -166,7 +175,7 @@ func (s *Server) startKubeIntegrationWatchers() error { for key, val := range clustersByRegionAndIntegration { key, val := key, val - go s.enrollEKSClusters(key.region, key.integration, key.discoveryConfig, val, agentVersion, &mu, enrollingClusters) + go s.enrollEKSClusters(key.region, key.integration, key.discoveryConfigName, val, agentVersion, &mu, enrollingClusters) } case <-s.ctx.Done(): @@ -185,7 +194,7 @@ func (s *Server) startKubeIntegrationWatchers() error { return nil } -func (s *Server) enrollEKSClusters(region, integration, discoveryConfig string, clusters []types.DiscoveredEKSCluster, agentVersion string, mu *sync.Mutex, enrollingClusters map[string]bool) { +func (s *Server) enrollEKSClusters(region, integration, discoveryConfigName string, clusters []types.DiscoveredEKSCluster, agentVersion string, mu *sync.Mutex, enrollingClusters map[string]bool) { mu.Lock() for _, c := range clusters { if _, ok := enrollingClusters[c.GetAWSConfig().Name]; !ok { @@ -201,7 +210,7 @@ func (s *Server) enrollEKSClusters(region, integration, discoveryConfig string, } mu.Unlock() - s.updateDiscoveryConfigStatus(discoveryConfig) + s.updateDiscoveryConfigStatus(discoveryConfigName) s.upsertTasksForAWSEKSFailedEnrollments() }() @@ -234,8 +243,8 @@ func (s *Server) enrollEKSClusters(region, integration, discoveryConfig string, }) if err != nil { s.awsEKSResourcesStatus.incrementFailed(awsResourceGroup{ - discoveryConfig: discoveryConfig, - integration: integration, + discoveryConfigName: discoveryConfigName, + integration: integration, }, len(clusterNames)) s.Log.ErrorContext(ctx, "Failed to enroll EKS clusters", "cluster_names", clusterNames, "error", err) continue @@ -244,8 +253,8 @@ func (s *Server) enrollEKSClusters(region, integration, discoveryConfig string, for _, r := range rsp.Results { if r.Error != "" { s.awsEKSResourcesStatus.incrementFailed(awsResourceGroup{ - discoveryConfig: discoveryConfig, - integration: integration, + discoveryConfigName: discoveryConfigName, + integration: integration, }, 1) if !strings.Contains(r.Error, "teleport-kube-agent is already installed on the cluster") { s.Log.ErrorContext(ctx, "Failed to enroll EKS cluster", "cluster_name", r.EksClusterName, "issue_type", r.IssueType, "error", r.Error) @@ -263,7 +272,7 @@ func (s *Server) enrollEKSClusters(region, integration, discoveryConfig string, appAutoDiscover: kubeAppDiscovery, }, &usertasksv1.DiscoverEKSCluster{ - DiscoveryConfig: discoveryConfig, + DiscoveryConfig: discoveryConfigName, DiscoveryGroup: s.DiscoveryGroup, SyncTime: timestamppb.New(s.clock.Now()), Name: cluster.GetAWSConfig().Name, diff --git a/lib/srv/discovery/status.go b/lib/srv/discovery/status.go index e2f6841843bf8..2d168c5aea776 100644 --- a/lib/srv/discovery/status.go +++ b/lib/srv/discovery/status.go @@ -45,40 +45,42 @@ import ( // - AWS EC2 Auto Discover status // - AWS RDS Auto Discover status // - AWS EKS Auto Discover status -func (s *Server) updateDiscoveryConfigStatus(discoveryConfigName string) { - // Static configurations (ie those in `teleport.yaml/discovery_config..matchers`) do not have a DiscoveryConfig resource. - // Those are discarded because there's no Status to update. - if discoveryConfigName == "" { - return - } +func (s *Server) updateDiscoveryConfigStatus(discoveryConfigNames ...string) { + for _, discoveryConfigName := range discoveryConfigNames { + // Static configurations (ie those in `teleport.yaml/discovery_config..matchers`) do not have a DiscoveryConfig resource. + // Those are discarded because there's no Status to update. + if discoveryConfigName == "" { + return + } - discoveryConfigStatus := discoveryconfig.Status{ - State: discoveryconfigv1.DiscoveryConfigState_DISCOVERY_CONFIG_STATE_SYNCING.String(), - LastSyncTime: s.clock.Now(), - IntegrationDiscoveredResources: make(map[string]*discoveryconfigv1.IntegrationDiscoveredSummary), - } + discoveryConfigStatus := discoveryconfig.Status{ + State: discoveryconfigv1.DiscoveryConfigState_DISCOVERY_CONFIG_STATE_SYNCING.String(), + LastSyncTime: s.clock.Now(), + IntegrationDiscoveredResources: make(map[string]*discoveryconfigv1.IntegrationDiscoveredSummary), + } - // Merge AWS Sync (TAG) status - discoveryConfigStatus = s.awsSyncStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) + // Merge AWS Sync (TAG) status + discoveryConfigStatus = s.awsSyncStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) - // Merge AWS EC2 Instances (auto discovery) status - discoveryConfigStatus = s.awsEC2ResourcesStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) + // Merge AWS EC2 Instances (auto discovery) status + discoveryConfigStatus = s.awsEC2ResourcesStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) - // Merge AWS RDS databases (auto discovery) status - discoveryConfigStatus = s.awsRDSResourcesStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) + // Merge AWS RDS databases (auto discovery) status + discoveryConfigStatus = s.awsRDSResourcesStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) - // Merge AWS EKS clusters (auto discovery) status - discoveryConfigStatus = s.awsEKSResourcesStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) + // Merge AWS EKS clusters (auto discovery) status + discoveryConfigStatus = s.awsEKSResourcesStatus.mergeIntoGlobalStatus(discoveryConfigName, discoveryConfigStatus) - ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second) - defer cancel() + ctx, cancel := context.WithTimeout(s.ctx, 5*time.Second) + defer cancel() - _, err := s.AccessPoint.UpdateDiscoveryConfigStatus(ctx, discoveryConfigName, discoveryConfigStatus) - switch { - case trace.IsNotImplemented(err): - s.Log.WarnContext(ctx, "UpdateDiscoveryConfigStatus method is not implemented in Auth Server. Please upgrade it to a recent version.") - case err != nil: - s.Log.InfoContext(ctx, "Error updating discovery config status", "discovery_config_name", discoveryConfigName, "error", err) + _, err := s.AccessPoint.UpdateDiscoveryConfigStatus(ctx, discoveryConfigName, discoveryConfigStatus) + switch { + case trace.IsNotImplemented(err): + s.Log.WarnContext(ctx, "UpdateDiscoveryConfigStatus method is not implemented in Auth Server. Please upgrade it to a recent version.") + case err != nil: + s.Log.InfoContext(ctx, "Error updating discovery config status", "discovery_config_name", discoveryConfigName, "error", err) + } } } @@ -220,14 +222,14 @@ type awsResourcesStatus struct { // awsResourceGroup is the key for the summary type awsResourceGroup struct { - discoveryConfig string - integration string + discoveryConfigName string + integration string } func awsResourceGroupFromLabels(labels map[string]string) awsResourceGroup { return awsResourceGroup{ - discoveryConfig: labels[types.TeleportInternalDiscoveryConfigName], - integration: labels[types.TeleportInternalDiscoveryIntegrationName], + discoveryConfigName: labels[types.TeleportInternalDiscoveryConfigName], + integration: labels[types.TeleportInternalDiscoveryIntegrationName], } } @@ -250,7 +252,7 @@ func (ars *awsResourcesStatus) mergeIntoGlobalStatus(discoveryConfigName string, defer ars.mu.RUnlock() for group, groupResult := range ars.awsResourcesResults { - if group.discoveryConfig != discoveryConfigName { + if group.discoveryConfigName != discoveryConfigName { continue } @@ -331,11 +333,11 @@ func (s *Server) ReportEC2SSMInstallationResult(ctx context.Context, result *ser } s.awsEC2ResourcesStatus.incrementFailed(awsResourceGroup{ - discoveryConfig: result.DiscoveryConfig, - integration: result.IntegrationName, + discoveryConfigName: result.DiscoveryConfigName, + integration: result.IntegrationName, }, 1) - s.updateDiscoveryConfigStatus(result.DiscoveryConfig) + s.updateDiscoveryConfigStatus(result.DiscoveryConfigName) s.awsEC2Tasks.addFailedEnrollment( awsEC2TaskKey{ @@ -348,7 +350,7 @@ func (s *Server) ReportEC2SSMInstallationResult(ctx context.Context, result *ser }, &usertasksv1.DiscoverEC2Instance{ InvocationUrl: result.SSMRunEvent.InvocationURL, - DiscoveryConfig: result.DiscoveryConfig, + DiscoveryConfig: result.DiscoveryConfigName, DiscoveryGroup: s.DiscoveryGroup, SyncTime: timestamppb.New(result.SSMRunEvent.Time), InstanceId: result.SSMRunEvent.InstanceID, diff --git a/lib/srv/server/azure_watcher.go b/lib/srv/server/azure_watcher.go index 4534942c6dff8..fda04125ae7e1 100644 --- a/lib/srv/server/azure_watcher.go +++ b/lib/srv/server/azure_watcher.go @@ -96,16 +96,17 @@ func NewAzureWatcher(ctx context.Context, fetchersFn func() []Fetcher, opts ...O } // MatchersToAzureInstanceFetchers converts a list of Azure VM Matchers into a list of Azure VM Fetchers. -func MatchersToAzureInstanceFetchers(matchers []types.AzureMatcher, clients azureClientGetter) []Fetcher { +func MatchersToAzureInstanceFetchers(matchers []types.AzureMatcher, clients azureClientGetter, discoveryConfigName string) []Fetcher { ret := make([]Fetcher, 0) for _, matcher := range matchers { for _, subscription := range matcher.Subscriptions { for _, resourceGroup := range matcher.ResourceGroups { fetcher := newAzureInstanceFetcher(azureFetcherConfig{ - Matcher: matcher, - Subscription: subscription, - ResourceGroup: resourceGroup, - AzureClientGetter: clients, + Matcher: matcher, + Subscription: subscription, + ResourceGroup: resourceGroup, + AzureClientGetter: clients, + DiscoveryConfigName: discoveryConfigName, }) ret = append(ret, fetcher) } @@ -115,29 +116,32 @@ func MatchersToAzureInstanceFetchers(matchers []types.AzureMatcher, clients azur } type azureFetcherConfig struct { - Matcher types.AzureMatcher - Subscription string - ResourceGroup string - AzureClientGetter azureClientGetter + Matcher types.AzureMatcher + Subscription string + ResourceGroup string + AzureClientGetter azureClientGetter + DiscoveryConfigName string } type azureInstanceFetcher struct { - AzureClientGetter azureClientGetter - Regions []string - Subscription string - ResourceGroup string - Labels types.Labels - Parameters map[string]string - ClientID string + AzureClientGetter azureClientGetter + Regions []string + Subscription string + ResourceGroup string + Labels types.Labels + Parameters map[string]string + ClientID string + DiscoveryConfigName string } func newAzureInstanceFetcher(cfg azureFetcherConfig) *azureInstanceFetcher { ret := &azureInstanceFetcher{ - AzureClientGetter: cfg.AzureClientGetter, - Regions: cfg.Matcher.Regions, - Subscription: cfg.Subscription, - ResourceGroup: cfg.ResourceGroup, - Labels: cfg.Matcher.ResourceTags, + AzureClientGetter: cfg.AzureClientGetter, + Regions: cfg.Matcher.Regions, + Subscription: cfg.Subscription, + ResourceGroup: cfg.ResourceGroup, + Labels: cfg.Matcher.ResourceTags, + DiscoveryConfigName: cfg.DiscoveryConfigName, } if cfg.Matcher.Params != nil { @@ -156,6 +160,10 @@ func (*azureInstanceFetcher) GetMatchingInstances(_ []types.Server, _ bool) ([]I return nil, trace.NotImplemented("not implemented for azure fetchers") } +func (f *azureInstanceFetcher) GetDiscoveryConfigName() string { + return f.DiscoveryConfigName +} + // GetInstances fetches all Azure virtual machines matching configured filters. func (f *azureInstanceFetcher) GetInstances(ctx context.Context, _ bool) ([]Instances, error) { client, err := f.AzureClientGetter.GetAzureVirtualMachinesClient(f.Subscription) diff --git a/lib/srv/server/azure_watcher_test.go b/lib/srv/server/azure_watcher_test.go index e73ce52667fa5..0c9a183b1fc89 100644 --- a/lib/srv/server/azure_watcher_test.go +++ b/lib/srv/server/azure_watcher_test.go @@ -146,7 +146,7 @@ func TestAzureWatcher(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) t.Cleanup(cancel) watcher, err := NewAzureWatcher(ctx, func() []Fetcher { - return MatchersToAzureInstanceFetchers([]types.AzureMatcher{tc.matcher}, &clients) + return MatchersToAzureInstanceFetchers([]types.AzureMatcher{tc.matcher}, &clients, "" /* discovery config */) }) require.NoError(t, err) diff --git a/lib/srv/server/ec2_watcher.go b/lib/srv/server/ec2_watcher.go index 4c6300e7bf661..5c61ff178210f 100644 --- a/lib/srv/server/ec2_watcher.go +++ b/lib/srv/server/ec2_watcher.go @@ -67,9 +67,9 @@ type EC2Instances struct { // Might be empty for instances that didn't use an Integration. Integration string - // DiscoveryConfig is the DiscoveryConfig name which originated this Run Request. + // DiscoveryConfigName is the DiscoveryConfig name which originated this Run Request. // Empty if using static matchers (coming from the `teleport.yaml`). - DiscoveryConfig string + DiscoveryConfigName string // EnrollMode is the mode used to enroll the instance into Teleport. EnrollMode types.InstallParamEnrollMode @@ -192,7 +192,7 @@ func NewEC2Watcher(ctx context.Context, fetchersFn func() []Fetcher, missedRotat type EC2ClientGetter func(ctx context.Context, region string, opts ...config.AWSOptionsFn) (ec2.DescribeInstancesAPIClient, error) // MatchersToEC2InstanceFetchers converts a list of AWS EC2 Matchers into a list of AWS EC2 Fetchers. -func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatcher, getEC2Client EC2ClientGetter, discoveryConfig string) ([]Fetcher, error) { +func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatcher, getEC2Client EC2ClientGetter, discoveryConfigName string) ([]Fetcher, error) { ret := []Fetcher{} for _, matcher := range matchers { for _, region := range matcher.Regions { @@ -205,14 +205,14 @@ func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatc } fetcher := newEC2InstanceFetcher(ec2FetcherConfig{ - Matcher: matcher, - Region: region, - Document: matcher.SSM.DocumentName, - EC2Client: ec2Client, - Labels: matcher.Tags, - Integration: matcher.Integration, - DiscoveryConfig: discoveryConfig, - EnrollMode: matcher.Params.EnrollMode, + Matcher: matcher, + Region: region, + Document: matcher.SSM.DocumentName, + EC2Client: ec2Client, + Labels: matcher.Tags, + Integration: matcher.Integration, + DiscoveryConfigName: discoveryConfigName, + EnrollMode: matcher.Params.EnrollMode, }) ret = append(ret, fetcher) } @@ -221,25 +221,25 @@ func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatc } type ec2FetcherConfig struct { - Matcher types.AWSMatcher - Region string - Document string - EC2Client ec2.DescribeInstancesAPIClient - Labels types.Labels - Integration string - DiscoveryConfig string - EnrollMode types.InstallParamEnrollMode + Matcher types.AWSMatcher + Region string + Document string + EC2Client ec2.DescribeInstancesAPIClient + Labels types.Labels + Integration string + DiscoveryConfigName string + EnrollMode types.InstallParamEnrollMode } type ec2InstanceFetcher struct { - Filters []ec2types.Filter - EC2 ec2.DescribeInstancesAPIClient - Region string - DocumentName string - Parameters map[string]string - Integration string - DiscoveryConfig string - EnrollMode types.InstallParamEnrollMode + Filters []ec2types.Filter + EC2 ec2.DescribeInstancesAPIClient + Region string + DocumentName string + Parameters map[string]string + Integration string + DiscoveryConfigName string + EnrollMode types.InstallParamEnrollMode // cachedInstances keeps all of the ec2 instances that were matched // in the last run of GetInstances for use as a cache with @@ -325,14 +325,14 @@ func newEC2InstanceFetcher(cfg ec2FetcherConfig) *ec2InstanceFetcher { } fetcherConfig := ec2InstanceFetcher{ - EC2: cfg.EC2Client, - Filters: tagFilters, - Region: cfg.Region, - DocumentName: cfg.Document, - Parameters: parameters, - Integration: cfg.Integration, - DiscoveryConfig: cfg.DiscoveryConfig, - EnrollMode: cfg.EnrollMode, + EC2: cfg.EC2Client, + Filters: tagFilters, + Region: cfg.Region, + DocumentName: cfg.Document, + Parameters: parameters, + Integration: cfg.Integration, + DiscoveryConfigName: cfg.DiscoveryConfigName, + EnrollMode: cfg.EnrollMode, cachedInstances: &instancesCache{ instances: map[cachedInstanceKey]struct{}{}, }, @@ -343,12 +343,12 @@ func newEC2InstanceFetcher(cfg ec2FetcherConfig) *ec2InstanceFetcher { // GetMatchingInstances returns a list of EC2 instances from a list of matching Teleport nodes func (f *ec2InstanceFetcher) GetMatchingInstances(nodes []types.Server, rotation bool) ([]Instances, error) { insts := EC2Instances{ - Region: f.Region, - DocumentName: f.DocumentName, - Parameters: f.Parameters, - Rotation: rotation, - Integration: f.Integration, - DiscoveryConfig: f.DiscoveryConfig, + Region: f.Region, + DocumentName: f.DocumentName, + Parameters: f.Parameters, + Rotation: rotation, + Integration: f.Integration, + DiscoveryConfigName: f.DiscoveryConfigName, } for _, node := range nodes { // Heartbeating and expiration keeps Teleport Agents up to date, no need to consider those nodes. @@ -396,14 +396,14 @@ func chunkInstances(insts EC2Instances) []Instances { end = len(insts.Instances) } inst := EC2Instances{ - AccountID: insts.AccountID, - Region: insts.Region, - DocumentName: insts.DocumentName, - Parameters: insts.Parameters, - Instances: insts.Instances[i:end], - Rotation: insts.Rotation, - Integration: insts.Integration, - DiscoveryConfig: insts.DiscoveryConfig, + AccountID: insts.AccountID, + Region: insts.Region, + DocumentName: insts.DocumentName, + Parameters: insts.Parameters, + Instances: insts.Instances[i:end], + Rotation: insts.Rotation, + Integration: insts.Integration, + DiscoveryConfigName: insts.DiscoveryConfigName, } instColl = append(instColl, Instances{EC2: &inst}) } @@ -432,15 +432,15 @@ func (f *ec2InstanceFetcher) GetInstances(ctx context.Context, rotation bool) ([ } ownerID := aws.ToString(res.OwnerId) inst := EC2Instances{ - AccountID: ownerID, - Region: f.Region, - DocumentName: f.DocumentName, - Instances: ToEC2Instances(res.Instances[i:end]), - Parameters: f.Parameters, - Rotation: rotation, - Integration: f.Integration, - DiscoveryConfig: f.DiscoveryConfig, - EnrollMode: f.EnrollMode, + AccountID: ownerID, + Region: f.Region, + DocumentName: f.DocumentName, + Instances: ToEC2Instances(res.Instances[i:end]), + Parameters: f.Parameters, + Rotation: rotation, + Integration: f.Integration, + DiscoveryConfigName: f.DiscoveryConfigName, + EnrollMode: f.EnrollMode, } for _, ec2inst := range res.Instances[i:end] { f.cachedInstances.add(ownerID, aws.ToString(ec2inst.InstanceId)) @@ -456,3 +456,8 @@ func (f *ec2InstanceFetcher) GetInstances(ctx context.Context, rotation bool) ([ return instances, nil } + +// GetDiscoveryConfigName returns the discovery config name that created this fetcher. +func (f *ec2InstanceFetcher) GetDiscoveryConfigName() string { + return f.DiscoveryConfigName +} diff --git a/lib/srv/server/gcp_watcher.go b/lib/srv/server/gcp_watcher.go index 466a85e29fe3b..4b3ddca5ebb98 100644 --- a/lib/srv/server/gcp_watcher.go +++ b/lib/srv/server/gcp_watcher.go @@ -91,14 +91,15 @@ func NewGCPWatcher(ctx context.Context, fetchersFn func() []Fetcher, opts ...Opt } // MatchersToGCPInstanceFetchers converts a list of GCP GCE Matchers into a list of GCP GCE Fetchers. -func MatchersToGCPInstanceFetchers(matchers []types.GCPMatcher, gcpClient gcp.InstancesClient, projectsClient gcp.ProjectsClient) []Fetcher { +func MatchersToGCPInstanceFetchers(matchers []types.GCPMatcher, gcpClient gcp.InstancesClient, projectsClient gcp.ProjectsClient, discoveryConfigName string) []Fetcher { fetchers := make([]Fetcher, 0, len(matchers)) for _, matcher := range matchers { fetchers = append(fetchers, newGCPInstanceFetcher(gcpFetcherConfig{ - Matcher: matcher, - GCPClient: gcpClient, - projectsClient: projectsClient, + Matcher: matcher, + GCPClient: gcpClient, + projectsClient: projectsClient, + DiscoveryConfigName: discoveryConfigName, })) } @@ -106,20 +107,22 @@ func MatchersToGCPInstanceFetchers(matchers []types.GCPMatcher, gcpClient gcp.In } type gcpFetcherConfig struct { - Matcher types.GCPMatcher - GCPClient gcp.InstancesClient - projectsClient gcp.ProjectsClient + Matcher types.GCPMatcher + GCPClient gcp.InstancesClient + projectsClient gcp.ProjectsClient + DiscoveryConfigName string } type gcpInstanceFetcher struct { - GCP gcp.InstancesClient - ProjectIDs []string - Zones []string - ProjectID string - ServiceAccounts []string - Labels types.Labels - Parameters map[string]string - projectsClient gcp.ProjectsClient + GCP gcp.InstancesClient + ProjectIDs []string + Zones []string + ProjectID string + ServiceAccounts []string + Labels types.Labels + Parameters map[string]string + projectsClient gcp.ProjectsClient + DiscoveryConfigName string } func newGCPInstanceFetcher(cfg gcpFetcherConfig) *gcpInstanceFetcher { @@ -145,6 +148,10 @@ func (*gcpInstanceFetcher) GetMatchingInstances(_ []types.Server, _ bool) ([]Ins return nil, trace.NotImplemented("not implemented for gcp fetchers") } +func (f *gcpInstanceFetcher) GetDiscoveryConfigName() string { + return f.DiscoveryConfigName +} + // GetInstances fetches all GCP virtual machines matching configured filters. func (f *gcpInstanceFetcher) GetInstances(ctx context.Context, _ bool) ([]Instances, error) { // Key by project ID, then by zone. diff --git a/lib/srv/server/ssm_install.go b/lib/srv/server/ssm_install.go index 51943f4400058..259da98b246e9 100644 --- a/lib/srv/server/ssm_install.go +++ b/lib/srv/server/ssm_install.go @@ -78,9 +78,9 @@ type SSMInstallationResult struct { // IntegrationName is the integration name when using integration credentials. // Empty if using ambient credentials. IntegrationName string - // DiscoveryConfig is the DiscoveryConfig name which originated this Run Request. + // DiscoveryConfigName is the DiscoveryConfig name which originated this Run Request. // Empty if using static matchers (coming from the `teleport.yaml`). - DiscoveryConfig string + DiscoveryConfigName string // IssueType identifies the type of issue that occurred if the installation failed. // These are well known identifiers that can be found at types.AutoDiscoverEC2Issue*. IssueType string @@ -118,9 +118,9 @@ type SSMRunRequest struct { // IntegrationName is the integration name when using integration credentials. // Empty if using ambient credentials. IntegrationName string - // DiscoveryConfig is the DiscoveryConfig name which originated this Run Request. + // DiscoveryConfigName is the DiscoveryConfig name which originated this Run Request. // Empty if using static matchers (coming from the `teleport.yaml`). - DiscoveryConfig string + DiscoveryConfigName string } // InstallerScriptName returns the Teleport Installer script name. @@ -255,12 +255,12 @@ func invalidSSMInstanceInstallationResult(req SSMRunRequest, instanceID, instanc InstanceID: instanceID, Status: status, }, - IntegrationName: req.IntegrationName, - DiscoveryConfig: req.DiscoveryConfig, - IssueType: issueType, - SSMDocumentName: req.DocumentName, - InstallerScript: req.InstallerScriptName(), - InstanceName: instanceName, + IntegrationName: req.IntegrationName, + DiscoveryConfigName: req.DiscoveryConfigName, + IssueType: issueType, + SSMDocumentName: req.DocumentName, + InstallerScript: req.InstallerScriptName(), + InstanceName: instanceName, } } @@ -413,13 +413,13 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com } return trace.Wrap(si.ReportSSMInstallationResultFunc(ctx, &SSMInstallationResult{ - SSMRunEvent: invocationResultEvent, - IntegrationName: req.IntegrationName, - DiscoveryConfig: req.DiscoveryConfig, - IssueType: usertasks.AutoDiscoverEC2IssueSSMScriptFailure, - SSMDocumentName: req.DocumentName, - InstallerScript: req.InstallerScriptName(), - InstanceName: instanceName, + SSMRunEvent: invocationResultEvent, + IntegrationName: req.IntegrationName, + DiscoveryConfigName: req.DiscoveryConfigName, + IssueType: usertasks.AutoDiscoverEC2IssueSSMScriptFailure, + SSMDocumentName: req.DocumentName, + InstallerScript: req.InstallerScriptName(), + InstanceName: instanceName, })) } @@ -430,13 +430,13 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com lastStep := i+1 == len(invocationSteps) if stepResultEvent.Metadata.Code != libevents.SSMRunSuccessCode || lastStep { return trace.Wrap(si.ReportSSMInstallationResultFunc(ctx, &SSMInstallationResult{ - SSMRunEvent: stepResultEvent, - IntegrationName: req.IntegrationName, - DiscoveryConfig: req.DiscoveryConfig, - IssueType: usertasks.AutoDiscoverEC2IssueSSMScriptFailure, - SSMDocumentName: req.DocumentName, - InstallerScript: req.InstallerScriptName(), - InstanceName: instanceName, + SSMRunEvent: stepResultEvent, + IntegrationName: req.IntegrationName, + DiscoveryConfigName: req.DiscoveryConfigName, + IssueType: usertasks.AutoDiscoverEC2IssueSSMScriptFailure, + SSMDocumentName: req.DocumentName, + InstallerScript: req.InstallerScriptName(), + InstanceName: instanceName, })) } } diff --git a/lib/srv/server/ssm_install_test.go b/lib/srv/server/ssm_install_test.go index 102bcbf5a4475..e7f498f9edbf6 100644 --- a/lib/srv/server/ssm_install_test.go +++ b/lib/srv/server/ssm_install_test.go @@ -103,12 +103,12 @@ func TestSSMInstaller(t *testing.T) { Instances: []EC2Instance{ {InstanceID: "instance-id-1", InstanceName: "my-instance-name"}, }, - DocumentName: document, - Params: map[string]string{"token": "abcdefg"}, - IntegrationName: "aws-integration", - DiscoveryConfig: "dc001", - Region: "eu-central-1", - AccountID: "account-id", + DocumentName: document, + Params: map[string]string{"token": "abcdefg"}, + IntegrationName: "aws-integration", + DiscoveryConfigName: "dc001", + Region: "eu-central-1", + AccountID: "account-id", }, client: &mockSSMClient{ commandOutput: &ssm.SendCommandOutput{ @@ -128,8 +128,8 @@ func TestSSMInstaller(t *testing.T) { }, }, expectedInstallations: []*SSMInstallationResult{{ - IntegrationName: "aws-integration", - DiscoveryConfig: "dc001", + IntegrationName: "aws-integration", + DiscoveryConfigName: "dc001", SSMRunEvent: &events.SSMRun{ Metadata: events.Metadata{ Type: libevent.SSMRunEvent, diff --git a/lib/srv/server/watcher.go b/lib/srv/server/watcher.go index 437fb90fed660..8fa5de1ee9a90 100644 --- a/lib/srv/server/watcher.go +++ b/lib/srv/server/watcher.go @@ -42,6 +42,9 @@ type Fetcher interface { // GetMatchingInstances finds Instances from the list of nodes // that the fetcher matches. GetMatchingInstances(nodes []types.Server, rotation bool) ([]Instances, error) + // GetDiscoveryConfigName returns the DiscoveryConfig name that created this fetcher. + // Empty for Fetchers created from `teleport.yaml/discovery_service.aws.` matchers. + GetDiscoveryConfigName() string } // WithTriggerFetchC sets a poll trigger to manual start a resource polling. diff --git a/lib/utils/slices/slices.go b/lib/utils/slices/slices.go new file mode 100644 index 0000000000000..c3d3fcb0a4496 --- /dev/null +++ b/lib/utils/slices/slices.go @@ -0,0 +1,38 @@ +/* + * Teleport + * Copyright (C) 2024 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 slices + +import ( + "cmp" + "slices" +) + +// FilterMapUnique applies a function to all elements of a slice and collects them. +// The function returns the value to collect and whether the current element should be included. +// Returned values are sorted and deduplicated. +func FilterMapUnique[T any, S cmp.Ordered](ts []T, fn func(T) (s S, include bool)) []S { + ss := make([]S, 0, len(ts)) + for _, t := range ts { + if s, include := fn(t); include { + ss = append(ss, s) + } + } + slices.Sort(ss) + return slices.Compact(ss) +} diff --git a/lib/utils/slices/slices_test.go b/lib/utils/slices/slices_test.go new file mode 100644 index 0000000000000..6591880c0ff27 --- /dev/null +++ b/lib/utils/slices/slices_test.go @@ -0,0 +1,72 @@ +/* + * Teleport + * Copyright (C) 2024 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 slices + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilterMapUnique(t *testing.T) { + for _, tt := range []struct { + name string + input []string + collector func(string) (s string, include bool) + expected []string + }{ + { + name: "no elements", + input: []string{}, + collector: func(in string) (s string, include bool) { + return in, true + }, + expected: []string{}, + }, + { + name: "multiple strings, all match", + input: []string{"x", "y"}, + collector: func(in string) (s string, include bool) { + return in, true + }, + expected: []string{"x", "y"}, + }, + { + name: "deduplicates items", + input: []string{"x", "y", "z", "x"}, + collector: func(in string) (s string, include bool) { + return in, true + }, + expected: []string{"x", "y", "z"}, + }, + { + name: "not included values are not returned", + input: []string{"x", "y", "z", ""}, + collector: func(in string) (s string, include bool) { + return in, in != "" + }, + expected: []string{"x", "y", "z"}, + }, + } { + t.Run(tt.name, func(t *testing.T) { + got := FilterMapUnique(tt.input, tt.collector) + require.Equal(t, tt.expected, got) + }) + } +}