Skip to content

fix(deploy): manage Cloud Resource Manager API enablement in Pulumi#1213

Merged
rdimitrov merged 3 commits intomainfrom
enable-crm-api-pulumi
Apr 27, 2026
Merged

fix(deploy): manage Cloud Resource Manager API enablement in Pulumi#1213
rdimitrov merged 3 commits intomainfrom
enable-crm-api-pulumi

Conversation

@rdimitrov
Copy link
Copy Markdown
Member

@rdimitrov rdimitrov commented Apr 27, 2026

Summary

Follow-up to #1211 to encode the GCP API and IAM dependencies its node-SA bindings rely on. After #1211 merged, the staging deploy hit two undocumented prereqs in sequence — Cloud Resource Manager API not enabled (because projects.NewIAMMember calls SetIamPolicy through CRM), then the Pulumi service account missing roles/resourcemanager.projectIamAdmin. Both fixed manually to unblock; this PR makes them explicit so they don't bite again.

Changes

ensureRequiredAPIs adopts five GCP APIs as Pulumi-managed projects.Service resources, all with DisableOnDestroy: false and DisableDependentServices: false so a Pulumi destroy/refactor can never disable a shared API:

API Why
cloudresourcemanager projects.NewIAMMember → SetIamPolicy
compute compute.GetDefaultServiceAccount invoke + GKE
container GKE cluster
logging fluentbit-gke ships container logs
monitoring managed Prometheus collector ships metrics

CRM is created explicitly (not in the loop) so callers get a direct reference for pulumi.DependsOn. Storage is intentionally not Pulumi-managed — Pulumi state itself lives in a GCS bucket, chicken-and-egg.

grantNodeServiceAccountRoles now DependsOn the CRM projects.Service for the four IAM bindings, and uses compute.GetDefaultServiceAccount to derive the SA email (Compute API was already required for GKE — avoids a CRM-dependent project-number lookup).

deploy/README.md — adds roles/resourcemanager.projectIamAdmin to the Pulumi SA's required roles and clarifies which APIs must be enabled before the first pulumi up (storage, cloudresourcemanager, container) vs. which ensureRequiredAPIs adopts.

Notes

projects.Service is idempotent against already-enabled APIs — on existing projects (staging/prod), Pulumi adopts the live enablement into state without re-enabling. This was confirmed live: staging deploy went green after the manual unblocks above, validating the runtime model.

Test plan

  • go build ./... clean for deploy/
  • golangci-lint run clean for deploy/pkg/providers/gcp/... (one pre-existing nilnil at line 61, unrelated)
  • Manual unblock + green staging deploy verified the runtime behavior this PR encodes
  • On merge: staging deploy adopts the existing API enablements without churn, then proceeds normally
  • Prod deploy via deploy: update prod to v1.7.1 #1212 (image promotion) inherits the same setup

🤖 Generated with Claude Code

rdimitrov and others added 3 commits April 27, 2026 17:57
The IAM bindings added in #1211 fail on a fresh GCP project because they
depend on the Cloud Resource Manager API being enabled, which isn't a
default for new projects. Staging deploy hit this directly; the manual
`gcloud services enable cloudresourcemanager.googleapis.com` command
unblocks it but isn't drift-protected.

- Add a projects.Service resource for cloudresourcemanager.googleapis.com
  with DisableOnDestroy=false so Pulumi adopts the API enablement and
  re-enables it on accidental disable, but never tears it down on its own.
- Make the node-SA IAM bindings explicitly DependsOn the API service so
  the dependency is encoded rather than implicit.
- Replace organizations.LookupProject (CRM-dependent) with
  compute.GetDefaultServiceAccount, which uses the Compute API (already
  required for GKE) and returns the SA email directly. Removes the
  bootstrap chicken-and-egg between the API enablement and the project
  number lookup that the original code performed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the CRM API enablement to also encode container, compute, logging,
and monitoring as Pulumi-managed projects.Service resources, all with
DisableOnDestroy=false so a Pulumi destroy never disables a shared API.
Same drift-protection / new-project bootstrap reasoning as CRM.

Refactors the API enablement into a small ensureRequiredAPIs helper that
returns the CRM resource so the IAM bindings can DependsOn it explicitly.

Also updates deploy/README.md to:

- List `roles/resourcemanager.projectIamAdmin` as a required Pulumi SA
  role. Without it the deploy fails with "Error retrieving IAM policy ...
  forbidden" when Pulumi tries to create the node-SA IAM bindings. This
  was the second prereq we hit after the CRM API enablement.
- Reduce the bootstrap API list to the three actually required to first
  reach Pulumi (storage for the state bucket, cloudresourcemanager for
  the IAM bindings, container for the GKE cluster). The rest are adopted
  by ensureRequiredAPIs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Addresses review feedback on the ensureRequiredAPIs helper:

- Create the CRM service explicitly outside the loop instead of
  detecting it via a string match on the resource name. The previous
  shape silently broke the IAM-binding DependsOn if anyone renamed
  "crm-api" — extracting it makes the data-flow obvious and removes
  the accumulator variable.
- Reword the helper doc to acknowledge that bootstrap APIs (storage,
  cloudresourcemanager, container) are adopted *after* their consumers
  run, since those resources are created earlier in the stack. The
  previous wording overclaimed that the helper ensures-then-uses every
  API, which only matches reality for logging/monitoring.
- Drop the misleading "rough dependency order" sentence — the loop
  resources have no DependsOn between them, so there's no order to
  speak of.

No functional change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rdimitrov rdimitrov merged commit c6d3096 into main Apr 27, 2026
6 checks passed
@rdimitrov rdimitrov deleted the enable-crm-api-pulumi branch April 27, 2026 16:32
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.

2 participants