@caelo-cms/provisioning 0.3.9 → 0.3.10
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 +2 -2
- package/stacks/gcp-firebase/index.ts +67 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@caelo-cms/provisioning",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
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.
|
|
41
|
+
"@caelo-cms/shared": "^0.3.10",
|
|
42
42
|
"@clack/prompts": "1.3.0",
|
|
43
43
|
"@pulumi/aws": "^7",
|
|
44
44
|
"@pulumi/awsx": "^3",
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* Shared code lives in `packages/provisioning/src/` (imported here).
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
|
+
import * as command from "@pulumi/command";
|
|
28
29
|
import * as gcp from "@pulumi/gcp";
|
|
29
30
|
import * as pulumi from "@pulumi/pulumi";
|
|
30
31
|
import * as random from "@pulumi/random";
|
|
@@ -465,18 +466,26 @@ const adminSvc = cloudRunService({
|
|
|
465
466
|
// to proxy traffic via `rewrites` to Cloud Run. Grant it
|
|
466
467
|
// `roles/run.invoker` on the gateway so /api/** requests succeed.
|
|
467
468
|
//
|
|
468
|
-
// v0.3.
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
|
|
469
|
+
// v0.3.10 — `gcp.projects.ServiceIdentity` is NOT supported for
|
|
470
|
+
// `firebasehosting.googleapis.com` (the GenerateServiceIdentity API
|
|
471
|
+
// returns IAM_SERVICE_NOT_CONFIGURED_FOR_IDENTITIES for it). The
|
|
472
|
+
// `gcloud beta services identity create` CLI command works where
|
|
473
|
+
// the GenerateServiceIdentity API does not — it falls back to a
|
|
474
|
+
// different bootstrap path. We shell out via @pulumi/command,
|
|
475
|
+
// which is idempotent (gcloud returns existing if the SA is
|
|
476
|
+
// already provisioned). The SA email is deterministic from the
|
|
477
|
+
// project number, so we construct it independently.
|
|
478
|
+
const firebaseHostingIdentityCmd = new command.local.Command(
|
|
475
479
|
`${namePrefix}-firebasehosting-identity`,
|
|
476
|
-
{
|
|
480
|
+
{
|
|
481
|
+
create: pulumi.interpolate`gcloud beta services identity create --service=firebasehosting.googleapis.com --project=${project} --quiet`,
|
|
482
|
+
},
|
|
477
483
|
opts,
|
|
478
484
|
);
|
|
479
485
|
|
|
486
|
+
const projectInfo = gcp.organizations.getProjectOutput({ projectId: project }, opts);
|
|
487
|
+
const firebaseHostingSa = pulumi.interpolate`serviceAccount:service-${projectInfo.number}@gcp-sa-firebasehosting.iam.gserviceaccount.com`;
|
|
488
|
+
|
|
480
489
|
new gcp.cloudrun.IamMember(
|
|
481
490
|
`${namePrefix}-gateway-firebase-invoker`,
|
|
482
491
|
{
|
|
@@ -484,27 +493,43 @@ new gcp.cloudrun.IamMember(
|
|
|
484
493
|
project,
|
|
485
494
|
service: gatewaySvc.name,
|
|
486
495
|
role: "roles/run.invoker",
|
|
487
|
-
member:
|
|
496
|
+
member: firebaseHostingSa,
|
|
488
497
|
},
|
|
489
|
-
{ ...opts, dependsOn: [
|
|
498
|
+
{ ...opts, dependsOn: [firebaseHostingIdentityCmd] },
|
|
490
499
|
);
|
|
491
500
|
|
|
492
501
|
// =========================================================================
|
|
493
502
|
// Admin custom domain — Cloud Run domain mapping
|
|
494
503
|
// =========================================================================
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
504
|
+
//
|
|
505
|
+
// v0.3.10 — Cloud Run DomainMapping requires the operator to verify
|
|
506
|
+
// domain ownership in Google Search Console FIRST. Without that
|
|
507
|
+
// verification the create fails with "Caller is not authorized to
|
|
508
|
+
// administer the domain ..." even when the provisioner SA has full
|
|
509
|
+
// admin rights on the project. This is a one-time manual step we
|
|
510
|
+
// cannot automate.
|
|
511
|
+
//
|
|
512
|
+
// To keep the install working out of the box, the DomainMapping is
|
|
513
|
+
// opt-in via `provisionAdminDomain=true`. When disabled (default),
|
|
514
|
+
// the operator reaches the admin via the Cloud Run-generated URL
|
|
515
|
+
// (`https://<admin-svc>-<hash>-<region>.a.run.app`). They can flip
|
|
516
|
+
// the knob + verify the domain at https://search.google.com/search-console
|
|
517
|
+
// later and run `pulumi up` to bind admin.<domain> → Cloud Run.
|
|
518
|
+
const provisionAdminDomain = cfg.getBoolean("provisionAdminDomain") ?? false;
|
|
519
|
+
const adminDomainMapping = provisionAdminDomain
|
|
520
|
+
? new gcp.cloudrun.DomainMapping(
|
|
521
|
+
`${namePrefix}-admin-domain`,
|
|
522
|
+
{
|
|
523
|
+
location: region,
|
|
524
|
+
name: adminDomain,
|
|
525
|
+
metadata: { namespace: project },
|
|
526
|
+
spec: {
|
|
527
|
+
routeName: adminSvc.name,
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
{ ...opts, dependsOn: [adminSvc] },
|
|
531
|
+
)
|
|
532
|
+
: undefined;
|
|
508
533
|
|
|
509
534
|
// =========================================================================
|
|
510
535
|
// IAP on the admin Cloud Run service (no LB)
|
|
@@ -521,13 +546,19 @@ const adminDomainMapping = new gcp.cloudrun.DomainMapping(
|
|
|
521
546
|
// shipped. If your provider build is older than v8.x, run
|
|
522
547
|
// `gcloud beta run services update <admin-service> --iap` as a
|
|
523
548
|
// post-up step. The stack provisions the IAM bindings either way.
|
|
549
|
+
// v0.3.10 — Cloud Run v1 IAM API (`gcp.cloudrun.IamMember`) rejects
|
|
550
|
+
// `roles/iap.httpsResourceAccessor` with "Role is not supported for
|
|
551
|
+
// this resource". The v2 IAM API (`gcp.cloudrunv2.ServiceIamMember`)
|
|
552
|
+
// is the right binding for native-IAP-on-Cloud-Run, matching
|
|
553
|
+
// Google's documented setup: `gcloud run services
|
|
554
|
+
// add-iam-policy-binding ... --role=roles/iap.httpsResourceAccessor`.
|
|
524
555
|
for (const principal of iapAllowlist) {
|
|
525
|
-
new gcp.
|
|
556
|
+
new gcp.cloudrunv2.ServiceIamMember(
|
|
526
557
|
`${namePrefix}-admin-iap-${principal.replace(/[^a-z0-9]/gi, "-").slice(0, 40)}`,
|
|
527
558
|
{
|
|
528
559
|
location: region,
|
|
529
560
|
project,
|
|
530
|
-
|
|
561
|
+
name: adminSvc.name,
|
|
531
562
|
role: "roles/iap.httpsResourceAccessor",
|
|
532
563
|
member: principal,
|
|
533
564
|
},
|
|
@@ -539,12 +570,22 @@ for (const principal of iapAllowlist) {
|
|
|
539
570
|
// Outputs — DNS records the operator wires manually
|
|
540
571
|
// =========================================================================
|
|
541
572
|
|
|
573
|
+
const adminMappingStatuses: pulumi.Output<
|
|
574
|
+
gcp.types.output.cloudrun.DomainMappingStatus[] | undefined
|
|
575
|
+
> = adminDomainMapping
|
|
576
|
+
? adminDomainMapping.statuses
|
|
577
|
+
: (pulumi.output(undefined) as pulumi.Output<
|
|
578
|
+
gcp.types.output.cloudrun.DomainMappingStatus[] | undefined
|
|
579
|
+
>);
|
|
580
|
+
|
|
542
581
|
const dnsRecords: pulumi.Output<DnsRecord[]> = pulumi
|
|
543
|
-
.all([
|
|
582
|
+
.all([adminMappingStatuses, firebaseCustomDomain.requiredDnsUpdates])
|
|
544
583
|
.apply(([adminStatuses, firebaseUpdates]) => {
|
|
545
584
|
const out: DnsRecord[] = [];
|
|
546
585
|
|
|
547
586
|
// Admin subdomain — Cloud Run domain mapping prints CNAMEs/As.
|
|
587
|
+
// When `provisionAdminDomain=false`, no mapping exists and the
|
|
588
|
+
// operator reaches admin via the *.run.app URL printed separately.
|
|
548
589
|
const adminTargets =
|
|
549
590
|
adminStatuses
|
|
550
591
|
?.flatMap((s) => s.resourceRecords ?? [])
|