• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

elastic / cloudbeat / 14900461121

08 May 2025 06:49AM UTC coverage: 76.227% (+0.1%) from 76.125%
14900461121

Pull #3250

github

moukoublen
log errors on env vars parsing
Pull Request #3250: Increase gcp ListAssets timeout and page size

57 of 67 new or added lines in 5 files covered. (85.07%)

15 existing lines in 2 files now uncovered.

9209 of 12081 relevant lines covered (76.23%)

16.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.27
/internal/resources/providers/gcplib/inventory/provider.go
1
// Licensed to Elasticsearch B.V. under one or more contributor
2
// license agreements. See the NOTICE file distributed with
3
// this work for additional information regarding copyright
4
// ownership. Elasticsearch B.V. licenses this file to you under
5
// the Apache License, Version 2.0 (the "License"); you may
6
// not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
//     http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17

18
package inventory
19

20
import (
21
        "context"
22
        "fmt"
23
        "strings"
24
        "sync"
25

26
        asset "cloud.google.com/go/asset/apiv1"
27
        "cloud.google.com/go/asset/apiv1/assetpb"
28
        "github.com/googleapis/gax-go/v2"
29
        "github.com/samber/lo"
30
        "google.golang.org/api/cloudresourcemanager/v3"
31
        "google.golang.org/api/iterator"
32
        "google.golang.org/api/option"
33
        "google.golang.org/protobuf/types/known/structpb"
34

35
        "github.com/elastic/cloudbeat/internal/config"
36
        "github.com/elastic/cloudbeat/internal/infra/clog"
37
        "github.com/elastic/cloudbeat/internal/resources/fetching"
38
        "github.com/elastic/cloudbeat/internal/resources/providers/gcplib/auth"
39
)
40

41
type Provider struct {
42
        log                       *clog.Logger
43
        config                    auth.GcpFactoryConfig
44
        inventory                 *AssetsInventoryWrapper
45
        crm                       *ResourceManagerWrapper
46
        cloudAccountMetadataCache *MapCache[*fetching.CloudAccountMetadata]
47
}
48

49
type AssetsInventoryWrapper struct {
50
        Close      func() error
51
        ListAssets func(ctx context.Context, req *assetpb.ListAssetsRequest, opts ...gax.CallOption) Iterator
52
}
53

54
type ResourceManagerWrapper struct {
55
        // returns project display name or an empty string
56
        getProjectDisplayName func(ctx context.Context, parent string) string
57

58
        // returns org display name or an empty string
59
        getOrganizationDisplayName func(ctx context.Context, parent string) string
60
}
61

62
type MonitoringAsset struct {
63
        CloudAccount *fetching.CloudAccountMetadata
64
        LogMetrics   []*ExtendedGcpAsset `json:"log_metrics,omitempty"`
65
        Alerts       []*ExtendedGcpAsset `json:"alerts,omitempty"`
66
}
67

68
type LoggingAsset struct {
69
        CloudAccount *fetching.CloudAccountMetadata
70
        LogSinks     []*ExtendedGcpAsset `json:"log_sinks,omitempty"`
71
}
72

73
type ProjectPoliciesAsset struct {
74
        CloudAccount *fetching.CloudAccountMetadata
75
        Policies     []*ExtendedGcpAsset `json:"policies,omitempty"`
76
}
77

78
type ServiceUsageAsset struct {
79
        CloudAccount *fetching.CloudAccountMetadata
80
        Services     []*ExtendedGcpAsset `json:"services,omitempty"`
81
}
82

83
type ExtendedGcpAsset struct {
84
        *assetpb.Asset
85
        CloudAccount *fetching.CloudAccountMetadata
86
}
87

88
type ProviderInitializer struct{}
89

90
type dnsPolicyFields struct {
91
        networks      []string
92
        enableLogging bool
93
}
94

95
type TypeGenerator[T any] func(assets []*ExtendedGcpAsset, projectId, projectName, orgId, orgName string) *T
96

97
type Iterator interface {
98
        Next() (*assetpb.Asset, error)
99
}
100

101
type ServiceAPI interface {
102
        // ListAllAssetTypesByName List all content types of the given assets types
103
        ListAllAssetTypesByName(ctx context.Context, assets []string) ([]*ExtendedGcpAsset, error)
104

105
        // ListMonitoringAssets List all monitoring assets by project id
106
        ListMonitoringAssets(ctx context.Context, monitoringAssetTypes map[string][]string) ([]*MonitoringAsset, error)
107

108
        // ListLoggingAssets returns a list of logging assets grouped by project id, extended with folder and org level log sinks
109
        ListLoggingAssets(ctx context.Context) ([]*LoggingAsset, error)
110

111
        // ListServiceUsageAssets returns a list of service usage assets grouped by project id
112
        ListServiceUsageAssets(ctx context.Context) ([]*ServiceUsageAsset, error)
113

114
        // returns a project policies for all its ancestors
115
        ListProjectsAncestorsPolicies(ctx context.Context) ([]*ProjectPoliciesAsset, error)
116

117
        // Close the GCP asset client
118
        Close() error
119
}
120

121
type ProviderInitializerAPI interface {
122
        // Init initializes the GCP asset client
123
        Init(ctx context.Context, log *clog.Logger, gcpConfig auth.GcpFactoryConfig, cfg config.GcpConfig) (ServiceAPI, error)
124
}
125

126
func (p *ProviderInitializer) Init(ctx context.Context, log *clog.Logger, gcpConfig auth.GcpFactoryConfig, cfg config.GcpConfig) (ServiceAPI, error) {
1✔
127
        limiter := NewAssetsInventoryRateLimiter(log)
1✔
128
        // initialize GCP assets inventory client
1✔
129
        client, err := asset.NewClient(ctx, append(gcpConfig.ClientOpts, option.WithGRPCDialOption(limiter.GetInterceptorDialOption()))...)
1✔
130
        if err != nil {
2✔
131
                return nil, err
1✔
132
        }
1✔
133
        // wrap the assets inventory client for mocking
134
        assetsInventoryWrapper := &AssetsInventoryWrapper{
×
135
                Close: client.Close,
×
136
                ListAssets: func(ctx context.Context, req *assetpb.ListAssetsRequest, opts ...gax.CallOption) Iterator {
×
NEW
137
                        if req.PageSize == 0 {
×
NEW
138
                                req.PageSize = cfg.GcpCallOpt.ListAssetsPageSize
×
NEW
139
                        }
×
NEW
140
                        return client.ListAssets(ctx, req, append(opts, GAXCallOptionRetrier(log), gax.WithTimeout(cfg.GcpCallOpt.ListAssetsTimeout))...)
×
141
                },
142
        }
143

144
        // initialize GCP resource manager client
145
        var gcpClientOpt []option.ClientOption
×
146
        gcpClientOpt = append(append(gcpClientOpt, option.WithScopes(cloudresourcemanager.CloudPlatformReadOnlyScope)), gcpConfig.ClientOpts...)
×
147
        crmService, err := cloudresourcemanager.NewService(ctx, gcpClientOpt...)
×
148
        if err != nil {
×
149
                return nil, err
×
150
        }
×
151

152
        displayNamesCache := NewMapCache[string]()
×
153
        // wrap the resource manager client for mocking
×
154
        crmServiceWrapper := &ResourceManagerWrapper{
×
155
                getProjectDisplayName: func(ctx context.Context, parent string) string {
×
156
                        return displayNamesCache.Get(func() string {
×
157
                                prj, err := crmService.Projects.Get(parent).Context(ctx).Do()
×
158
                                if err != nil {
×
159
                                        log.Errorf("error fetching GCP Project: %s, error: %s", parent, err)
×
160
                                        return ""
×
161
                                }
×
162
                                return prj.DisplayName
×
163
                        }, parent)
164
                },
165
                getOrganizationDisplayName: func(ctx context.Context, parent string) string {
×
166
                        return displayNamesCache.Get(func() string {
×
167
                                org, err := crmService.Organizations.Get(parent).Context(ctx).Do()
×
168
                                if err != nil {
×
169
                                        log.Errorf("error fetching GCP Org: %s, error: %s", parent, err)
×
170
                                        return ""
×
171
                                }
×
172
                                return org.DisplayName
×
173
                        }, parent)
174
                },
175
        }
176

177
        return &Provider{
×
178
                config:                    gcpConfig,
×
179
                log:                       log,
×
180
                inventory:                 assetsInventoryWrapper,
×
181
                crm:                       crmServiceWrapper,
×
182
                cloudAccountMetadataCache: NewMapCache[*fetching.CloudAccountMetadata](),
×
183
        }, nil
×
184
}
185

186
func (p *Provider) ListAllAssetTypesByName(ctx context.Context, assetTypes []string) ([]*ExtendedGcpAsset, error) {
6✔
187
        wg := sync.WaitGroup{}
6✔
188
        var resourceAssets []*assetpb.Asset
6✔
189
        var policyAssets []*assetpb.Asset
6✔
190

6✔
191
        wg.Add(1)
6✔
192
        go func() {
12✔
193
                resourceAssets = p.getAllAssets(ctx, &assetpb.ListAssetsRequest{
6✔
194
                        Parent:      p.config.Parent,
6✔
195
                        AssetTypes:  assetTypes,
6✔
196
                        ContentType: assetpb.ContentType_RESOURCE,
6✔
197
                })
6✔
198
                wg.Done()
6✔
199
        }()
6✔
200
        wg.Add(1)
6✔
201
        go func() {
12✔
202
                policyAssets = p.getAllAssets(ctx, &assetpb.ListAssetsRequest{
6✔
203
                        Parent:      p.config.Parent,
6✔
204
                        AssetTypes:  assetTypes,
6✔
205
                        ContentType: assetpb.ContentType_IAM_POLICY,
6✔
206
                })
6✔
207
                wg.Done()
6✔
208
        }()
6✔
209

210
        wg.Wait()
6✔
211

6✔
212
        var assets []*assetpb.Asset
6✔
213
        assets = append(append(assets, resourceAssets...), policyAssets...)
6✔
214
        mergedAssets := mergeAssetContentType(assets)
6✔
215
        extendedAssets := p.extendWithCloudMetadata(ctx, mergedAssets)
6✔
216
        // Enrich network assets with dns policy
6✔
217
        p.enrichNetworkAssets(ctx, extendedAssets)
6✔
218

6✔
219
        return extendedAssets, nil
6✔
220
}
221

222
// ListMonitoringAssets returns a list of monitoring assets grouped by project id
223
func (p *Provider) ListMonitoringAssets(ctx context.Context, monitoringAssetTypes map[string][]string) ([]*MonitoringAsset, error) {
1✔
224
        logMetrics, err := p.ListAllAssetTypesByName(ctx, monitoringAssetTypes["LogMetric"])
1✔
225
        if err != nil {
1✔
226
                return nil, err
×
227
        }
×
228

229
        alertPolicies, err := p.ListAllAssetTypesByName(ctx, monitoringAssetTypes["AlertPolicy"])
1✔
230
        if err != nil {
1✔
231
                return nil, err
×
232
        }
×
233

234
        typeGenerator := func(assets []*ExtendedGcpAsset, projectId, projectName, orgId, orgName string) *MonitoringAsset {
3✔
235
                return &MonitoringAsset{
2✔
236
                        LogMetrics: getAssetsByType(assets, MonitoringLogMetricAssetType),
2✔
237
                        Alerts:     getAssetsByType(assets, MonitoringAlertPolicyAssetType),
2✔
238
                        CloudAccount: &fetching.CloudAccountMetadata{
2✔
239
                                AccountId:        projectId,
2✔
240
                                AccountName:      projectName,
2✔
241
                                OrganisationId:   orgId,
2✔
242
                                OrganizationName: orgName,
2✔
243
                        },
2✔
244
                }
2✔
245
        }
2✔
246

247
        var assets []*ExtendedGcpAsset
1✔
248
        assets = append(append(assets, logMetrics...), alertPolicies...)
1✔
249
        monitoringAssets := getAssetsByProject[MonitoringAsset](assets, p.log, typeGenerator)
1✔
250

1✔
251
        return monitoringAssets, nil
1✔
252
}
253

254
// ListLoggingAssets returns a list of logging assets grouped by project id, extended with folder and org level log sinks
255
func (p *Provider) ListLoggingAssets(ctx context.Context) ([]*LoggingAsset, error) {
1✔
256
        logSinks, err := p.ListAllAssetTypesByName(ctx, []string{LogSinkAssetType})
1✔
257
        if err != nil {
1✔
258
                return nil, err
×
259
        }
×
260

261
        typeGenerator := func(assets []*ExtendedGcpAsset, projectId, projectName, orgId, orgName string) *LoggingAsset {
3✔
262
                return &LoggingAsset{
2✔
263
                        LogSinks: assets,
2✔
264
                        CloudAccount: &fetching.CloudAccountMetadata{
2✔
265
                                AccountId:        projectId,
2✔
266
                                AccountName:      projectName,
2✔
267
                                OrganisationId:   orgId,
2✔
268
                                OrganizationName: orgName,
2✔
269
                        },
2✔
270
                }
2✔
271
        }
2✔
272

273
        loggingAssets := getAssetsByProject[LoggingAsset](logSinks, p.log, typeGenerator)
1✔
274
        return loggingAssets, nil
1✔
275
}
276

277
// ListServiceUsageAssets returns a list of service usage assets grouped by project id
278
func (p *Provider) ListServiceUsageAssets(ctx context.Context) ([]*ServiceUsageAsset, error) {
1✔
279
        services, err := p.ListAllAssetTypesByName(ctx, []string{ServiceUsageAssetType})
1✔
280
        if err != nil {
1✔
281
                return nil, err
×
282
        }
×
283

284
        typeGenerator := func(assets []*ExtendedGcpAsset, projectId, projectName, orgId, orgName string) *ServiceUsageAsset {
3✔
285
                return &ServiceUsageAsset{
2✔
286
                        Services: assets,
2✔
287
                        CloudAccount: &fetching.CloudAccountMetadata{
2✔
288
                                AccountId:        projectId,
2✔
289
                                AccountName:      projectName,
2✔
290
                                OrganisationId:   orgId,
2✔
291
                                OrganizationName: orgName,
2✔
292
                        },
2✔
293
                }
2✔
294
        }
2✔
295

296
        assets := getAssetsByProject[ServiceUsageAsset](services, p.log, typeGenerator)
1✔
297
        return assets, nil
1✔
298
}
299

300
func (p *Provider) Close() error {
×
301
        return p.inventory.Close()
×
302
}
×
303

304
// enrichNetworkAssets enriches the network assets with dns policy if exists
305
func (p *Provider) enrichNetworkAssets(ctx context.Context, assets []*ExtendedGcpAsset) {
7✔
306
        networkAssets := getAssetsByType(assets, ComputeNetworkAssetType)
7✔
307
        if len(networkAssets) == 0 {
13✔
308
                p.log.Infof("no %s assets were listed", ComputeNetworkAssetType)
6✔
309
                return
6✔
310
        }
6✔
311
        dnsPolicyAssets := p.getAllAssets(ctx, &assetpb.ListAssetsRequest{
1✔
312
                Parent:      p.config.Parent,
1✔
313
                AssetTypes:  []string{DnsPolicyAssetType},
1✔
314
                ContentType: assetpb.ContentType_RESOURCE,
1✔
315
        })
1✔
316

1✔
317
        if len(dnsPolicyAssets) == 0 {
1✔
318
                p.log.Infof("no %s assets were listed, return original assets", DnsPolicyAssetType)
×
319
                return
×
320
        }
×
321
        dnsPolicies := decodeDnsPolicies(dnsPolicyAssets)
1✔
322

1✔
323
        p.log.Infof("attempting to enrich %d %s assets with dns policy", len(assets), ComputeNetworkAssetType)
1✔
324
        for _, networkAsset := range networkAssets {
4✔
325
                networkAssetFields := networkAsset.GetResource().GetData().GetFields()
3✔
326
                networkIdentifier := strings.TrimPrefix(networkAsset.GetName(), "//compute.googleapis.com")
3✔
327

3✔
328
                dnsPolicy := findDnsPolicyByNetwork(dnsPolicies, networkIdentifier)
3✔
329
                if dnsPolicy != nil {
5✔
330
                        p.log.Infof("enrich a %s asset with dns policy, name: %s", ComputeNetworkAssetType, networkIdentifier)
2✔
331
                        networkAssetFields["enabledDnsLogging"] = &structpb.Value{Kind: &structpb.Value_BoolValue{BoolValue: dnsPolicy.enableLogging}}
2✔
332
                }
2✔
333
        }
334
}
335

336
// findDnsPolicyByNetwork finds DNS policy by network identifier
337
func findDnsPolicyByNetwork(dnsPolicies []*dnsPolicyFields, networkIdentifier string) *dnsPolicyFields {
3✔
338
        for _, dnsPolicy := range dnsPolicies {
6✔
339
                if lo.SomeBy(dnsPolicy.networks, func(networkUrl string) bool {
8✔
340
                        return strings.HasSuffix(networkUrl, networkIdentifier)
5✔
341
                }) {
7✔
342
                        return dnsPolicy
2✔
343
                }
2✔
344
        }
345
        return nil
1✔
346
}
347

348
// decodeDnsPolicies gets the required fields from the dns policies assets
349
func decodeDnsPolicies(dnsPolicyAssets []*assetpb.Asset) []*dnsPolicyFields {
1✔
350
        dnsPolicies := make([]*dnsPolicyFields, 0)
1✔
351
        for _, dnsPolicyAsset := range dnsPolicyAssets {
2✔
352
                fields := new(dnsPolicyFields)
1✔
353
                dnsPolicyData := dnsPolicyAsset.GetResource().GetData().GetFields()
1✔
354

1✔
355
                if attachedNetworks, exist := dnsPolicyData["networks"]; exist {
2✔
356
                        networks := attachedNetworks.GetListValue().GetValues()
1✔
357
                        for _, network := range networks {
3✔
358
                                if networkUrl, found := network.GetStructValue().GetFields()["networkUrl"]; found {
4✔
359
                                        fields.networks = append(fields.networks, networkUrl.GetStringValue())
2✔
360
                                }
2✔
361
                        }
362
                }
363

364
                if enableLogging, exist := dnsPolicyData["enableLogging"]; exist {
2✔
365
                        fields.enableLogging = enableLogging.GetBoolValue()
1✔
366
                }
1✔
367

368
                dnsPolicies = append(dnsPolicies, fields)
1✔
369
        }
370

371
        return dnsPolicies
1✔
372
}
373

374
// getAssetsByProject groups assets by project, extracts metadata for each project, and adds folder and organization-level resources for each group.
375
func getAssetsByProject[T any](assets []*ExtendedGcpAsset, log *clog.Logger, f TypeGenerator[T]) []*T {
3✔
376
        assetsByProject := lo.GroupBy(assets, func(asset *ExtendedGcpAsset) string { return asset.CloudAccount.AccountId })
10✔
377
        enrichedAssets := make([]*T, 0, len(assetsByProject))
3✔
378
        for projectId, projectAssets := range assetsByProject {
10✔
379
                if projectId == "" {
8✔
380
                        continue
1✔
381
                }
382

383
                if len(projectAssets) == 0 {
6✔
384
                        log.Errorf("no assets were listed for project: %s", projectId)
×
385
                        continue
×
386
                }
387

388
                cloudAccount := projectAssets[0].CloudAccount
6✔
389

6✔
390
                // add folder and org level log sinks for each project
6✔
391
                projectAssets = append(projectAssets, assetsByProject[""]...)
6✔
392
                enrichedAssets = append(enrichedAssets, f(
6✔
393
                        projectAssets,
6✔
394
                        projectId,
6✔
395
                        cloudAccount.AccountName,
6✔
396
                        cloudAccount.OrganisationId,
6✔
397
                        cloudAccount.OrganizationName,
6✔
398
                ))
6✔
399
        }
400
        return enrichedAssets
3✔
401
}
402

403
func (p *Provider) getAllAssets(ctx context.Context, request *assetpb.ListAssetsRequest) []*assetpb.Asset {
15✔
404
        p.log.Infof("Listing asset types: %v of type %v for %v", request.AssetTypes, request.ContentType, request.Parent)
15✔
405
        results := make([]*assetpb.Asset, 0)
15✔
406
        it := p.inventory.ListAssets(ctx, request)
15✔
407
        for {
52✔
408
                response, err := it.Next()
37✔
409
                if err == iterator.Done {
52✔
410
                        break
15✔
411
                }
412
                if err != nil {
22✔
413
                        p.log.Errorf("Error fetching GCP Asset: %s", err)
×
414
                        return results
×
415
                }
×
416

417
                p.log.Debugf("Fetched GCP Asset: %+v", response.Name)
22✔
418
                results = append(results, response)
22✔
419
        }
420
        return results
15✔
421
}
422

423
func mergeAssetContentType(assets []*assetpb.Asset) []*assetpb.Asset {
6✔
424
        resultsMap := make(map[string]*assetpb.Asset)
6✔
425
        for _, asset := range assets {
25✔
426
                assetKey := asset.Name
19✔
427
                if _, ok := resultsMap[assetKey]; !ok {
28✔
428
                        resultsMap[assetKey] = asset
9✔
429
                        continue
9✔
430
                }
431
                item := resultsMap[assetKey]
10✔
432
                if asset.Resource != nil {
15✔
433
                        item.Resource = asset.Resource
5✔
434
                }
5✔
435
                if asset.IamPolicy != nil {
14✔
436
                        item.IamPolicy = asset.IamPolicy
4✔
437
                }
4✔
438
        }
439
        results := make([]*assetpb.Asset, 0, len(resultsMap))
6✔
440
        for _, asset := range resultsMap {
15✔
441
                results = append(results, asset)
9✔
442
        }
9✔
443
        return results
6✔
444
}
445

446
// extends the assets with the project and organization display name
447
func (p *Provider) extendWithCloudMetadata(ctx context.Context, assets []*assetpb.Asset) []*ExtendedGcpAsset {
8✔
448
        extendedAssets := make([]*ExtendedGcpAsset, 0, len(assets))
8✔
449
        for _, asset := range assets {
19✔
450
                orgId := getOrganizationId(asset.Ancestors)
11✔
451
                projectId := getProjectId(asset.Ancestors)
11✔
452
                cacheKey := fmt.Sprintf("%s/%s", projectId, orgId)
11✔
453
                cloudAccount := p.cloudAccountMetadataCache.Get(func() *fetching.CloudAccountMetadata {
22✔
454
                        return p.getCloudAccountMetadata(ctx, projectId, orgId)
11✔
455
                }, cacheKey)
11✔
456
                extendedAssets = append(extendedAssets, &ExtendedGcpAsset{
11✔
457
                        Asset:        asset,
11✔
458
                        CloudAccount: cloudAccount,
11✔
459
                })
11✔
460
        }
461
        return extendedAssets
8✔
462
}
463

464
func (p *Provider) ListProjectsAncestorsPolicies(ctx context.Context) ([]*ProjectPoliciesAsset, error) {
1✔
465
        projects := p.getAllAssets(ctx, &assetpb.ListAssetsRequest{
1✔
466
                ContentType: assetpb.ContentType_IAM_POLICY,
1✔
467
                Parent:      p.config.Parent,
1✔
468
                AssetTypes:  []string{CrmProjectAssetType},
1✔
469
        })
1✔
470
        p.log.Infof("Listed %d GCP projects", len(projects))
1✔
471
        ancestorsPoliciesCache := NewMapCache[[]*ExtendedGcpAsset]()
1✔
472
        return lo.Map(projects, func(project *assetpb.Asset, _ int) *ProjectPoliciesAsset {
2✔
473
                projectAsset := p.extendWithCloudMetadata(ctx, []*assetpb.Asset{project})[0]
1✔
474
                // Skip first ancestor it as we already got it
1✔
475
                policiesAssets := append([]*ExtendedGcpAsset{projectAsset}, getAncestorsAssets(ctx, ancestorsPoliciesCache, p, project.Ancestors[1:])...)
1✔
476
                return &ProjectPoliciesAsset{CloudAccount: projectAsset.CloudAccount, Policies: policiesAssets}
1✔
477
        }), nil
1✔
478
}
479

480
func getAncestorsAssets(ctx context.Context, ancestorsPoliciesCache *MapCache[[]*ExtendedGcpAsset], p *Provider, ancestors []string) []*ExtendedGcpAsset {
1✔
481
        return lo.Flatten(lo.Map(ancestors, func(parent string, _ int) []*ExtendedGcpAsset {
2✔
482
                return ancestorsPoliciesCache.Get(func() []*ExtendedGcpAsset {
2✔
483
                        var assetType string
1✔
484
                        if isFolder(parent) {
1✔
485
                                assetType = CrmFolderAssetType
×
486
                        }
×
487
                        if isOrganization(parent) {
2✔
488
                                assetType = CrmOrgAssetType
1✔
489
                        }
1✔
490
                        return p.extendWithCloudMetadata(ctx, p.getAllAssets(ctx, &assetpb.ListAssetsRequest{
1✔
491
                                ContentType: assetpb.ContentType_IAM_POLICY,
1✔
492
                                Parent:      parent,
1✔
493
                                AssetTypes:  []string{assetType},
1✔
494
                        }))
1✔
495
                }, parent)
496
        }))
497
}
498

499
func (p *Provider) getCloudAccountMetadata(ctx context.Context, projectId string, orgId string) *fetching.CloudAccountMetadata {
11✔
500
        var orgName string
11✔
501
        var projectName string
11✔
502
        wg := sync.WaitGroup{}
11✔
503
        wg.Add(1)
11✔
504
        go func() {
22✔
505
                if isOrganization(p.config.Parent) {
12✔
506
                        orgName = p.crm.getOrganizationDisplayName(ctx, fmt.Sprintf("organizations/%s", orgId))
1✔
507
                }
1✔
508
                wg.Done()
11✔
509
        }()
510
        wg.Add(1)
11✔
511
        go func() {
22✔
512
                // some assets are not associated with a project
11✔
513
                if projectId != "" {
20✔
514
                        projectName = p.crm.getProjectDisplayName(ctx, fmt.Sprintf("projects/%s", projectId))
9✔
515
                }
9✔
516
                wg.Done()
11✔
517
        }()
518
        wg.Wait()
11✔
519
        return &fetching.CloudAccountMetadata{
11✔
520
                AccountId:        projectId,
11✔
521
                AccountName:      projectName,
11✔
522
                OrganisationId:   orgId,
11✔
523
                OrganizationName: orgName,
11✔
524
        }
11✔
525
}
526

527
func getOrganizationId(ancestors []string) string {
11✔
528
        last := ancestors[len(ancestors)-1]
11✔
529
        parts := strings.Split(last, "/") // organizations/1234567890
11✔
530

11✔
531
        if parts[0] == "organizations" {
22✔
532
                return parts[1]
11✔
533
        }
11✔
534

535
        return ""
×
536
}
537

538
func getProjectId(ancestors []string) string {
11✔
539
        parts := strings.Split(ancestors[0], "/") // projects/1234567890
11✔
540

11✔
541
        if parts[0] == "projects" {
20✔
542
                return parts[1]
9✔
543
        }
9✔
544

545
        return ""
2✔
546
}
547

548
func getAssetsByType(projectAssets []*ExtendedGcpAsset, assetType string) []*ExtendedGcpAsset {
11✔
549
        return lo.Filter(projectAssets, func(asset *ExtendedGcpAsset, _ int) bool {
27✔
550
                return asset.AssetType == assetType
16✔
551
        })
16✔
552
}
553

554
func isFolder(parent string) bool {
1✔
555
        return strings.HasPrefix(parent, "folders")
1✔
556
}
1✔
557

558
func isOrganization(parent string) bool {
12✔
559
        return strings.HasPrefix(parent, "organizations")
12✔
560
}
12✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc