Skip to content

feat(gcp): add support for organizations - #2345

Merged
moshloop merged 4 commits into
mainfrom
gcp-org
Aug 5, 2026
Merged

feat(gcp): add support for organizations #2345
moshloop merged 4 commits into
mainfrom
gcp-org

Conversation

@yashmehrotra

@yashmehrotra yashmehrotra commented Aug 4, 2026

Copy link
Copy Markdown
Member

Fixes: #2341

Summary by CodeRabbit

  • New Features

    • Added organization-scoped and multi-project GCP scraping.
    • Added flexible project configuration, including single-project aliases and project filtering.
    • Added organization and project hierarchy detection for more accurate resource ownership.
    • Improved audit-log attribution to affected projects and support for organization-level datasets.
    • Added broader IAM, Security Center, Cloud SQL backup, and asset coverage across configured scopes.
  • Bug Fixes

    • Improved handling of project ancestry, aliases, conditional IAM bindings, and out-of-scope audit-log entries.
    • Preserved successful results when individual project operations fail.
  • Documentation

    • Updated configuration schemas and examples with scope, filtering, and audit-log guidance.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b8c31a1-a6a8-4eef-a4d6-c9e22c0f33f0

📥 Commits

Reviewing files that changed from the base of the PR and between dfeec39 and 41ce4a2.

📒 Files selected for processing (10)
  • fixtures/gcp.yaml
  • scrapers/gcp/audit_logs_test.go
  • scrapers/gcp/cloudsql_backup.go
  • scrapers/gcp/cloudsql_backup_test.go
  • scrapers/gcp/gcp.go
  • scrapers/gcp/iam.go
  • scrapers/gcp/iam_hierarchy.go
  • scrapers/gcp/iam_scope_test.go
  • scrapers/gcp/iam_test.go
  • scrapers/gcp/targets_test.go
💤 Files with no reviewable changes (1)
  • scrapers/gcp/targets_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • scrapers/gcp/cloudsql_backup.go
  • scrapers/gcp/audit_logs_test.go
  • scrapers/gcp/iam_test.go
  • scrapers/gcp/cloudsql_backup_test.go
  • fixtures/gcp.yaml
  • scrapers/gcp/iam_scope_test.go
  • scrapers/gcp/iam_hierarchy.go
  • scrapers/gcp/gcp.go

Walkthrough

GCP scraping now supports organization roots, project lists, and a singular project alias. It resolves resource ancestry, scopes IAM and Security Center requests, attributes audit logs and Cloud SQL backups to affected projects, and updates schemas, validation, and tests.

Changes

GCP organization and multi-project scope

Layer / File(s) Summary
Scope configuration contracts
api/v1/gcp.go, api/v1/gcp_test.go, api/v1/zz_generated.deepcopy.go, config/schemas/*, chart/crds/*, fixtures/gcp.yaml
GCP supports organization and project selectors. Validation, normalization, audit-log dataset configuration, schemas, examples, and deep-copy behavior were updated.
Hierarchy and scrape-root resolution
scrapers/gcp/ancestry.go, scrapers/gcp/ancestry_test.go, scrapers/gcp/targets.go, scrapers/gcp/targets_test.go
Resource ancestry resolves project ownership and organization roots. Target resolution qualifies projects and filters configured projects within an organization.
Scoped asset scraping
scrapers/gcp/gcp.go, scrapers/gcp/gcp_test.go, scrapers/gcp/security_center.go
Asset fetching uses explicit parent resources and ancestry-based relationships. Scraping runs across resolved roots and passes those roots to asset and Security Center requests.
Organization-aware IAM
scrapers/gcp/iam.go, scrapers/gcp/iam_group_members.go, scrapers/gcp/iam_hierarchy.go, scrapers/gcp/*iam*_test.go
IAM resolves organization or project tenancy, assigns hierarchy-aware role parents, preserves conditional expressions, excludes conditional access edges, coalesces duplicate roles, and propagates tenancy to memberships.
Project-aware logs and backups
scrapers/gcp/audit_logs.go, scrapers/gcp/audit_logs_test.go, scrapers/gcp/cloudsql_backup.go, scrapers/gcp/cloudsql_backup_test.go
Audit logs resolve and filter affected projects. Cloud SQL backups group instances and operations by project and aggregate project-specific errors.

Sequence Diagram(s)

sequenceDiagram
  participant Scrape
  participant TargetResolver
  participant AssetInventory
  participant IAM
  participant SecurityCenter
  participant BigQuery
  Scrape->>TargetResolver: resolve organization and project roots
  TargetResolver->>AssetInventory: list organization projects when needed
  TargetResolver-->>Scrape: return qualified parent roots
  Scrape->>AssetInventory: fetch assets for each parent
  Scrape->>IAM: fetch IAM policies for each parent
  Scrape->>SecurityCenter: list findings for each parent
  Scrape->>BigQuery: query the configured audit-log dataset
  BigQuery-->>Scrape: return affected project IDs
Loading

Possibly related PRs

Suggested reviewers: moshloop

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: adding GCP organization support.
Linked Issues check ✅ Passed The changes add GCP organization support and implement IAM and project relationships required by issue #2341.
Out of Scope Changes check ✅ Passed The changes support organization and project-scoped GCP scraping, IAM relationships, and related configuration and tests.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gcp-org
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch gcp-org

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Benchstat

Base: 98f287b936e487ef6b7fccbfb3ddc20940a595c9
Head: 41ce4a205f9e1e9d25eab1925fbfa302aa540e79

✅ No significant performance changes detected

Full benchstat output
goos: linux
goarch: amd64
pkg: github.com/flanksource/config-db/bench
cpu: AMD EPYC 7763 64-Core Processor                
                                         │ bench-base.txt │           bench-head.txt           │
                                         │     sec/op     │    sec/op     vs base              │
LocationFilter/smallEnv-4                    19.55µ ± 20%   19.00µ ± 23%       ~ (p=0.132 n=6)
LocationFilter/largeEnv-4                    47.52µ ± 17%   47.55µ ± 15%       ~ (p=0.589 n=6)
RunTemplateBool/smallEnv-4                   10.75µ ±  2%   10.80µ ±  0%       ~ (p=0.180 n=6)
RunTemplateBool/largeEnv-4                   20.29µ ±  1%   20.21µ ±  1%       ~ (p=0.240 n=6)
BenchSaveResultsSeed/N=1000-4                 4.132 ± 10%    4.142 ± 10%       ~ (p=0.937 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4      3.448 ±  1%    3.467 ±  1%       ~ (p=0.093 n=6)
BenchSaveResultsUpdateChanged/N=1000-4        7.674 ±  3%    7.676 ± 12%       ~ (p=0.818 n=6)
geomean                                      4.176m         4.164m        -0.28%

                                         │ bench-base.txt │            bench-head.txt            │
                                         │      B/op      │     B/op      vs base                │
LocationFilter/smallEnv-4                    14.97Ki ± 0%   14.97Ki ± 0%       ~ (p=1.000 n=6) ¹
LocationFilter/largeEnv-4                    20.38Ki ± 0%   20.38Ki ± 0%       ~ (p=0.242 n=6)
RunTemplateBool/smallEnv-4                   8.359Ki ± 0%   8.359Ki ± 0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/largeEnv-4                   10.16Ki ± 0%   10.16Ki ± 0%       ~ (p=0.455 n=6)
BenchSaveResultsSeed/N=1000-4                1.278Gi ± 0%   1.278Gi ± 0%       ~ (p=0.699 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     32.20Mi ± 0%   32.20Mi ± 0%       ~ (p=0.394 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       797.1Mi ± 1%   797.4Mi ± 1%       ~ (p=0.818 n=6)
geomean                                      990.3Ki        990.3Ki       +0.00%
¹ all samples are equal

                                         │ bench-base.txt │            bench-head.txt            │
                                         │   allocs/op    │  allocs/op    vs base                │
LocationFilter/smallEnv-4                     278.0 ±  0%    278.0 ±  0%       ~ (p=1.000 n=6) ¹
LocationFilter/largeEnv-4                     522.0 ±  0%    522.0 ±  0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/smallEnv-4                    216.0 ±  0%    216.0 ±  0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/largeEnv-4                    297.0 ±  0%    297.0 ±  0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsSeed/N=1000-4                442.6k ±  0%   442.7k ±  0%       ~ (p=0.974 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     396.9k ±  0%   396.9k ±  0%       ~ (p=0.498 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       984.1k ± 13%   984.1k ± 13%       ~ (p=0.937 n=6)
geomean                                      7.703k         7.703k        +0.00%
¹ all samples are equal

                                         │ bench-base.txt │           bench-head.txt           │
                                         │      MB/s      │    MB/s     vs base                │
BenchSaveResultsSeed/N=1000-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsUpdateUnchanged/N=1000-4     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsUpdateChanged/N=1000-4       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                 ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

Gavel summary

Source Pass Fail Skip Duration
api 6 0 0 804.714µs
aws 98 0 0 21ms
azure 43 0 0 13ms
changes 30 0 0 4.8s
cmd 5 0 0 567.973µs
db 39 0 0 8.3s
devops 208 0 0 15ms
exec 12 0 0 2m2s
extract 45 0 0 203ms
file 9 0 0 373.68µs
gcp 68 0 0 5ms
github 78 0 3 26ms
github.com/flanksource/config-db/api/v1 5 0 0 -
github.com/flanksource/config-db/cmd 4 0 0 6.4s
github.com/flanksource/config-db/cmd/scrapeui 2 0 0 -
github.com/flanksource/config-db/db 22 0 0 -
github.com/flanksource/config-db/scrapers/aws 5 0 0 -
github.com/flanksource/config-db/scrapers/gcp 2 0 0 -
github.com/flanksource/config-db/scrapers/github 6 0 0 -
github.com/flanksource/config-db/scrapers/postgres 2 0 0 -
github.com/flanksource/config-db/scrapers/processors 8 0 0 -
http 28 0 0 34ms
jobs 5 0 0 41.6s
kubernetes 34 0 0 2ms
playwright 9 0 0 612.408µs
scrapers 166 0 0 29.5s
terraform 1 0 0 2ms
tests 2 0 2 3.5s
utils 12 0 0 520.013µs
v1 124 0 0 4ms

Totals: 1078 passed · 0 failed · 5 skipped · 3m37s

View full results

@yashmehrotra
yashmehrotra marked this pull request as ready for review August 5, 2026 04:11

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scrapers/gcp/audit_logs.go (1)

197-217: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reconcile existing GCP audit log access rows when changing the affected project.

FetchAuditLogs now keys config_access.id and config_external_id on the audit event’s affected project instead of the configured BigQuery dataset project. Existing single-project scrapes that already populated auditedProject will generate new access IDs and points while the old deleted_at <> null rows remain unless the reconcile path deletes orphaned accesses, or unless affected-project logs currently only come from an aggregated audit sink. Document the key change in upgrade notes and add a migration or reconcile clean-up if orphaned rows are possible.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/audit_logs.go` around lines 197 - 217, Update FetchAuditLogs and
its reconciliation flow to clean up previously persisted access rows keyed to
the configured dataset project when affectedProject changes, deleting or
reconciling orphaned rows so stale deleted_at entries do not remain. Preserve
the new affectedProject-based ExternalID and access ID generation, and document
this key change in the upgrade notes.
🧹 Nitpick comments (10)
scrapers/gcp/cloudsql_backup_test.go (1)

100-109: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Rename the errors counter.

errors is an int counter here. The package under test uses the standard errors package in scrapers/gcp/cloudsql_backup.go. The name compiles because this file does not import errors, but it invites a shadowing mistake if an import is added later.

♻️ Proposed rename
 		var changes []v1.ChangeResult
-		var errors int
+		var errorCount int
 		for _, result := range results {
 			changes = append(changes, result.Changes...)
 			if result.Error != nil {
-				errors++
+				errorCount++
 				Expect(result.Error.Error()).To(ContainSubstring("gcp-proj-2"))
 			}
 		}
-		Expect(errors).To(Equal(1))
+		Expect(errorCount).To(Equal(1))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/cloudsql_backup_test.go` around lines 100 - 109, Rename the
integer counter currently named errors in the results loop to a more specific
name, and update its increment and final Expect assertion accordingly; leave the
standard errors package and other result handling unchanged.
scrapers/gcp/cloudsql_backup.go (1)

104-116: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Log instances that are dropped for a missing project.

instancesByProject silently discards any instance whose project is empty. Under an organization root, projectFromParent returns an empty fallback, so an instance whose ancestry resolution failed loses its operation changes with no signal. Add a warning so the gap is visible in scrape logs.

♻️ Proposed observability change
-func instancesByProject(instances []instanceInfo) map[string][]instanceInfo {
+func instancesByProject(ctx *GCPContext, instances []instanceInfo) map[string][]instanceInfo {
 	grouped := make(map[string][]instanceInfo)
 	for _, instance := range instances {
 		if instance.project == "" {
+			ctx.Warnf("gcp cloudsql: skipping operations for instance %s, its project is unresolved", instance.name)
 			continue
 		}
 		grouped[instance.project] = append(grouped[instance.project], instance)
 	}
 	return grouped
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/cloudsql_backup.go` around lines 104 - 116, Update
instancesByProject to emit a scrape warning whenever an instance has an empty
project before skipping it, using the existing logging mechanism and including
enough instance context to identify the dropped instance. Preserve the current
grouping behavior for instances with a non-empty project.
scrapers/gcp/iam_test.go (1)

537-541: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the duplicated role loop.

Lines 538-541 iterate the same three roles as the loop that ends at Line 537 and call findRoleConfig a second time. Move the parent assertion into the first loop and reuse rc.

♻️ Proposed merge
 		g.Expect(rc.Aliases).To(gomega.ContainElement(role))
-	}
-	for _, role := range []string{roleOwner, roleStorage, roleCustom} {
-		g.Expect(findRoleConfig(res.RoleConfigs, role).Parents).To(gomega.ContainElement(
+		g.Expect(rc.Parents).To(gomega.ContainElement(
 			v1.ConfigExternalKey{Type: v1.GCPProject, ExternalID: project},
 		))
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/iam_test.go` around lines 537 - 541, Merge the duplicated role
iteration in the IAM test by moving the parent assertion into the preceding
loop, reusing its existing role configuration variable (rc) instead of calling
findRoleConfig again. Remove the second loop while preserving the assertions for
roleOwner, roleStorage, and roleCustom.
scrapers/gcp/iam_hierarchy.go (1)

53-61: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

A non-project, non-organization parent would produce a wrong OrganizationID.

Line 53 treats any parent without a project segment as an organization root. For a hypothetical folders/123 parent, strings.TrimPrefix does not match organizationPrefix, so OrganizationID becomes "folders/123". That value is then stamped as the tenant on every identity through scopeFor.

The current configuration surface exposes only organization, projects, and project, so this path is unreachable today. Add an explicit prefix check so a future folder root fails loudly instead of tenanting incorrectly.

♻️ Proposed guard
 	if projectFromParent(parent) == "" {
-		hierarchy := resourceHierarchy{OrganizationID: strings.TrimPrefix(parent, organizationPrefix)}
+		organization, ok := strings.CutPrefix(parent, organizationPrefix)
+		if !ok {
+			return resourceHierarchy{}, fmt.Errorf("unsupported GCP scrape root %q", parent)
+		}
+		hierarchy := resourceHierarchy{OrganizationID: organization}
 		node, err := fetchResourceManagerNode(ctx, service, parent)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/iam_hierarchy.go` around lines 53 - 61, Update the non-project
branch in the resource hierarchy logic around projectFromParent to validate that
parent starts with organizationPrefix before constructing resourceHierarchy.
Return an error for any unsupported non-project, non-organization parent such as
a folder, and preserve the existing organization-root handling only for valid
organization parents.
scrapers/gcp/iam.go (1)

334-342: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

coalesceIAMRoleConfigs mutates the caller's Config map in place.

existing.Config and the duplicate result.Config are distinct maps, but target is the same map instance held by the input slice element that was kept. Writing into it mutates a value the caller still owns. processResults discards the input slice, so there is no current defect. Copy the map before merging if this function is later reused on results that are retained.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/iam.go` around lines 334 - 342, Update coalesceIAMRoleConfigs so
merging duplicate IAM role configs does not mutate the map owned by the retained
input element. Before adding missing entries from result.Config, create a copy
of existing.Config, merge into that copy, and assign the copy to the retained
result; preserve existing values when keys overlap.
scrapers/gcp/gcp.go (2)

438-443: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

securityCenterParents is an identity function.

The function returns its argument unchanged. It adds a call layer without logic. Inline it and keep the explanation as a comment at the call site.

♻️ Proposed simplification
-// securityCenterParents returns the already-resolved scrape roots. An
-// unrestricted organization has one organization root, while a narrowed
-// organization has only its selected project roots.
-func securityCenterParents(parents []string) []string {
-	return parents
-}
-
-			for _, parent := range securityCenterParents(parents) {
+			for _, parent := range parents {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/gcp.go` around lines 438 - 443, Remove the redundant
securityCenterParents function and inline its callers to use the resolved
parents slice directly. Preserve the explanation about organization and project
roots as a comment at the relevant call site.

288-293: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Index parity between results and ancestries is correct but implicit.

Both slices are appended only together at Lines 350-354, so ancestries[i] always matches results[i]. Any future continue added between the two appends would break this pairing and panic or mis-attribute projects. Consider storing the ancestry on a small struct alongside the result, or adding a length assertion before the resolution loop.

♻️ Optional: make the pairing explicit
 	for i := range results {
+		// results and ancestries are appended in lockstep above.
 		ancestry := ancestries[i]

Also applies to: 304-305, 351-384

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/gcp.go` around lines 288 - 293, Make the relationship between
each result and its ancestry explicit in the flow around resolver, results, and
ancestries. Prefer storing both values together in a small paired structure and
update the append and resolution loop accordingly; if retaining separate slices,
add a length assertion before resolution to prevent mismatched indexing.
scrapers/gcp/audit_logs_test.go (1)

117-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the organization-scope acceptance case.

Both assertions at Lines 117-121 pass because BigQueryRow{} has an empty ProjectID. The organization entry therefore proves only the empty-project rejection, not the organization behavior. The documented organization path — an organization parent produces no scopedProjects, so every resolved project is accepted — has no coverage.

💚 Proposed additional case
 	_, ok = auditLogAffectedProject(BigQueryRow{}, []string{"organizations/1234"})
 	g.Expect(ok).To(gomega.BeFalse(), "an organization scrape must not fall back to the sink project")
+
+	project, ok = auditLogAffectedProject(
+		BigQueryRow{ProjectID: "any-member-project"},
+		[]string{"organizations/1234"},
+	)
+	g.Expect(ok).To(gomega.BeTrue(), "an organization scrape accepts every affected project")
+	g.Expect(project).To(gomega.Equal("any-member-project"))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/audit_logs_test.go` around lines 117 - 122, Add an
organization-scope acceptance test alongside the existing
auditLogAffectedProject cases, using a BigQueryRow with a non-empty project ID
and organization scope input such as organizations/1234. Assert that the project
is returned and the success flag is true, covering the documented behavior that
organization scope accepts every resolved project.
scrapers/gcp/iam_scope_test.go (1)

61-69: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

This spec does not exercise fetchResourceManagerHierarchy.

The Describe block is named fetchResourceManagerHierarchy salvage, but the body builds a resourceHierarchy literal and calls only scopeFor. scopeFor is already covered at Lines 14-32. The documented salvage behavior — fetchResourceManagerHierarchy recording OrganizationID from the parent chain before a node read fails — has no coverage.

Rename the block to describe what it checks, or extend fetchResourceManagerHierarchy with an injectable node fetcher so the salvage path can be tested directly. I can draft the injectable-fetcher version if that helps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scrapers/gcp/iam_scope_test.go` around lines 61 - 69, The test labeled for
fetchResourceManagerHierarchy salvage only verifies scopeFor and does not cover
the documented traversal behavior. Rename the Describe block and test
description to reflect the scopeFor organization tenant mapping they actually
exercise, or modify fetchResourceManagerHierarchy to accept an injectable node
fetcher and add a test that triggers a node-read failure after recording the
parent OrganizationID.
api/v1/gcp.go (1)

49-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider CEL admission validation for organization/projects requirement.

Validate() requires an organization or at least one project, but this check only runs at scrape time. If gcp.Organization is empty and gcp.ConfiguredProjects() has no entries, the config returns "one of organization or projects must be set". A ScrapeConfig missing both fields is still accepted by the Kubernetes API server, and the invalid state only surfaces as a scrape-time error rather than at kubectl apply time.

Since the CRD is generated with controller-gen.kubebuilder.io/version: v0.19.0, add a +kubebuilder:validation:XValidation CEL rule on the GCP struct to enforce this at admission time, then regenerate the CRD.

Also applies to: 136-142

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/v1/gcp.go` around lines 49 - 99, The GCP configuration currently defers
the required organization-or-projects check to scrape time. Add a kubebuilder
XValidation CEL rule on the GCP struct that rejects configurations where
Organization is empty and the effective project list, including the Project
alias via ConfiguredProjects(), has no entries; then regenerate the CRD using
the existing controller-gen setup.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fixtures/gcp.yaml`:
- Around line 8-13: Update the include-related fixture comments near the
organization/projects example to clarify that IAMPolicy and IAMGroupMembers run
by default only when include is empty. Extend the asset-type filtering example
to explicitly list the IAM collection flags so enabling the filter does not omit
IAM data unintentionally.

In `@scrapers/gcp/iam_hierarchy.go`:
- Around line 259-271: Document the Resource Manager conditional-binding
behavior change around the binding conversion in the IAM hierarchy flow:
bindings with a non-nil Condition are preserved but excluded from effective
access by buildIAMAccess, which emits a bounded warning. Include an
operator-facing change notice for users relying on the previous unconditional
edges; no implementation changes are needed.

---

Outside diff comments:
In `@scrapers/gcp/audit_logs.go`:
- Around line 197-217: Update FetchAuditLogs and its reconciliation flow to
clean up previously persisted access rows keyed to the configured dataset
project when affectedProject changes, deleting or reconciling orphaned rows so
stale deleted_at entries do not remain. Preserve the new affectedProject-based
ExternalID and access ID generation, and document this key change in the upgrade
notes.

---

Nitpick comments:
In `@api/v1/gcp.go`:
- Around line 49-99: The GCP configuration currently defers the required
organization-or-projects check to scrape time. Add a kubebuilder XValidation CEL
rule on the GCP struct that rejects configurations where Organization is empty
and the effective project list, including the Project alias via
ConfiguredProjects(), has no entries; then regenerate the CRD using the existing
controller-gen setup.

In `@scrapers/gcp/audit_logs_test.go`:
- Around line 117-122: Add an organization-scope acceptance test alongside the
existing auditLogAffectedProject cases, using a BigQueryRow with a non-empty
project ID and organization scope input such as organizations/1234. Assert that
the project is returned and the success flag is true, covering the documented
behavior that organization scope accepts every resolved project.

In `@scrapers/gcp/cloudsql_backup_test.go`:
- Around line 100-109: Rename the integer counter currently named errors in the
results loop to a more specific name, and update its increment and final Expect
assertion accordingly; leave the standard errors package and other result
handling unchanged.

In `@scrapers/gcp/cloudsql_backup.go`:
- Around line 104-116: Update instancesByProject to emit a scrape warning
whenever an instance has an empty project before skipping it, using the existing
logging mechanism and including enough instance context to identify the dropped
instance. Preserve the current grouping behavior for instances with a non-empty
project.

In `@scrapers/gcp/gcp.go`:
- Around line 438-443: Remove the redundant securityCenterParents function and
inline its callers to use the resolved parents slice directly. Preserve the
explanation about organization and project roots as a comment at the relevant
call site.
- Around line 288-293: Make the relationship between each result and its
ancestry explicit in the flow around resolver, results, and ancestries. Prefer
storing both values together in a small paired structure and update the append
and resolution loop accordingly; if retaining separate slices, add a length
assertion before resolution to prevent mismatched indexing.

In `@scrapers/gcp/iam_hierarchy.go`:
- Around line 53-61: Update the non-project branch in the resource hierarchy
logic around projectFromParent to validate that parent starts with
organizationPrefix before constructing resourceHierarchy. Return an error for
any unsupported non-project, non-organization parent such as a folder, and
preserve the existing organization-root handling only for valid organization
parents.

In `@scrapers/gcp/iam_scope_test.go`:
- Around line 61-69: The test labeled for fetchResourceManagerHierarchy salvage
only verifies scopeFor and does not cover the documented traversal behavior.
Rename the Describe block and test description to reflect the scopeFor
organization tenant mapping they actually exercise, or modify
fetchResourceManagerHierarchy to accept an injectable node fetcher and add a
test that triggers a node-read failure after recording the parent
OrganizationID.

In `@scrapers/gcp/iam_test.go`:
- Around line 537-541: Merge the duplicated role iteration in the IAM test by
moving the parent assertion into the preceding loop, reusing its existing role
configuration variable (rc) instead of calling findRoleConfig again. Remove the
second loop while preserving the assertions for roleOwner, roleStorage, and
roleCustom.

In `@scrapers/gcp/iam.go`:
- Around line 334-342: Update coalesceIAMRoleConfigs so merging duplicate IAM
role configs does not mutate the map owned by the retained input element. Before
adding missing entries from result.Config, create a copy of existing.Config,
merge into that copy, and assign the copy to the retained result; preserve
existing values when keys overlap.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 28495f7d-9678-4687-a32d-f61435b5e35f

📥 Commits

Reviewing files that changed from the base of the PR and between 98f287b and dfeec39.

📒 Files selected for processing (23)
  • api/v1/gcp.go
  • api/v1/gcp_test.go
  • api/v1/zz_generated.deepcopy.go
  • chart/crds/configs.flanksource.com_scrapeconfigs.yaml
  • config/schemas/config_gcp.schema.json
  • config/schemas/scrape_config.schema.json
  • fixtures/gcp.yaml
  • scrapers/gcp/ancestry.go
  • scrapers/gcp/ancestry_test.go
  • scrapers/gcp/audit_logs.go
  • scrapers/gcp/audit_logs_test.go
  • scrapers/gcp/cloudsql_backup.go
  • scrapers/gcp/cloudsql_backup_test.go
  • scrapers/gcp/gcp.go
  • scrapers/gcp/gcp_test.go
  • scrapers/gcp/iam.go
  • scrapers/gcp/iam_group_members.go
  • scrapers/gcp/iam_hierarchy.go
  • scrapers/gcp/iam_scope_test.go
  • scrapers/gcp/iam_test.go
  • scrapers/gcp/security_center.go
  • scrapers/gcp/targets.go
  • scrapers/gcp/targets_test.go

Comment thread fixtures/gcp.yaml
Comment thread scrapers/gcp/iam_hierarchy.go
@moshloop
moshloop merged commit 47526a3 into main Aug 5, 2026
17 of 18 checks passed
@moshloop
moshloop deleted the gcp-org branch August 5, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GCP - Add organizations

2 participants