diff --git a/lib/cloud/mocks/aws_opensearch.go b/lib/cloud/mocks/aws_opensearch.go index f1ed0660e2027..fa36dd472cf4c 100644 --- a/lib/cloud/mocks/aws_opensearch.go +++ b/lib/cloud/mocks/aws_opensearch.go @@ -56,6 +56,11 @@ func (o *OpenSearchMock) DescribeDomainsWithContext(_ aws.Context, input *opense if o.Unauth { return nil, trace.AccessDenied("unauthorized") } + // The real API only allows 5 domains at a time: + // https://github.com/gravitational/teleport/issues/38651 + if len(input.DomainNames) > 5 { + return nil, trace.BadParameter("Please provide a maximum of 5 domain names to describe.") + } out := &opensearchservice.DescribeDomainsOutput{} for _, domain := range o.Domains { if slices.ContainsFunc(input.DomainNames, func(other *string) bool { diff --git a/lib/srv/discovery/fetchers/db/aws_opensearch.go b/lib/srv/discovery/fetchers/db/aws_opensearch.go index 381d72c8a267f..646fe00485de8 100644 --- a/lib/srv/discovery/fetchers/db/aws_opensearch.go +++ b/lib/srv/discovery/fetchers/db/aws_opensearch.go @@ -20,6 +20,7 @@ package db import ( "context" + "slices" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/opensearchservice" @@ -100,16 +101,21 @@ func getOpenSearchDomains(ctx context.Context, client opensearchserviceiface.Ope return nil, trace.Wrap(libcloudaws.ConvertRequestFailureError(err)) } - req := &opensearchservice.DescribeDomainsInput{DomainNames: []*string{}} - for _, domain := range names.DomainNames { - req.DomainNames = append(req.DomainNames, domain.DomainName) - } + // API only allows 5 at a time. + var all []*opensearchservice.DomainStatus + for chunk := range slices.Chunk(names.DomainNames, 5) { + req := &opensearchservice.DescribeDomainsInput{DomainNames: []*string{}} + for _, domain := range chunk { + req.DomainNames = append(req.DomainNames, domain.DomainName) + } - domains, err := client.DescribeDomainsWithContext(ctx, req) - if err != nil { - return nil, trace.Wrap(libcloudaws.ConvertRequestFailureError(err)) + domains, err := client.DescribeDomainsWithContext(ctx, req) + if err != nil { + return nil, trace.Wrap(libcloudaws.ConvertRequestFailureError(err)) + } + all = append(all, domains.DomainStatusList...) } - return domains.DomainStatusList, nil + return all, nil } // getOpenSearchResourceTags fetches resource tags for provided ARN. diff --git a/lib/srv/discovery/fetchers/db/aws_opensearch_test.go b/lib/srv/discovery/fetchers/db/aws_opensearch_test.go index 53c30d737577f..5eeff513326f8 100644 --- a/lib/srv/discovery/fetchers/db/aws_opensearch_test.go +++ b/lib/srv/discovery/fetchers/db/aws_opensearch_test.go @@ -19,6 +19,7 @@ package db import ( + "slices" "testing" "github.com/aws/aws-sdk-go/aws" @@ -46,17 +47,23 @@ func TestOpenSearchFetcher(t *testing.T) { test, testDBs := makeOpenSearchDomain(t, tags, "os5", "us-east-1", "test") + // Make a few more. + os6, os6DBs := makeOpenSearchDomain(t, tags, "os6", "us-east-1", "test") + os7, os7DBs := makeOpenSearchDomain(t, tags, "os7", "us-east-1", "test") + os8, os8DBs := makeOpenSearchDomain(t, tags, "os8", "us-east-1", "test") + os9, os9DBs := makeOpenSearchDomain(t, tags, "os9", "us-east-1", "test") + tests := []awsFetcherTest{ { name: "fetch all", inputClients: &cloud.TestCloudClients{ OpenSearch: &mocks.OpenSearchMock{ - Domains: []*opensearchservice.DomainStatus{prod, test}, + Domains: []*opensearchservice.DomainStatus{prod, test, os6, os7, os8, os9}, TagsByARN: tags, }, }, inputMatchers: makeAWSMatchersForType(types.AWSMatcherOpenSearch, "us-east-1", wildcardLabels), - wantDatabases: append(append(types.Databases{}, prodDBs...), testDBs...), + wantDatabases: slices.Concat(prodDBs, testDBs, os6DBs, os7DBs, os8DBs, os9DBs), }, { name: "fetch prod",