Skip to content

Commit d59647c

Browse files
fix-large-org-disvoery-gcp
1 parent 94162a1 commit d59647c

1 file changed

Lines changed: 58 additions & 4 deletions

File tree

pkg/providers/gcp/gcp.go

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import (
2323
"google.golang.org/api/option"
2424
run "google.golang.org/api/run/v1"
2525
"google.golang.org/api/storage/v1"
26+
"google.golang.org/genproto/googleapis/rpc/errdetails"
27+
"google.golang.org/grpc/codes"
28+
"google.golang.org/grpc/status"
2629
"google.golang.org/protobuf/types/known/structpb"
2730
"google.golang.org/protobuf/types/known/timestamppb"
2831
)
@@ -383,7 +386,7 @@ func (p *OrganizationProvider) Resources(ctx context.Context) (*schema.Resources
383386
gologger.Info().Msgf("Found 'all' service, starting comprehensive asset discovery")
384387
allAssets, err := p.getAllAssets(ctx, parent)
385388
if err != nil {
386-
gologger.Warning().Msgf("Could not get all assets: %s", err)
389+
gologger.Info().Msgf("Could not get all assets: %s", err)
387390
} else {
388391
finalResources.Merge(allAssets)
389392
}
@@ -392,7 +395,7 @@ func (p *OrganizationProvider) Resources(ctx context.Context) (*schema.Resources
392395
for _, service := range p.services.Keys() {
393396
assets, err := p.getAssetsForService(ctx, parent, service)
394397
if err != nil {
395-
gologger.Warning().Msgf("Could not get assets for service %s: %s", service, err)
398+
gologger.Info().Msgf("Could not get assets for service %s: %s", service, err)
396399
} else {
397400
finalResources.Merge(assets)
398401
}
@@ -456,16 +459,51 @@ func (p *OrganizationProvider) getAssetsForTypes(ctx context.Context, parent str
456459
resources := schema.NewResources()
457460
it := p.assetClient.ListAssets(ctx, req)
458461

459-
// Collect all assets first
462+
// Collect all assets, with retry on rate limit errors and partial result preservation
463+
const maxRateLimitRetries = 10
464+
const rateLimitWait = 90 * time.Second
460465
var assetInfos []assetInfo
466+
rateLimitRetries := 0
461467

468+
pagination:
462469
for {
463470
asset, err := it.Next()
464471
if err != nil {
465472
if errors.Is(err, iterator.Done) {
466473
break
467474
}
468-
return nil, err
475+
476+
// On rate limit, wait and resume from where we left off
477+
if isRateLimit, retryDelay := rateLimitInfo(err); isRateLimit && rateLimitRetries < maxRateLimitRetries {
478+
rateLimitRetries++
479+
pageToken := it.PageInfo().Token
480+
481+
wait := rateLimitWait
482+
if retryDelay > 0 {
483+
wait = retryDelay
484+
}
485+
gologger.Info().Msgf("Rate limit hit after %d assets, waiting %s before retry (%d/%d)",
486+
len(assetInfos), wait, rateLimitRetries, maxRateLimitRetries)
487+
488+
if pageToken == "" {
489+
break
490+
}
491+
492+
select {
493+
case <-time.After(wait):
494+
case <-ctx.Done():
495+
break pagination
496+
}
497+
498+
// Resume with a new iterator using the page token
499+
req.PageToken = pageToken
500+
it = p.assetClient.ListAssets(ctx, req)
501+
continue
502+
}
503+
504+
// Non-rate-limit error or max retries exceeded: return partial results
505+
gologger.Info().Msgf("ListAssets pagination stopped after %d assets: %s", len(assetInfos), err)
506+
break
469507
}
470508

471509
resource := p.parseAssetToResource(asset)
@@ -678,6 +716,22 @@ func newOrganizationProvider(options schema.OptionBlock, id, JSONData, organizat
678716
return provider, nil
679717
}
680718

719+
// rateLimitInfo returns whether the error is a rate limit error and the suggested retry delay
720+
func rateLimitInfo(err error) (bool, time.Duration) {
721+
s, ok := status.FromError(err)
722+
if !ok || s.Code() != codes.ResourceExhausted {
723+
return false, 0
724+
}
725+
for _, detail := range s.Details() {
726+
if retryInfo, ok := detail.(*errdetails.RetryInfo); ok {
727+
if d := retryInfo.GetRetryDelay(); d != nil {
728+
return true, d.AsDuration()
729+
}
730+
}
731+
}
732+
return true, 0
733+
}
734+
681735
// Helper functions to reduce nesting and improve readability
682736

683737
func getStringField(data *structpb.Struct, fieldNames ...string) string {

0 commit comments

Comments
 (0)