@caelo-cms/provisioning 0.10.46 → 0.10.48
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/dist/adc-impersonation.d.ts.map +1 -1
- package/dist/adc-impersonation.js +23 -1
- package/dist/adc-impersonation.js.map +1 -1
- package/dist/agent/apply.d.ts +45 -0
- package/dist/agent/apply.d.ts.map +1 -0
- package/dist/agent/apply.js +83 -0
- package/dist/agent/apply.js.map +1 -0
- package/dist/agent/cli-provenance.d.ts +42 -0
- package/dist/agent/cli-provenance.d.ts.map +1 -0
- package/dist/agent/cli-provenance.js +138 -0
- package/dist/agent/cli-provenance.js.map +1 -0
- package/dist/agent/command.d.ts +79 -0
- package/dist/agent/command.d.ts.map +1 -0
- package/dist/agent/command.js +454 -0
- package/dist/agent/command.js.map +1 -0
- package/dist/agent/config.d.ts +30 -0
- package/dist/agent/config.d.ts.map +1 -0
- package/dist/agent/config.js +60 -0
- package/dist/agent/config.js.map +1 -0
- package/dist/agent/lock.d.ts +13 -0
- package/dist/agent/lock.d.ts.map +1 -0
- package/dist/agent/lock.js +100 -0
- package/dist/agent/lock.js.map +1 -0
- package/dist/agent/queue.d.ts +35 -0
- package/dist/agent/queue.d.ts.map +1 -0
- package/dist/agent/queue.js +50 -0
- package/dist/agent/queue.js.map +1 -0
- package/dist/agent/run.d.ts +97 -0
- package/dist/agent/run.d.ts.map +1 -0
- package/dist/agent/run.js +198 -0
- package/dist/agent/run.js.map +1 -0
- package/dist/agent/systemd.d.ts +55 -0
- package/dist/agent/systemd.d.ts.map +1 -0
- package/dist/agent/systemd.js +171 -0
- package/dist/agent/systemd.js.map +1 -0
- package/dist/agent/units.d.ts +44 -0
- package/dist/agent/units.d.ts.map +1 -0
- package/dist/agent/units.js +127 -0
- package/dist/agent/units.js.map +1 -0
- package/dist/agent-db.js +18362 -0
- package/dist/cli.js +122 -16
- package/dist/cli.js.map +1 -1
- package/dist/cloudsql-iam-user.d.ts +39 -0
- package/dist/cloudsql-iam-user.d.ts.map +1 -0
- package/dist/cloudsql-iam-user.js +119 -0
- package/dist/cloudsql-iam-user.js.map +1 -0
- package/dist/compose.d.ts +16 -0
- package/dist/compose.d.ts.map +1 -1
- package/dist/compose.js +26 -1
- package/dist/compose.js.map +1 -1
- package/dist/dns/index.d.ts +2 -0
- package/dist/dns/index.d.ts.map +1 -1
- package/dist/dns/index.js +3 -2
- package/dist/dns/index.js.map +1 -1
- package/dist/dns/manual.d.ts +5 -0
- package/dist/dns/manual.d.ts.map +1 -1
- package/dist/dns/manual.js +28 -12
- package/dist/dns/manual.js.map +1 -1
- package/dist/gcloud.d.ts +2 -0
- package/dist/gcloud.d.ts.map +1 -1
- package/dist/gcloud.js +1 -1
- package/dist/gcloud.js.map +1 -1
- package/dist/gcp-names.d.ts +15 -0
- package/dist/gcp-names.d.ts.map +1 -1
- package/dist/gcp-names.js +23 -0
- package/dist/gcp-names.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/infra-contract.d.ts +1 -1
- package/dist/infra-contract.js +7 -1
- package/dist/infra-contract.js.map +1 -1
- package/dist/install-job-runner.d.ts +13 -0
- package/dist/install-job-runner.d.ts.map +1 -0
- package/dist/install-job-runner.js +155 -0
- package/dist/install-job-runner.js.map +1 -0
- package/dist/install-job.d.ts +210 -0
- package/dist/install-job.d.ts.map +1 -0
- package/dist/install-job.js +449 -0
- package/dist/install-job.js.map +1 -0
- package/dist/install-state.d.ts +19 -1
- package/dist/install-state.d.ts.map +1 -1
- package/dist/install-state.js +31 -3
- package/dist/install-state.js.map +1 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +24 -5
- package/dist/lifecycle.js.map +1 -1
- package/dist/migration-runner.d.ts +5 -0
- package/dist/migration-runner.d.ts.map +1 -1
- package/dist/migration-runner.js +1 -1
- package/dist/migration-runner.js.map +1 -1
- package/dist/operator-access.d.ts +14 -8
- package/dist/operator-access.d.ts.map +1 -1
- package/dist/operator-access.js +16 -102
- package/dist/operator-access.js.map +1 -1
- package/dist/run-mode.d.ts +27 -0
- package/dist/run-mode.d.ts.map +1 -0
- package/dist/run-mode.js +22 -0
- package/dist/run-mode.js.map +1 -0
- package/dist/runtime-secrets.d.ts +19 -0
- package/dist/runtime-secrets.d.ts.map +1 -1
- package/dist/runtime-secrets.js +44 -0
- package/dist/runtime-secrets.js.map +1 -1
- package/dist/self-hosted-roles.d.ts +22 -1
- package/dist/self-hosted-roles.d.ts.map +1 -1
- package/dist/self-hosted-roles.js +81 -12
- package/dist/self-hosted-roles.js.map +1 -1
- package/dist/self-hosted-upgrade.d.ts +17 -0
- package/dist/self-hosted-upgrade.d.ts.map +1 -1
- package/dist/self-hosted-upgrade.js +16 -1
- package/dist/self-hosted-upgrade.js.map +1 -1
- package/dist/stack-contract.d.ts +6 -0
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +10 -1
- package/dist/stack-contract.js.map +1 -1
- package/dist/state-attach.d.ts +23 -0
- package/dist/state-attach.d.ts.map +1 -0
- package/dist/state-attach.js +112 -0
- package/dist/state-attach.js.map +1 -0
- package/dist/state-backend.d.ts +11 -0
- package/dist/state-backend.d.ts.map +1 -1
- package/dist/state-backend.js +20 -0
- package/dist/state-backend.js.map +1 -1
- package/dist/state-command.d.ts +20 -0
- package/dist/state-command.d.ts.map +1 -0
- package/dist/state-command.js +237 -0
- package/dist/state-command.js.map +1 -0
- package/dist/state-migrate.d.ts +121 -0
- package/dist/state-migrate.d.ts.map +1 -0
- package/dist/state-migrate.js +290 -0
- package/dist/state-migrate.js.map +1 -0
- package/dist/state-pulumi.d.ts +17 -0
- package/dist/state-pulumi.d.ts.map +1 -0
- package/dist/state-pulumi.js +218 -0
- package/dist/state-pulumi.js.map +1 -0
- package/dist/state-store.d.ts +27 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +65 -0
- package/dist/state-store.js.map +1 -1
- package/dist/upgrade/converge.d.ts +6 -0
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +59 -2
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/upgrade/gcp-lookup.d.ts +6 -0
- package/dist/upgrade/gcp-lookup.d.ts.map +1 -1
- package/dist/upgrade/gcp-lookup.js +25 -0
- package/dist/upgrade/gcp-lookup.js.map +1 -1
- package/dist/upgrade/release-images.d.ts +12 -0
- package/dist/upgrade/release-images.d.ts.map +1 -1
- package/dist/upgrade/release-images.js +31 -0
- package/dist/upgrade/release-images.js.map +1 -1
- package/dist/upgrade/upgrade.d.ts +5 -0
- package/dist/upgrade/upgrade.d.ts.map +1 -1
- package/dist/upgrade/upgrade.js +28 -1
- package/dist/upgrade/upgrade.js.map +1 -1
- package/dist/upgrader-assess.d.ts +82 -0
- package/dist/upgrader-assess.d.ts.map +1 -0
- package/dist/upgrader-assess.js +116 -0
- package/dist/upgrader-assess.js.map +1 -0
- package/dist/upgrader-gcp.d.ts +17 -0
- package/dist/upgrader-gcp.d.ts.map +1 -0
- package/dist/upgrader-gcp.js +87 -0
- package/dist/upgrader-gcp.js.map +1 -0
- package/dist/upgrader-job.d.ts +131 -0
- package/dist/upgrader-job.d.ts.map +1 -0
- package/dist/upgrader-job.js +259 -0
- package/dist/upgrader-job.js.map +1 -0
- package/dist/upgrader-reporter.d.ts +35 -0
- package/dist/upgrader-reporter.d.ts.map +1 -0
- package/dist/upgrader-reporter.js +79 -0
- package/dist/upgrader-reporter.js.map +1 -0
- package/dist/upgrader-setup.d.ts +59 -0
- package/dist/upgrader-setup.d.ts.map +1 -0
- package/dist/upgrader-setup.js +308 -0
- package/dist/upgrader-setup.js.map +1 -0
- package/dist/wizard.d.ts +6 -0
- package/dist/wizard.d.ts.map +1 -1
- package/dist/wizard.js +32 -4
- package/dist/wizard.js.map +1 -1
- package/dist/wizards/gcp-firebase.d.ts +3 -0
- package/dist/wizards/gcp-firebase.d.ts.map +1 -1
- package/dist/wizards/gcp-firebase.js +1 -0
- package/dist/wizards/gcp-firebase.js.map +1 -1
- package/dist/wizards/gcp-install-job.d.ts +74 -0
- package/dist/wizards/gcp-install-job.d.ts.map +1 -0
- package/dist/wizards/gcp-install-job.js +398 -0
- package/dist/wizards/gcp-install-job.js.map +1 -0
- package/dist/wizards/gcp-prompts.d.ts +3 -0
- package/dist/wizards/gcp-prompts.d.ts.map +1 -0
- package/dist/wizards/gcp-prompts.js +25 -0
- package/dist/wizards/gcp-prompts.js.map +1 -0
- package/dist/wizards/gcp-pulumi.d.ts +9 -2
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +8 -6
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts +40 -0
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +291 -83
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +6 -4
- package/src/adapter.ts +97 -0
- package/src/adc-impersonation.ts +176 -0
- package/src/admin-domain.test.ts +254 -0
- package/src/admin-domain.ts +245 -0
- package/src/admin-memory.test.ts +151 -0
- package/src/agent/apply.test.ts +185 -0
- package/src/agent/apply.ts +124 -0
- package/src/agent/cli-provenance.test.ts +159 -0
- package/src/agent/cli-provenance.ts +191 -0
- package/src/agent/command.test.ts +77 -0
- package/src/agent/command.ts +564 -0
- package/src/agent/config.ts +86 -0
- package/src/agent/lock-config.test.ts +97 -0
- package/src/agent/lock.ts +110 -0
- package/src/agent/queue.test.ts +10 -0
- package/src/agent/queue.ts +91 -0
- package/src/agent/run.test.ts +382 -0
- package/src/agent/run.ts +295 -0
- package/src/agent/systemd.ts +209 -0
- package/src/agent/units.test.ts +223 -0
- package/src/agent/units.ts +163 -0
- package/src/azure-front-door.test.ts +119 -0
- package/src/azure-front-door.ts +56 -0
- package/src/bootstrap-token.ts +20 -0
- package/src/caddy.ts +112 -0
- package/src/cdn-copy.ts +91 -0
- package/src/cert-wait.test.ts +194 -0
- package/src/cert-wait.ts +163 -0
- package/src/checkpoint-secrets.test.ts +380 -0
- package/src/checkpoint-secrets.ts +283 -0
- package/src/cli-routing.test.ts +45 -0
- package/src/cli-routing.ts +43 -0
- package/src/cli.ts +1491 -0
- package/src/cloudsql-iam-user.ts +147 -0
- package/src/compose.ts +334 -0
- package/src/default-firewall.test.ts +411 -0
- package/src/default-firewall.ts +342 -0
- package/src/deployed-release.test.ts +231 -0
- package/src/deployed-release.ts +220 -0
- package/src/dns/cloudflare.ts +182 -0
- package/src/dns/groups.test.ts +76 -0
- package/src/dns/groups.ts +86 -0
- package/src/dns/index.ts +56 -0
- package/src/dns/manual.ts +130 -0
- package/src/dns/types.ts +27 -0
- package/src/gateway-credentials.test.ts +180 -0
- package/src/gateway-credentials.ts +144 -0
- package/src/gcloud-components.test.ts +76 -0
- package/src/gcloud-components.ts +85 -0
- package/src/gcloud-retry.ts +41 -0
- package/src/gcloud-runner.test.ts +61 -0
- package/src/gcloud.test.ts +99 -0
- package/src/gcloud.ts +322 -0
- package/src/gcp-names.ts +135 -0
- package/src/index.test.ts +332 -0
- package/src/index.ts +114 -0
- package/src/infra-contract.ts +32 -0
- package/src/install-job-runner.ts +166 -0
- package/src/install-job.test.ts +566 -0
- package/src/install-job.ts +542 -0
- package/src/install-state.test.ts +239 -0
- package/src/install-state.ts +583 -0
- package/src/internal-token.ts +35 -0
- package/src/lifecycle.ts +900 -0
- package/src/mcp-iap-stacks.test.ts +58 -0
- package/src/mcp-iap.test.ts +149 -0
- package/src/mcp-iap.ts +131 -0
- package/src/media-volume.test.ts +296 -0
- package/src/migration-runner.ts +365 -0
- package/src/operator-access-stacks.test.ts +55 -0
- package/src/operator-access.test.ts +332 -0
- package/src/operator-access.ts +554 -0
- package/src/ops-backend.test.ts +320 -0
- package/src/ops-backend.ts +414 -0
- package/src/provisioner-identity.test.ts +482 -0
- package/src/provisioner-identity.ts +407 -0
- package/src/redirects-emit.ts +175 -0
- package/src/region-data.ts +428 -0
- package/src/regions.test.ts +277 -0
- package/src/regions.ts +370 -0
- package/src/release-contract.ts +57 -0
- package/src/release-manifest.test.ts +293 -0
- package/src/release-manifest.ts +430 -0
- package/src/run-mode.ts +40 -0
- package/src/runtime-secrets.test.ts +627 -0
- package/src/runtime-secrets.ts +754 -0
- package/src/self-command.test.ts +14 -0
- package/src/self-command.ts +21 -0
- package/src/self-hosted-compose.test.ts +177 -0
- package/src/self-hosted-release.ts +240 -0
- package/src/self-hosted-roles.integration.test.ts +161 -0
- package/src/self-hosted-roles.test.ts +124 -0
- package/src/self-hosted-roles.ts +203 -0
- package/src/self-hosted-upgrade.test.ts +394 -0
- package/src/self-hosted-upgrade.ts +548 -0
- package/src/stack-contract-parity.test.ts +199 -0
- package/src/stack-contract.test.ts +391 -0
- package/src/stack-contract.ts +888 -0
- package/src/stack-converge-secrets.test.ts +421 -0
- package/src/stack-converge.test.ts +833 -0
- package/src/stack-converge.ts +909 -0
- package/src/staging-admin.test.ts +246 -0
- package/src/staging-admin.ts +324 -0
- package/src/state-attach.test.ts +211 -0
- package/src/state-attach.ts +132 -0
- package/src/state-backend.test.ts +145 -0
- package/src/state-backend.ts +262 -0
- package/src/state-command.ts +293 -0
- package/src/state-migrate.test.ts +479 -0
- package/src/state-migrate.ts +434 -0
- package/src/state-pulumi.ts +289 -0
- package/src/state-store.test.ts +212 -0
- package/src/state-store.ts +243 -0
- package/src/upgrade/app-roll.ts +486 -0
- package/src/upgrade/backup.ts +109 -0
- package/src/upgrade/converge.ts +437 -0
- package/src/upgrade/gcp-lookup.ts +167 -0
- package/src/upgrade/health.test.ts +279 -0
- package/src/upgrade/health.ts +305 -0
- package/src/upgrade/release-images.test.ts +119 -0
- package/src/upgrade/release-images.ts +391 -0
- package/src/upgrade/reporter.ts +46 -0
- package/src/upgrade/upgrade.test.ts +626 -0
- package/src/upgrade/upgrade.ts +249 -0
- package/src/upgrader-assess.test.ts +265 -0
- package/src/upgrader-assess.ts +233 -0
- package/src/upgrader-gcp.ts +123 -0
- package/src/upgrader-job.test.ts +408 -0
- package/src/upgrader-job.ts +499 -0
- package/src/upgrader-reporter.test.ts +46 -0
- package/src/upgrader-reporter.ts +105 -0
- package/src/upgrader-setup.test.ts +169 -0
- package/src/upgrader-setup.ts +355 -0
- package/src/wizard.ts +425 -0
- package/src/wizards/gcp-cost.ts +188 -0
- package/src/wizards/gcp-firebase.ts +72 -0
- package/src/wizards/gcp-install-job.ts +506 -0
- package/src/wizards/gcp-prompts.ts +26 -0
- package/src/wizards/gcp-pulumi.test.ts +246 -0
- package/src/wizards/gcp-pulumi.ts +516 -0
- package/src/wizards/gcp.ts +1935 -0
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pulumi Automation SDK wrapper for the GCP wizard. Replaces the
|
|
5
|
+
* shell-out `pulumi up` flow with a programmatic equivalent that
|
|
6
|
+
* streams progress events into the wizard's UI.
|
|
7
|
+
*
|
|
8
|
+
* Per CLAUDE.md §11.C: end-users never see `pulumi` directly. The
|
|
9
|
+
* wizard handles the state backend (state-backend.ts: the install's ops
|
|
10
|
+
* bucket + KMS key, or the local dir + passphrase of an older install),
|
|
11
|
+
* config and up; failures surface inline with provider errors.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { resolve as resolvePath } from "node:path";
|
|
15
|
+
import * as pulumi from "@pulumi/pulumi/automation";
|
|
16
|
+
import { checkpointableOutputs } from "../checkpoint-secrets.js";
|
|
17
|
+
import { GCP_STACK_ENV } from "../gcp-names.js";
|
|
18
|
+
import type { ImageDigests } from "../install-state.js";
|
|
19
|
+
import {
|
|
20
|
+
AMBIENT_GOOGLE_CREDENTIAL_ENV,
|
|
21
|
+
withoutAmbientGoogleCredentials,
|
|
22
|
+
} from "../provisioner-identity.js";
|
|
23
|
+
import { ensureGeneratedSecrets, stackSecretReplication } from "../runtime-secrets.js";
|
|
24
|
+
import { ADMIN_MEMORY_DEFAULT } from "../stack-contract.js";
|
|
25
|
+
import type { ResolvedBackend } from "../state-backend.js";
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The §11.B knobs every wizard run sets (and the cost table prices). One
|
|
29
|
+
* place, so `state migrate`'s verification previews set the same config the
|
|
30
|
+
* wizard does.
|
|
31
|
+
*/
|
|
32
|
+
export const GCP_STACK_KNOB_DEFAULTS = {
|
|
33
|
+
cloudSqlTier: "db-f1-micro",
|
|
34
|
+
cloudSqlHa: false,
|
|
35
|
+
adminMinInstances: 0,
|
|
36
|
+
adminMemory: ADMIN_MEMORY_DEFAULT,
|
|
37
|
+
gatewayMinInstances: 0,
|
|
38
|
+
wafAdaptiveProtection: false,
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
export interface PulumiUpInputs {
|
|
42
|
+
installId: string;
|
|
43
|
+
installRoot: string;
|
|
44
|
+
secretsDir: string;
|
|
45
|
+
projectId: string;
|
|
46
|
+
domain: string;
|
|
47
|
+
/** #650 — the protected staging site's host (install setting). */
|
|
48
|
+
stagingDomain: string;
|
|
49
|
+
ownerEmail: string;
|
|
50
|
+
region: string;
|
|
51
|
+
/**
|
|
52
|
+
* #696 — the service account Pulumi's GCP provider impersonates
|
|
53
|
+
* (`caelo-provisioner@<project>`), with the operator's Application Default
|
|
54
|
+
* Credentials as the source. No key file. Null when this process runs AS
|
|
55
|
+
* the provisioner (the install job, install-job.ts): its ambient
|
|
56
|
+
* metadata-server credentials are the provisioner's own.
|
|
57
|
+
*/
|
|
58
|
+
impersonateServiceAccount: string | null;
|
|
59
|
+
/** #696 — where the stack's state lives (state-backend.ts resolveBackend). */
|
|
60
|
+
backend: ResolvedBackend;
|
|
61
|
+
/**
|
|
62
|
+
* #696 — the install is deployed, so its stack must already be in
|
|
63
|
+
* `backend`; never create an empty one in its place.
|
|
64
|
+
*/
|
|
65
|
+
stackMustExist?: boolean;
|
|
66
|
+
cloudSqlTier: string;
|
|
67
|
+
cloudSqlHa: boolean;
|
|
68
|
+
adminMinInstances: number;
|
|
69
|
+
/** #553 — admin memory (`2Gi`): it runs the Lighthouse quality audit. */
|
|
70
|
+
adminMemory: string;
|
|
71
|
+
gatewayMinInstances: number;
|
|
72
|
+
wafAdaptiveProtection: boolean;
|
|
73
|
+
iapAllowlist: string[];
|
|
74
|
+
/** Resolved sha256 digests per service (admin, gateway). The wizard
|
|
75
|
+
* pre-resolves the floating `:latest` release tag to a fixed digest
|
|
76
|
+
* so each pulumi up rolls Cloud Run to the newest release image. */
|
|
77
|
+
imageDigests: ImageDigests;
|
|
78
|
+
/**
|
|
79
|
+
* v0.3.1 — provider variant. Selects which stack dir to apply
|
|
80
|
+
* + which config namespace (caelo-gcp vs caelo-gcp-firebase).
|
|
81
|
+
* Defaults to 'gcp' for backwards compatibility.
|
|
82
|
+
*/
|
|
83
|
+
provider?: "gcp" | "gcp-firebase";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface PulumiUpResult {
|
|
87
|
+
/**
|
|
88
|
+
* The stack outputs safe to checkpoint: no output Pulumi marks secret and
|
|
89
|
+
* none holding a credential (the Owner setup link). Code that needs a
|
|
90
|
+
* secret output reads it with {@link readStackOutput} when it needs it
|
|
91
|
+
* (#656).
|
|
92
|
+
*/
|
|
93
|
+
outputs: Record<string, unknown>;
|
|
94
|
+
resourceCount: { created: number; updated: number; deleted: number };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve the GCP stack workdir relative to the installed package.
|
|
99
|
+
* Works whether the CLI runs from source (apps/admin/scripts/...)
|
|
100
|
+
* or from the published npm tarball (node_modules/@caelo-cms/...).
|
|
101
|
+
*/
|
|
102
|
+
export function gcpStackWorkDir(provider: "gcp" | "gcp-firebase" = "gcp"): string {
|
|
103
|
+
// The Pulumi.yaml + stacks/<provider>/index.ts ship in the npm
|
|
104
|
+
// tarball under `stacks/<provider>/`. From this file's location at
|
|
105
|
+
// runtime (dist/wizards/gcp-pulumi.js), the stack dir is two
|
|
106
|
+
// levels up + into the provider-specific stack folder.
|
|
107
|
+
return resolvePath(import.meta.dir, `../../stacks/${provider}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The stack config keys that pin each Cloud Run service's image
|
|
112
|
+
* (`<ns>:image-digest-<service>`, read by `imageRef` / `imageTag` in the
|
|
113
|
+
* stacks). Shared by the wizard and by `upgrade`'s write-back so both pin
|
|
114
|
+
* the same keys.
|
|
115
|
+
*/
|
|
116
|
+
export function imageDigestConfig(
|
|
117
|
+
provider: "gcp" | "gcp-firebase",
|
|
118
|
+
digests: ImageDigests,
|
|
119
|
+
): Record<string, { value: string }> {
|
|
120
|
+
return Object.fromEntries(
|
|
121
|
+
Object.entries(digests).map(([service, digest]) => [
|
|
122
|
+
`caelo-${provider}:image-digest-${service}`,
|
|
123
|
+
{ value: digest },
|
|
124
|
+
]),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Write the digests `upgrade` rolled to into the install's Pulumi stack
|
|
130
|
+
* config, without running `pulumi up`, so a later `pulumi up` keeps them
|
|
131
|
+
* instead of re-resolving the floating tag. On `gcp`, an `image-<service>`
|
|
132
|
+
* override wins over the digest pin (stacks/gcp `imageTag`), so any such
|
|
133
|
+
* override is removed: it no longer describes what the service runs.
|
|
134
|
+
* Needs the `pulumi` CLI (the Automation API shells out to it); throws when
|
|
135
|
+
* the CLI or the stack is missing — the caller reports that.
|
|
136
|
+
*
|
|
137
|
+
* @returns the override keys it removed.
|
|
138
|
+
*/
|
|
139
|
+
export async function writeImageDigestsToStack(inputs: {
|
|
140
|
+
backend: ResolvedBackend;
|
|
141
|
+
provider: "gcp" | "gcp-firebase";
|
|
142
|
+
digests: ImageDigests;
|
|
143
|
+
}): Promise<{ removedOverrides: string[] }> {
|
|
144
|
+
const stack = await selectInstallStack(inputs.backend, inputs.provider);
|
|
145
|
+
await stack.setAllConfig(imageDigestConfig(inputs.provider, inputs.digests));
|
|
146
|
+
const config = await stack.getAllConfig();
|
|
147
|
+
const removedOverrides = imageOverrideKeys(inputs.provider).filter((k) => k in config);
|
|
148
|
+
if (removedOverrides.length > 0) await stack.removeAllConfig(removedOverrides);
|
|
149
|
+
return { removedOverrides };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* One output of the install's stack, secrets included (the Automation API's
|
|
154
|
+
* `pulumi stack output --show-secrets`). Read at the moment of use, never
|
|
155
|
+
* checkpointed (#656). Needs the `pulumi` CLI and the stack's state.
|
|
156
|
+
*
|
|
157
|
+
* @returns the output's value, or undefined when the stack has no such output
|
|
158
|
+
*/
|
|
159
|
+
export async function readStackOutput(inputs: {
|
|
160
|
+
backend: ResolvedBackend;
|
|
161
|
+
provider: "gcp" | "gcp-firebase";
|
|
162
|
+
name: string;
|
|
163
|
+
}): Promise<unknown> {
|
|
164
|
+
const stack = await selectInstallStack(inputs.backend, inputs.provider);
|
|
165
|
+
const outputs = await stack.outputs();
|
|
166
|
+
return outputs[inputs.name]?.value;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* The stack's settings file in the package's stack dir with secrets
|
|
171
|
+
* settings that can't contradict the install's state, or null when it is
|
|
172
|
+
* already fine (#696).
|
|
173
|
+
*
|
|
174
|
+
* That file (`Pulumi.production.yaml`) lives in the package, not with the
|
|
175
|
+
* state: every `bunx` version unpacks its own, and installs of the same
|
|
176
|
+
* provider on one machine share it, so it can name another install's
|
|
177
|
+
* passphrase salt or KMS key. A file naming anything but the install's own
|
|
178
|
+
* KMS key loses its secrets-provider fields and the `secure:` values
|
|
179
|
+
* encrypted under them (no stack config the CLI sets is secret);
|
|
180
|
+
* `PULUMI_FALLBACK_TO_STATE_SECRETS_MANAGER` (resolveBackend) then makes
|
|
181
|
+
* Pulumi take the provider — KMS key or passphrase salt — from the
|
|
182
|
+
* install's own state. A local install's salt can't be checked without
|
|
183
|
+
* reading its state, so any secrets settings in the file are dropped and
|
|
184
|
+
* re-read from the state on every run.
|
|
185
|
+
*/
|
|
186
|
+
export function alignedStackSettings(
|
|
187
|
+
settings: pulumi.StackSettings,
|
|
188
|
+
backend: Pick<ResolvedBackend, "kind" | "secretsProvider">,
|
|
189
|
+
): pulumi.StackSettings | null {
|
|
190
|
+
const hasSecretsSettings =
|
|
191
|
+
settings.secretsProvider !== undefined ||
|
|
192
|
+
settings.encryptedKey !== undefined ||
|
|
193
|
+
settings.encryptionSalt !== undefined;
|
|
194
|
+
const fine =
|
|
195
|
+
backend.kind === "gcs"
|
|
196
|
+
? settings.secretsProvider === backend.secretsProvider &&
|
|
197
|
+
settings.encryptionSalt === undefined
|
|
198
|
+
: !hasSecretsSettings;
|
|
199
|
+
if (fine) return null;
|
|
200
|
+
const { secretsProvider: _p, encryptedKey: _k, encryptionSalt: _s, config, ...rest } = settings;
|
|
201
|
+
const kept = Object.fromEntries(
|
|
202
|
+
Object.entries(config ?? {}).filter(
|
|
203
|
+
([, v]) => !(v !== null && typeof v === "object" && "secure" in (v as object)),
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
return { ...rest, config: kept };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Bring the package's stack settings file in line with the install's backend
|
|
211
|
+
* ({@link alignedStackSettings}) before Pulumi reads it.
|
|
212
|
+
*/
|
|
213
|
+
export async function alignStackSettingsFile(
|
|
214
|
+
backend: ResolvedBackend,
|
|
215
|
+
provider: "gcp" | "gcp-firebase",
|
|
216
|
+
): Promise<void> {
|
|
217
|
+
const ws = await pulumi.LocalWorkspace.create({
|
|
218
|
+
workDir: gcpStackWorkDir(provider),
|
|
219
|
+
envVars: stateOnlyEnv(backend),
|
|
220
|
+
});
|
|
221
|
+
let settings: pulumi.StackSettings;
|
|
222
|
+
try {
|
|
223
|
+
settings = await ws.stackSettings(GCP_STACK_ENV);
|
|
224
|
+
} catch (e) {
|
|
225
|
+
if (/failed to find stack settings file/.test(String(e))) return;
|
|
226
|
+
throw e;
|
|
227
|
+
}
|
|
228
|
+
const aligned = alignedStackSettings(settings, backend);
|
|
229
|
+
if (aligned) await ws.saveStackSettings(GCP_STACK_ENV, aligned);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The environment for a Pulumi call that only touches the stack's state
|
|
234
|
+
* (select, config, outputs): the backend's variables, with ambient Google
|
|
235
|
+
* credentials blanked as in {@link pulumiGcpEnv} — reaching the ops bucket
|
|
236
|
+
* and KMS key must use the operator's ADC, the same as provisioning did,
|
|
237
|
+
* not a stray key file in the shell.
|
|
238
|
+
*/
|
|
239
|
+
export function stateOnlyEnv(backend: Pick<ResolvedBackend, "envVars">): Record<string, string> {
|
|
240
|
+
return {
|
|
241
|
+
...Object.fromEntries(AMBIENT_GOOGLE_CREDENTIAL_ENV.map((k) => [k, ""])),
|
|
242
|
+
...backend.envVars,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The install's existing stack, on its backend. */
|
|
247
|
+
export async function selectInstallStack(
|
|
248
|
+
backend: ResolvedBackend,
|
|
249
|
+
provider: "gcp" | "gcp-firebase",
|
|
250
|
+
): Promise<pulumi.Stack> {
|
|
251
|
+
await alignStackSettingsFile(backend, provider);
|
|
252
|
+
return pulumi.LocalWorkspace.selectStack(
|
|
253
|
+
{ stackName: GCP_STACK_ENV, workDir: gcpStackWorkDir(provider) },
|
|
254
|
+
{ envVars: stateOnlyEnv(backend) },
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Stack config keys that override the image a service runs, ahead of the
|
|
260
|
+
* digest pin. Only the `gcp` stack reads them.
|
|
261
|
+
*/
|
|
262
|
+
function imageOverrideKeys(provider: "gcp" | "gcp-firebase"): string[] {
|
|
263
|
+
return provider === "gcp" ? ["caelo-gcp:image-admin", "caelo-gcp:image-gateway"] : [];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Pick the stack's `secretReplication` (see stacks/<provider>/index.ts).
|
|
268
|
+
* A value already in config wins. Otherwise secrets that already exist
|
|
269
|
+
* in state with `auto` replication keep it — switching would replace
|
|
270
|
+
* them — and everything else (new stacks) gets `regional`.
|
|
271
|
+
*/
|
|
272
|
+
export function resolveSecretReplication(
|
|
273
|
+
configured: string | undefined,
|
|
274
|
+
stateResources: ReadonlyArray<{ type?: string; outputs?: Record<string, unknown> }>,
|
|
275
|
+
): "auto" | "regional" {
|
|
276
|
+
if (configured === "auto" || configured === "regional") return configured;
|
|
277
|
+
if (configured !== undefined) {
|
|
278
|
+
throw new Error(`secretReplication must be "auto" or "regional", got "${configured}"`);
|
|
279
|
+
}
|
|
280
|
+
const hasAutoSecret = stateResources.some((r) => {
|
|
281
|
+
if (r.type !== "gcp:secretmanager/secret:Secret") return false;
|
|
282
|
+
const replication = r.outputs?.replication as { auto?: unknown } | undefined;
|
|
283
|
+
return replication?.auto != null;
|
|
284
|
+
});
|
|
285
|
+
return hasAutoSecret ? "auto" : "regional";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** A resource as `pulumi stack export` lists it. */
|
|
289
|
+
export interface StateResource {
|
|
290
|
+
type?: string;
|
|
291
|
+
inputs?: Record<string, unknown>;
|
|
292
|
+
outputs?: Record<string, unknown>;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* The region an existing stack was deployed in, read from its state (the
|
|
297
|
+
* stacks' explicit `gcp.Provider` carries it), or null for a stack with
|
|
298
|
+
* nothing deployed. State, not stack config: the config file lives in the
|
|
299
|
+
* package's stack dir, which a newer `bunx` version doesn't share.
|
|
300
|
+
*/
|
|
301
|
+
export function deployedStackRegion(resources: ReadonlyArray<StateResource>): string | null {
|
|
302
|
+
const regions = new Set(
|
|
303
|
+
resources
|
|
304
|
+
.filter((r) => r.type === "pulumi:providers:gcp")
|
|
305
|
+
.map((r) => r.inputs?.region)
|
|
306
|
+
.filter((v): v is string => typeof v === "string" && v.length > 0),
|
|
307
|
+
);
|
|
308
|
+
if (regions.size > 1) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`the Pulumi state holds GCP providers for several regions (${[...regions].join(", ")}); fix the stack before re-running.`,
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
return [...regions][0] ?? null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Refuse to `up` a stack into another region than it was deployed in
|
|
318
|
+
* (#607): Pulumi would replace Cloud SQL, the buckets and the services.
|
|
319
|
+
*/
|
|
320
|
+
export function assertStackRegion(deployed: string | null, requested: string): void {
|
|
321
|
+
if (deployed && deployed !== requested) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`this install's stack is deployed in ${deployed}, but the run asks for ${requested}. The region is fixed after install — moving it would replace the database, buckets and services. Set "region": "${deployed}" in install.json and re-run.`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** The inputs {@link applyStackConfig} sets as stack config. */
|
|
329
|
+
export type StackConfigInputs = Pick<
|
|
330
|
+
PulumiUpInputs,
|
|
331
|
+
| "projectId"
|
|
332
|
+
| "domain"
|
|
333
|
+
| "stagingDomain"
|
|
334
|
+
| "ownerEmail"
|
|
335
|
+
| "region"
|
|
336
|
+
| "cloudSqlTier"
|
|
337
|
+
| "cloudSqlHa"
|
|
338
|
+
| "adminMinInstances"
|
|
339
|
+
| "adminMemory"
|
|
340
|
+
| "gatewayMinInstances"
|
|
341
|
+
| "wafAdaptiveProtection"
|
|
342
|
+
| "iapAllowlist"
|
|
343
|
+
| "imageDigests"
|
|
344
|
+
> & { provider: "gcp" | "gcp-firebase" };
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Set every config value the stack reads, then its `secretReplication`
|
|
348
|
+
* ({@link resolveSecretReplication}). The config file lives in the package's
|
|
349
|
+
* stack dir, so every run sets all of it — the wizard before `up`, and
|
|
350
|
+
* `state migrate` before the previews that prove a migration changed
|
|
351
|
+
* nothing (#696).
|
|
352
|
+
*
|
|
353
|
+
* @returns the secret replication now configured
|
|
354
|
+
*/
|
|
355
|
+
export async function applyStackConfig(
|
|
356
|
+
stack: pulumi.Stack,
|
|
357
|
+
inputs: StackConfigInputs,
|
|
358
|
+
stateResources: ReadonlyArray<StateResource>,
|
|
359
|
+
): Promise<"auto" | "regional"> {
|
|
360
|
+
const { provider } = inputs;
|
|
361
|
+
const ns = `caelo-${provider}`;
|
|
362
|
+
// wafAdaptiveProtection is gcp-only; the gcp-firebase stack has no
|
|
363
|
+
// Cloud Armor + ignores the key.
|
|
364
|
+
await stack.setAllConfig({
|
|
365
|
+
[`${ns}:project`]: { value: inputs.projectId },
|
|
366
|
+
[`${ns}:domain`]: { value: inputs.domain },
|
|
367
|
+
[`${ns}:stagingDomain`]: { value: inputs.stagingDomain },
|
|
368
|
+
[`${ns}:ownerEmail`]: { value: inputs.ownerEmail },
|
|
369
|
+
[`${ns}:region`]: { value: inputs.region },
|
|
370
|
+
[`${ns}:cloudSqlTier`]: { value: inputs.cloudSqlTier },
|
|
371
|
+
[`${ns}:cloudSqlHa`]: { value: String(inputs.cloudSqlHa) },
|
|
372
|
+
[`${ns}:adminMinInstances`]: { value: String(inputs.adminMinInstances) },
|
|
373
|
+
[`${ns}:adminMemory`]: { value: inputs.adminMemory },
|
|
374
|
+
[`${ns}:gatewayMinInstances`]: { value: String(inputs.gatewayMinInstances) },
|
|
375
|
+
...(provider === "gcp"
|
|
376
|
+
? { [`${ns}:wafAdaptiveProtection`]: { value: String(inputs.wafAdaptiveProtection) } }
|
|
377
|
+
: {}),
|
|
378
|
+
[`${ns}:iapAllowlist`]: { value: inputs.iapAllowlist.join(",") },
|
|
379
|
+
// v0.3.2 — anthropicApiKey config dropped. Runtime path is
|
|
380
|
+
// /settings/ai → ai_providers (KEK-encrypted).
|
|
381
|
+
...imageDigestConfig(provider, inputs.imageDigests),
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
// Read back only after the required keys are set: `pulumi config` refuses to
|
|
385
|
+
// list a stack that lacks them, which is every fresh stack.
|
|
386
|
+
const configuredReplication = await stack.getConfig(`${ns}:secretReplication`).then(
|
|
387
|
+
(v) => v.value,
|
|
388
|
+
(e: unknown) => {
|
|
389
|
+
if (/not found/i.test(String(e))) return undefined;
|
|
390
|
+
throw e;
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
const secretReplication = resolveSecretReplication(configuredReplication, stateResources);
|
|
394
|
+
await stack.setConfig(`${ns}:secretReplication`, { value: secretReplication });
|
|
395
|
+
return secretReplication;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* The environment `pulumi refresh` / `pulumi up` run with (#696).
|
|
400
|
+
*
|
|
401
|
+
* Pulumi's GCP provider impersonates the provisioner service account
|
|
402
|
+
* (`GOOGLE_IMPERSONATE_SERVICE_ACCOUNT`, read by the provider plugin itself,
|
|
403
|
+
* so it also applies to the stacks' explicit `gcp.Provider` and never enters
|
|
404
|
+
* the stack's state or its provider inputs). The source credential is the
|
|
405
|
+
* operator's Application Default Credentials — refreshable, so the provider
|
|
406
|
+
* re-mints the impersonated token whenever it expires and a run longer than
|
|
407
|
+
* an hour keeps working. Ambient credential variables are dropped so neither
|
|
408
|
+
* a key file (`GOOGLE_APPLICATION_CREDENTIALS`) nor a static token that dies
|
|
409
|
+
* after an hour can take the place of that chain. They are set to "" rather
|
|
410
|
+
* than left out: the Automation API (execa) merges this process's environment
|
|
411
|
+
* into the child's, and Google's libraries treat an empty value as unset. The
|
|
412
|
+
* backend (and, for a local install, its passphrase) are scoped to the child
|
|
413
|
+
* process only. Pulumi's CLI reaches an ops-bucket backend and its KMS key
|
|
414
|
+
* with the operator's ADC itself — the impersonation variable is read by the
|
|
415
|
+
* GCP provider plugin, not by the backend.
|
|
416
|
+
*/
|
|
417
|
+
export function pulumiGcpEnv(
|
|
418
|
+
base: NodeJS.ProcessEnv,
|
|
419
|
+
inputs: Pick<PulumiUpInputs, "backend" | "impersonateServiceAccount">,
|
|
420
|
+
): Record<string, string> {
|
|
421
|
+
return {
|
|
422
|
+
...withoutAmbientGoogleCredentials(base),
|
|
423
|
+
...Object.fromEntries(AMBIENT_GOOGLE_CREDENTIAL_ENV.map((k) => [k, ""])),
|
|
424
|
+
GOOGLE_IMPERSONATE_SERVICE_ACCOUNT: inputs.impersonateServiceAccount ?? "",
|
|
425
|
+
...inputs.backend.envVars,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Run `pulumi up` against the GCP stack via the Automation SDK.
|
|
431
|
+
* Streams resource-create events to the supplied onEvent callback so
|
|
432
|
+
* the wizard can render a live progress bar.
|
|
433
|
+
*/
|
|
434
|
+
export async function pulumiUpGcp(
|
|
435
|
+
inputs: PulumiUpInputs,
|
|
436
|
+
onEvent: (kind: "resource" | "error" | "log", message: string) => void,
|
|
437
|
+
): Promise<PulumiUpResult> {
|
|
438
|
+
const env = pulumiGcpEnv(process.env, inputs);
|
|
439
|
+
|
|
440
|
+
const provider = inputs.provider ?? "gcp";
|
|
441
|
+
const stackName = GCP_STACK_ENV;
|
|
442
|
+
const workDir = gcpStackWorkDir(provider);
|
|
443
|
+
|
|
444
|
+
await alignStackSettingsFile(inputs.backend, provider);
|
|
445
|
+
const workspaceOpts = {
|
|
446
|
+
envVars: env,
|
|
447
|
+
// Used when the stack is created: a new install's secrets are
|
|
448
|
+
// encrypted with its own KMS key (or a local install's passphrase).
|
|
449
|
+
secretsProvider: inputs.backend.secretsProvider,
|
|
450
|
+
};
|
|
451
|
+
const stack = inputs.stackMustExist
|
|
452
|
+
? await pulumi.LocalWorkspace.selectStack({ stackName, workDir }, workspaceOpts).catch(
|
|
453
|
+
(e: unknown) => {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`the install is deployed but its stack is not in ${inputs.backend.url} (${String(e).split("\n")[0]}). Its state may have moved (state migrate) or been lost; nothing was changed.`,
|
|
456
|
+
);
|
|
457
|
+
},
|
|
458
|
+
)
|
|
459
|
+
: await pulumi.LocalWorkspace.createOrSelectStack({ stackName, workDir }, workspaceOpts);
|
|
460
|
+
|
|
461
|
+
const stateResources = ((await stack.exportStack()).deployment?.resources ??
|
|
462
|
+
[]) as StateResource[];
|
|
463
|
+
assertStackRegion(deployedStackRegion(stateResources), inputs.region);
|
|
464
|
+
|
|
465
|
+
const secretReplication = await applyStackConfig(stack, { ...inputs, provider }, stateResources);
|
|
466
|
+
|
|
467
|
+
// The stack references the CLI-generated runtime secrets (stack-contract.ts
|
|
468
|
+
// CLI_GENERATED_SECRETS) by id, so they must exist before `up`.
|
|
469
|
+
const generated = await ensureGeneratedSecrets({
|
|
470
|
+
projectId: inputs.projectId,
|
|
471
|
+
env: stackName,
|
|
472
|
+
region: inputs.region,
|
|
473
|
+
replication: stackSecretReplication(secretReplication, inputs.region),
|
|
474
|
+
});
|
|
475
|
+
const notEnsured = generated.filter((o) => o.status === "failed");
|
|
476
|
+
if (notEnsured.length > 0) {
|
|
477
|
+
throw new Error(
|
|
478
|
+
`could not create the runtime secrets: ${notEnsured.map((o) => `${o.id}: ${o.error ?? ""}`).join("; ")}`,
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
for (const o of generated) {
|
|
482
|
+
if (o.detail) onEvent("log", `created ${o.id} (${o.detail})\n`);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Refresh state first to detect drift from any out-of-band changes.
|
|
486
|
+
await stack.refresh({ onOutput: (msg) => onEvent("log", msg) });
|
|
487
|
+
|
|
488
|
+
// Run `up`. The onEvent callback receives the same JSON-RPC events
|
|
489
|
+
// Pulumi prints to stdout in --json mode.
|
|
490
|
+
const result = await stack.up({
|
|
491
|
+
onOutput: (msg) => onEvent("log", msg),
|
|
492
|
+
onEvent: (event) => {
|
|
493
|
+
// We only care about resource start/finish events for the
|
|
494
|
+
// progress UI — diagnostics surface via onOutput already.
|
|
495
|
+
if (event.resOpFailedEvent) {
|
|
496
|
+
onEvent(
|
|
497
|
+
"error",
|
|
498
|
+
`${event.resOpFailedEvent.metadata.urn}: ${event.resOpFailedEvent.status}`,
|
|
499
|
+
);
|
|
500
|
+
} else if (event.resOutputsEvent) {
|
|
501
|
+
onEvent("resource", event.resOutputsEvent.metadata.urn);
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const summary = result.summary;
|
|
507
|
+
const changes = summary.resourceChanges ?? {};
|
|
508
|
+
return {
|
|
509
|
+
outputs: checkpointableOutputs(result.outputs),
|
|
510
|
+
resourceCount: {
|
|
511
|
+
created: changes.create ?? 0,
|
|
512
|
+
updated: changes.update ?? 0,
|
|
513
|
+
deleted: changes.delete ?? 0,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|