@caelo-cms/provisioning 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caelo-cms/provisioning",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "private": false,
5
5
  "license": "MPL-2.0",
6
6
  "description": "Pulumi-driven provisioning for Caelo CMS — `bunx @caelo-cms/provisioning --provider <self-hosted|gcp|aws|azure>` brings up a TLS-served install with Postgres + storage + secrets manager + the admin + the API gateway.",
@@ -38,7 +38,7 @@
38
38
  "prepublishOnly": "tsc -b --force && tsc --project stacks/gcp/tsconfig.json && tsc --project stacks/gcp-firebase/tsconfig.json"
39
39
  },
40
40
  "dependencies": {
41
- "@caelo-cms/shared": "^0.3.7",
41
+ "@caelo-cms/shared": "^0.3.9",
42
42
  "@clack/prompts": "1.3.0",
43
43
  "@pulumi/aws": "^7",
44
44
  "@pulumi/awsx": "^3",
@@ -59,10 +59,20 @@ const iapAllowlist =
59
59
  const env = pulumi.getStack() as "dev" | "staging" | "production";
60
60
  const namePrefix = `caelo-${env}`;
61
61
  const adminDomain = `admin.${domain}`;
62
- // Firebase site IDs are GLOBALLY unique across all Firebase projects.
63
- // Default uses the namePrefix; operator can override if the default
64
- // collides with another project's site.
65
- const firebaseSiteId = cfg.get("firebaseSiteId") || `${namePrefix}-site`;
62
+ // Firebase site IDs are GLOBALLY unique across all Firebase projects —
63
+ // and reserved for ~30 days even after the parent project is deleted.
64
+ // A fixed default like `caelo-production-site` collides on re-install
65
+ // or after a failed run against a soft-deleted project. The suffix is
66
+ // a `random.RandomId` so it persists in Pulumi state across `up` runs
67
+ // but stays unique across installs. Operator override stays available
68
+ // for branded URLs (e.g. `cms.example.com` → `cms-example-com`).
69
+ const firebaseSiteSuffix = new random.RandomId(`${namePrefix}-firebase-site-suffix`, {
70
+ byteLength: 3, // 6 hex chars — `caelo-production-site-abc123`
71
+ });
72
+ const firebaseSiteIdOverride = cfg.get("firebaseSiteId");
73
+ const firebaseSiteId: pulumi.Input<string> = firebaseSiteIdOverride
74
+ ? firebaseSiteIdOverride
75
+ : pulumi.interpolate`${namePrefix}-site-${firebaseSiteSuffix.hex}`;
66
76
 
67
77
  const gcpProvider = new gcp.Provider(`${namePrefix}-gcp`, { project, region });
68
78
  const opts = { provider: gcpProvider };
@@ -454,12 +464,18 @@ const adminSvc = cloudRunService({
454
464
  // v0.3.1 — Firebase Hosting uses a Google-managed service identity
455
465
  // to proxy traffic via `rewrites` to Cloud Run. Grant it
456
466
  // `roles/run.invoker` on the gateway so /api/** requests succeed.
457
- // The SA name follows the standard `service-<project-number>@
458
- // gcp-sa-firebasehosting.iam.gserviceaccount.com` shape. We fetch
459
- // the project number via `gcp.organizations.getProject` rather than
460
- // hardcoding it.
461
- const projectInfo = gcp.organizations.getProjectOutput({ projectId: project }, opts);
462
- const firebaseHostingSa = pulumi.interpolate`serviceAccount:service-${projectInfo.number}@gcp-sa-firebasehosting.iam.gserviceaccount.com`;
467
+ //
468
+ // v0.3.9 — The Firebase Hosting service identity SA is created
469
+ // on-demand, not when the API is merely enabled. Constructing the
470
+ // email by name and granting IAM to it fails with "Service account
471
+ // ... does not exist". `gcp.projects.ServiceIdentity` triggers
472
+ // creation and returns the email; we depend on it so the binding
473
+ // always sees the SA. Same pattern the gcp stack uses for IAP.
474
+ const firebaseHostingIdentity = new gcp.projects.ServiceIdentity(
475
+ `${namePrefix}-firebasehosting-identity`,
476
+ { project, service: "firebasehosting.googleapis.com" },
477
+ opts,
478
+ );
463
479
 
464
480
  new gcp.cloudrun.IamMember(
465
481
  `${namePrefix}-gateway-firebase-invoker`,
@@ -468,9 +484,9 @@ new gcp.cloudrun.IamMember(
468
484
  project,
469
485
  service: gatewaySvc.name,
470
486
  role: "roles/run.invoker",
471
- member: firebaseHostingSa,
487
+ member: pulumi.interpolate`serviceAccount:${firebaseHostingIdentity.email}`,
472
488
  },
473
- opts,
489
+ { ...opts, dependsOn: [firebaseHostingIdentity] },
474
490
  );
475
491
 
476
492
  // =========================================================================