From 401681d2430eb07b1d42caac577d9da7bddc1640 Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Mon, 16 Dec 2024 17:58:02 +0000 Subject: [PATCH] DiscoveryConfig Status: update even when no resources are found (#49588) * DiscoveryConfig Status: update even when no resources are found The DiscoveryService was not updating the DiscoveryConfigStatus when its matchers didn't discovered any resource. This would lead the user to think that there the DiscoveryConfig was not being processed, when in fact it was. * rename to FilterMapUnique * rename to DiscoveryConfigName and GetDiscoveryConfigName * fix var name --- lib/srv/discovery/common/interfaces.go | 4 +- lib/srv/discovery/common/watcher.go | 4 +- lib/srv/discovery/common/watcher_test.go | 2 +- lib/srv/discovery/database_watcher.go | 11 +- lib/srv/discovery/discovery.go | 131 +++++++++++------- lib/srv/discovery/discovery_test.go | 44 ++++-- lib/srv/discovery/fetchers/aks.go | 6 +- lib/srv/discovery/fetchers/db/aws.go | 8 +- lib/srv/discovery/fetchers/db/azure.go | 10 +- lib/srv/discovery/fetchers/db/db.go | 32 ++--- lib/srv/discovery/fetchers/db/helpers_test.go | 4 +- lib/srv/discovery/fetchers/eks.go | 28 ++-- lib/srv/discovery/fetchers/gke.go | 8 +- lib/srv/discovery/fetchers/kube_services.go | 8 +- lib/srv/discovery/kube_integration_watcher.go | 39 ++++-- lib/srv/discovery/status.go | 74 +++++----- lib/srv/server/azure_watcher.go | 50 ++++--- lib/srv/server/azure_watcher_test.go | 2 +- lib/srv/server/ec2_watcher.go | 121 ++++++++-------- lib/srv/server/gcp_watcher.go | 37 +++-- lib/srv/server/ssm_install.go | 48 +++---- lib/srv/server/ssm_install_test.go | 12 +- lib/srv/server/watcher.go | 3 + lib/utils/slices/slices.go | 38 +++++ lib/utils/slices/slices_test.go | 72 ++++++++++ 25 files changed, 502 insertions(+), 294 deletions(-) create mode 100644 lib/utils/slices/slices.go create mode 100644 lib/utils/slices/slices_test.go 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 24d50a7f86a28..ea784c8b9718a 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -63,11 +63,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. @@ -391,7 +394,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) } @@ -401,11 +404,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) } @@ -470,7 +473,6 @@ func (s *Server) initAWSWatchers(matchers []types.AWSMatcher) error { return matcherType == types.AWSMatcherEC2 }) - const noDiscoveryConfig = "" s.staticServerAWSFetchers, err = server.MatchersToEC2InstanceFetchers(s.ctx, ec2Matchers, s.CloudClients, noDiscoveryConfig) if err != nil { return trace.Wrap(err) @@ -481,6 +483,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() }), @@ -515,7 +525,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) } @@ -556,12 +566,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.CloudClients, discoveryConfig) + fetchers, err := server.MatchersToEC2InstanceFetchers(ctx, serverMatchers, s.CloudClients, discoveryConfigName) if err != nil { return nil, trace.Wrap(err) } @@ -570,16 +580,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 }) @@ -600,17 +610,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) } @@ -620,7 +630,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) } @@ -633,7 +643,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 @@ -641,7 +651,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) } @@ -654,12 +664,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 @@ -667,6 +677,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) @@ -695,11 +714,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) @@ -712,10 +732,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) } @@ -724,6 +744,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) @@ -739,7 +768,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 @@ -747,7 +776,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) } @@ -878,8 +907,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 { @@ -921,8 +950,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 } @@ -975,8 +1004,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) } }) @@ -998,19 +1027,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: ec2Client, - Instances: instances.Instances, - Params: instances.Parameters, - Region: instances.Region, - AccountID: instances.AccountID, - IntegrationName: instances.Integration, - DiscoveryConfig: instances.DiscoveryConfig, + DocumentName: instances.DocumentName, + SSM: ec2Client, + 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 { @@ -1024,7 +1053,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, @@ -1143,15 +1172,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() @@ -1574,7 +1603,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 } } @@ -1649,12 +1680,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 208ddf662d272..9c9ebbee0c788 100644 --- a/lib/srv/discovery/discovery_test.go +++ b/lib/srv/discovery/discovery_test.go @@ -1942,18 +1942,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( @@ -1979,6 +1979,7 @@ func TestDiscoveryDatabase(t *testing.T) { {Engine: aws.String(services.RDSEnginePostgres)}, }, }, + MemoryDB: &mocks.MemoryDBMock{}, Redshift: &mocks.RedshiftMock{ Clusters: []*redshift.Cluster{awsRedshiftResource}, }, @@ -2241,6 +2242,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 { @@ -2359,7 +2381,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{}, @@ -3529,10 +3551,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 @@ -3543,8 +3565,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 4339bfc713713..0138c9c98852a 100644 --- a/lib/srv/server/azure_watcher.go +++ b/lib/srv/server/azure_watcher.go @@ -97,16 +97,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) } @@ -116,29 +117,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 { @@ -157,6 +161,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 6c3989bc91a75..b804a941424e6 100644 --- a/lib/srv/server/azure_watcher_test.go +++ b/lib/srv/server/azure_watcher_test.go @@ -139,7 +139,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 25f12018e0170..23d3f80699dd9 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 @@ -189,7 +189,7 @@ func NewEC2Watcher(ctx context.Context, fetchersFn func() []Fetcher, missedRotat } // MatchersToEC2InstanceFetchers converts a list of AWS EC2 Matchers into a list of AWS EC2 Fetchers. -func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatcher, clients cloud.Clients, discoveryConfig string) ([]Fetcher, error) { +func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatcher, clients cloud.Clients, discoveryConfigName string) ([]Fetcher, error) { ret := []Fetcher{} for _, matcher := range matchers { for _, region := range matcher.Regions { @@ -202,14 +202,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) } @@ -218,25 +218,25 @@ func MatchersToEC2InstanceFetchers(ctx context.Context, matchers []types.AWSMatc } type ec2FetcherConfig struct { - Matcher types.AWSMatcher - Region string - Document string - EC2Client ec2iface.EC2API - Labels types.Labels - Integration string - DiscoveryConfig string - EnrollMode types.InstallParamEnrollMode + Matcher types.AWSMatcher + Region string + Document string + EC2Client ec2iface.EC2API + Labels types.Labels + Integration string + DiscoveryConfigName string + EnrollMode types.InstallParamEnrollMode } type ec2InstanceFetcher struct { - Filters []*ec2.Filter - EC2 ec2iface.EC2API - Region string - DocumentName string - Parameters map[string]string - Integration string - DiscoveryConfig string - EnrollMode types.InstallParamEnrollMode + Filters []*ec2.Filter + EC2 ec2iface.EC2API + 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 @@ -322,14 +322,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{}{}, }, @@ -340,12 +340,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. @@ -393,14 +393,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}) } @@ -423,15 +423,15 @@ func (f *ec2InstanceFetcher) GetInstances(ctx context.Context, rotation bool) ([ } ownerID := aws.StringValue(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.StringValue(ec2inst.InstanceId)) @@ -451,3 +451,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 3c23f672884a3..ffe94703ff6ec 100644 --- a/lib/srv/server/ssm_install.go +++ b/lib/srv/server/ssm_install.go @@ -59,9 +59,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 @@ -99,9 +99,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. @@ -230,12 +230,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, } } @@ -389,13 +389,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, })) } @@ -406,13 +406,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 c56b286258527..77ced2df9b362 100644 --- a/lib/srv/server/ssm_install_test.go +++ b/lib/srv/server/ssm_install_test.go @@ -104,10 +104,10 @@ 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", + DocumentName: document, + Params: map[string]string{"token": "abcdefg"}, + IntegrationName: "aws-integration", + DiscoveryConfigName: "dc001", SSM: &mockSSMClient{ commandOutput: &ssm.SendCommandOutput{ Command: &ssm.Command{ @@ -129,8 +129,8 @@ func TestSSMInstaller(t *testing.T) { AccountID: "account-id", }, 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) + }) + } +}