diff --git a/lib/web/integrations.go b/lib/web/integrations.go index 6705afcf8dc48..d0feb628b8a0c 100644 --- a/lib/web/integrations.go +++ b/lib/web/integrations.go @@ -20,6 +20,7 @@ package web import ( "context" + "log/slog" "net/http" "net/url" "slices" @@ -220,7 +221,14 @@ func (h *Handler) integrationStats(w http.ResponseWriter, r *http.Request, p htt return nil, trace.Wrap(err) } - summary, err := collectAWSOIDCAutoDiscoverStats(r.Context(), ig, clt.DiscoveryConfigClient()) + req := collectIntegrationStatsRequest{ + logger: h.logger, + integration: ig, + discoveryConfigLister: clt.DiscoveryConfigClient(), + databaseGetter: clt, + awsOIDCClient: clt.IntegrationAWSOIDCClient(), + } + summary, err := collectIntegrationStats(r.Context(), req) if err != nil { return nil, trace.Wrap(err) } @@ -228,44 +236,46 @@ func (h *Handler) integrationStats(w http.ResponseWriter, r *http.Request, p htt return summary, nil } -func collectAWSOIDCAutoDiscoverStats( - ctx context.Context, - integration types.Integration, - clt interface { - ListDiscoveryConfigs(ctx context.Context, pageSize int, nextToken string) ([]*discoveryconfig.DiscoveryConfig, string, error) - }, -) (ui.IntegrationWithSummary, error) { - var ret ui.IntegrationWithSummary +type collectIntegrationStatsRequest struct { + logger *slog.Logger + integration types.Integration + discoveryConfigLister discoveryConfigLister + databaseGetter databaseGetter + awsOIDCClient deployedDatabaseServiceLister +} - uiIg, err := ui.MakeIntegration(integration) +func collectIntegrationStats(ctx context.Context, req collectIntegrationStatsRequest) (*ui.IntegrationWithSummary, error) { + ret := &ui.IntegrationWithSummary{} + + uiIg, err := ui.MakeIntegration(req.integration) if err != nil { - return ret, err + return nil, err } ret.Integration = uiIg var nextPage string for { - discoveryConfigs, nextToken, err := clt.ListDiscoveryConfigs(ctx, 0, nextPage) + discoveryConfigs, nextToken, err := req.discoveryConfigLister.ListDiscoveryConfigs(ctx, 0, nextPage) if err != nil { - return ret, trace.Wrap(err) + return nil, trace.Wrap(err) } for _, dc := range discoveryConfigs { - discoveredResources, ok := dc.Status.IntegrationDiscoveredResources[integration.GetName()] + discoveredResources, ok := dc.Status.IntegrationDiscoveredResources[req.integration.GetName()] if !ok { continue } - if matchers := rulesWithIntegration(dc, types.AWSMatcherEC2, integration.GetName()); matchers != 0 { + if matchers := rulesWithIntegration(dc, types.AWSMatcherEC2, req.integration.GetName()); matchers != 0 { ret.AWSEC2.RulesCount += matchers mergeResourceTypeSummary(&ret.AWSEC2, dc.Status.LastSyncTime, discoveredResources.AwsEc2) } - if matchers := rulesWithIntegration(dc, types.AWSMatcherRDS, integration.GetName()); matchers != 0 { + if matchers := rulesWithIntegration(dc, types.AWSMatcherRDS, req.integration.GetName()); matchers != 0 { ret.AWSRDS.RulesCount += matchers mergeResourceTypeSummary(&ret.AWSRDS, dc.Status.LastSyncTime, discoveredResources.AwsRds) } - if matchers := rulesWithIntegration(dc, types.AWSMatcherEKS, integration.GetName()); matchers != 0 { + if matchers := rulesWithIntegration(dc, types.AWSMatcherEKS, req.integration.GetName()); matchers != 0 { ret.AWSEKS.RulesCount += matchers mergeResourceTypeSummary(&ret.AWSEKS, dc.Status.LastSyncTime, discoveredResources.AwsEks) } @@ -277,8 +287,17 @@ func collectAWSOIDCAutoDiscoverStats( nextPage = nextToken } - // TODO(marco): add total number of ECS Database Services. - ret.AWSRDS.ECSDatabaseServiceCount = 0 + regions, err := fetchRelevantAWSRegions(ctx, req.databaseGetter, req.discoveryConfigLister) + if err != nil { + return nil, trace.Wrap(err) + } + + services, err := listDeployedDatabaseServices(ctx, req.logger, req.integration.GetName(), regions, req.awsOIDCClient) + if err != nil { + return nil, trace.Wrap(err) + } + + ret.AWSRDS.ECSDatabaseServiceCount = len(services) return ret, nil } diff --git a/lib/web/integrations_test.go b/lib/web/integrations_test.go index 9719de251d0ec..0ffd4a139c3f7 100644 --- a/lib/web/integrations_test.go +++ b/lib/web/integrations_test.go @@ -27,12 +27,15 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/client/proto" discoveryconfigv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/discoveryconfig/v1" + integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/discoveryconfig" "github.com/gravitational/teleport/api/types/header" "github.com/gravitational/teleport/lib/services" libui "github.com/gravitational/teleport/lib/ui" + "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/web/ui" ) @@ -97,6 +100,8 @@ func TestIntegrationsCreateWithAudience(t *testing.T) { func TestCollectAWSOIDCAutoDiscoverStats(t *testing.T) { ctx := context.Background() + logger := utils.NewSlogLoggerForTests() + integrationName := "my-integration" integration, err := types.NewIntegrationAWSOIDC( types.Metadata{Name: integrationName}, @@ -106,14 +111,33 @@ func TestCollectAWSOIDCAutoDiscoverStats(t *testing.T) { ) require.NoError(t, err) + deployedServiceCommand := buildCommandDeployedDatabaseService(t, true, types.Labels{"vpc": []string{"vpc1", "vpc2"}}) + deployedDatabaseServicesClient := &mockDeployedDatabaseServices{ + integration: "my-integration", + servicesPerRegion: map[string][]*integrationv1.DeployedDatabaseService{ + "us-west-2": dummyDeployedDatabaseServices(1, deployedServiceCommand), + }, + } + t.Run("without discovery configs, returns just the integration", func(t *testing.T) { - clt := &mockDiscoveryConfigsGetter{ + clt := &mockRelevantAWSRegionsClient{ + databaseServices: &proto.ListResourcesResponse{ + Resources: []*proto.PaginatedResource{}, + }, + databases: make([]types.Database, 0), discoveryConfigs: make([]*discoveryconfig.DiscoveryConfig, 0), } - gotSummary, err := collectAWSOIDCAutoDiscoverStats(ctx, integration, clt) + req := collectIntegrationStatsRequest{ + logger: logger, + integration: integration, + discoveryConfigLister: clt, + databaseGetter: clt, + awsOIDCClient: deployedDatabaseServicesClient, + } + gotSummary, err := collectIntegrationStats(ctx, req) require.NoError(t, err) - expectedSummary := ui.IntegrationWithSummary{ + expectedSummary := &ui.IntegrationWithSummary{ Integration: &ui.Integration{ Name: integrationName, SubKind: "aws-oidc", @@ -145,7 +169,7 @@ func TestCollectAWSOIDCAutoDiscoverStats(t *testing.T) { Spec: discoveryconfig.Spec{AWS: []types.AWSMatcher{{ Integration: integrationName, Types: []string{"rds"}, - Regions: []string{"us-east-1", "us-east-2"}, + Regions: []string{"us-east-1", "us-east-2", "us-west-2"}, }}}, Status: discoveryconfig.Status{ LastSyncTime: syncTime, @@ -173,17 +197,26 @@ func TestCollectAWSOIDCAutoDiscoverStats(t *testing.T) { }, }, } - clt := &mockDiscoveryConfigsGetter{ + clt := &mockRelevantAWSRegionsClient{ discoveryConfigs: []*discoveryconfig.DiscoveryConfig{ dcForEC2, dcForRDS, dcForEKS, }, + databaseServices: &proto.ListResourcesResponse{}, + databases: make([]types.Database, 0), } - gotSummary, err := collectAWSOIDCAutoDiscoverStats(ctx, integration, clt) + req := collectIntegrationStatsRequest{ + logger: logger, + integration: integration, + discoveryConfigLister: clt, + databaseGetter: clt, + awsOIDCClient: deployedDatabaseServicesClient, + } + gotSummary, err := collectIntegrationStats(ctx, req) require.NoError(t, err) - expectedSummary := ui.IntegrationWithSummary{ + expectedSummary := &ui.IntegrationWithSummary{ Integration: &ui.Integration{ Name: integrationName, SubKind: "aws-oidc", @@ -197,10 +230,11 @@ func TestCollectAWSOIDCAutoDiscoverStats(t *testing.T) { DiscoverLastSync: &syncTime, }, AWSRDS: ui.ResourceTypeSummary{ - RulesCount: 2, + RulesCount: 3, ResourcesFound: 2, ResourcesEnrollmentFailed: 1, ResourcesEnrollmentSuccess: 1, + ECSDatabaseServiceCount: 1, DiscoverLastSync: &syncTime, }, AWSEKS: ui.ResourceTypeSummary{ @@ -220,7 +254,7 @@ func TestCollectAutoDiscoveryRules(t *testing.T) { integrationName := "my-integration" t.Run("without discovery configs, returns no rules", func(t *testing.T) { - clt := &mockDiscoveryConfigsGetter{ + clt := &mockRelevantAWSRegionsClient{ discoveryConfigs: make([]*discoveryconfig.DiscoveryConfig, 0), } @@ -287,7 +321,7 @@ func TestCollectAutoDiscoveryRules(t *testing.T) { Tags: types.Labels{"*": []string{"*"}}, }}}, } - clt := &mockDiscoveryConfigsGetter{ + clt := &mockRelevantAWSRegionsClient{ discoveryConfigs: []*discoveryconfig.DiscoveryConfig{ dcForEC2, dcForRDS, @@ -350,11 +384,3 @@ func TestCollectAutoDiscoveryRules(t *testing.T) { require.ElementsMatch(t, expectedRules, got.Rules) }) } - -type mockDiscoveryConfigsGetter struct { - discoveryConfigs []*discoveryconfig.DiscoveryConfig -} - -func (m *mockDiscoveryConfigsGetter) ListDiscoveryConfigs(ctx context.Context, pageSize int, nextToken string) ([]*discoveryconfig.DiscoveryConfig, string, error) { - return m.discoveryConfigs, "", nil -}