@caelo-cms/provisioning 0.10.70 → 0.10.72
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-check.d.ts +36 -0
- package/dist/adc-check.d.ts.map +1 -0
- package/dist/adc-check.js +52 -0
- package/dist/adc-check.js.map +1 -0
- package/dist/adc-impersonation.d.ts +6 -2
- package/dist/adc-impersonation.d.ts.map +1 -1
- package/dist/adc-impersonation.js +8 -9
- package/dist/adc-impersonation.js.map +1 -1
- package/dist/agent-db.js +3 -3
- package/dist/remote-mcp.d.ts +27 -8
- package/dist/remote-mcp.d.ts.map +1 -1
- package/dist/remote-mcp.js +88 -56
- package/dist/remote-mcp.js.map +1 -1
- package/dist/stack-contract.d.ts +53 -39
- package/dist/stack-contract.d.ts.map +1 -1
- package/dist/stack-contract.js +110 -0
- package/dist/stack-contract.js.map +1 -1
- package/dist/state-command.d.ts.map +1 -1
- package/dist/state-command.js +33 -3
- package/dist/state-command.js.map +1 -1
- package/dist/state-migrate.d.ts +12 -0
- package/dist/state-migrate.d.ts.map +1 -1
- package/dist/state-migrate.js +4 -0
- package/dist/state-migrate.js.map +1 -1
- package/dist/state-pulumi.d.ts +7 -0
- package/dist/state-pulumi.d.ts.map +1 -1
- package/dist/state-pulumi.js +11 -0
- package/dist/state-pulumi.js.map +1 -1
- package/dist/upgrade/converge.d.ts.map +1 -1
- package/dist/upgrade/converge.js +4 -2
- package/dist/upgrade/converge.js.map +1 -1
- package/dist/wizards/gcp-pulumi.d.ts +9 -1
- package/dist/wizards/gcp-pulumi.d.ts.map +1 -1
- package/dist/wizards/gcp-pulumi.js +8 -1
- package/dist/wizards/gcp-pulumi.js.map +1 -1
- package/dist/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +105 -53
- package/dist/wizards/gcp.js.map +1 -1
- package/package.json +2 -2
- package/src/adc-check.test.ts +54 -0
- package/src/adc-check.ts +70 -0
- package/src/adc-impersonation.ts +14 -11
- package/src/provisioner-identity.test.ts +25 -0
- package/src/remote-mcp-stack-config.test.ts +195 -0
- package/src/remote-mcp.test.ts +99 -16
- package/src/remote-mcp.ts +104 -58
- package/src/stack-contract-parity.test.ts +28 -3
- package/src/stack-contract.ts +107 -6
- package/src/state-command.test.ts +12 -0
- package/src/state-command.ts +43 -3
- package/src/state-migrate.test.ts +53 -0
- package/src/state-migrate.ts +11 -0
- package/src/state-pulumi.ts +21 -0
- package/src/upgrade/converge.ts +4 -2
- package/src/wizards/gcp-pulumi.ts +21 -1
- package/src/wizards/gcp.ts +140 -64
- package/stacks/gcp/index.ts +13 -2
- package/stacks/gcp-firebase/index.ts +20 -6
|
@@ -30,7 +30,12 @@ import {
|
|
|
30
30
|
withoutAmbientGoogleCredentials,
|
|
31
31
|
} from "../provisioner-identity.js";
|
|
32
32
|
import { ensureGeneratedSecrets, stackSecretReplication } from "../runtime-secrets.js";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
ADMIN_MEMORY_DEFAULT,
|
|
35
|
+
REMOTE_MCP_IDENTITY_CONFIG,
|
|
36
|
+
type RemoteMcpIdentity,
|
|
37
|
+
serializeRemoteMcpIdentity,
|
|
38
|
+
} from "../stack-contract.js";
|
|
34
39
|
import type { ResolvedBackend } from "../state-backend.js";
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -96,6 +101,13 @@ export interface PulumiUpInputs {
|
|
|
96
101
|
* Defaults to 'gcp' for backwards compatibility.
|
|
97
102
|
*/
|
|
98
103
|
provider?: "gcp" | "gcp-firebase";
|
|
104
|
+
/**
|
|
105
|
+
* #1082 — the remote-MCP identity the stack declares as env
|
|
106
|
+
* (stack-contract.ts REMOTE_MCP_IDENTITY_CONFIG), resolved from the live
|
|
107
|
+
* install. Null when it could not be resolved (a fresh install before its
|
|
108
|
+
* first `up`): the config keeps what an earlier run wrote.
|
|
109
|
+
*/
|
|
110
|
+
remoteMcpIdentity?: RemoteMcpIdentity | null;
|
|
99
111
|
}
|
|
100
112
|
|
|
101
113
|
export interface PulumiUpResult {
|
|
@@ -456,6 +468,7 @@ export type StackConfigInputs = Pick<
|
|
|
456
468
|
| "wafAdaptiveProtection"
|
|
457
469
|
| "iapAllowlist"
|
|
458
470
|
| "imageDigests"
|
|
471
|
+
| "remoteMcpIdentity"
|
|
459
472
|
> & { provider: "gcp" | "gcp-firebase" };
|
|
460
473
|
|
|
461
474
|
/**
|
|
@@ -494,6 +507,13 @@ export async function applyStackConfig(
|
|
|
494
507
|
// v0.3.2 — anthropicApiKey config dropped. Runtime path is
|
|
495
508
|
// /settings/ai → ai_providers (KEK-encrypted).
|
|
496
509
|
...imageDigestConfig(provider, inputs.imageDigests),
|
|
510
|
+
...(inputs.remoteMcpIdentity
|
|
511
|
+
? {
|
|
512
|
+
[`${ns}:${REMOTE_MCP_IDENTITY_CONFIG}`]: {
|
|
513
|
+
value: serializeRemoteMcpIdentity(inputs.remoteMcpIdentity),
|
|
514
|
+
},
|
|
515
|
+
}
|
|
516
|
+
: {}),
|
|
497
517
|
});
|
|
498
518
|
|
|
499
519
|
// Read back only after the required keys are set: `pulumi config` refuses to
|
package/src/wizards/gcp.ts
CHANGED
|
@@ -51,6 +51,7 @@ import type { DnsRecord } from "../dns/types.js";
|
|
|
51
51
|
import {
|
|
52
52
|
activeAccount,
|
|
53
53
|
type BillingAccount,
|
|
54
|
+
checkGcloudAuth,
|
|
54
55
|
createProject,
|
|
55
56
|
createServiceAccount,
|
|
56
57
|
enableApis,
|
|
@@ -105,10 +106,20 @@ import {
|
|
|
105
106
|
} from "../provisioner-identity.js";
|
|
106
107
|
import { revokePublicRoleMemberships } from "../public-role-isolation.js";
|
|
107
108
|
import { decideRegion, detectCliRegion, installableRegions, suggestRegion } from "../regions.js";
|
|
108
|
-
import {
|
|
109
|
+
import {
|
|
110
|
+
checkRemoteMcpIdentityEnv,
|
|
111
|
+
ensureRemoteMcpEdge,
|
|
112
|
+
findRemoteMcpTarget,
|
|
113
|
+
type RemoteMcpTarget,
|
|
114
|
+
resolveStackRemoteMcpIdentity,
|
|
115
|
+
} from "../remote-mcp.js";
|
|
109
116
|
import type { RunMode } from "../run-mode.js";
|
|
110
117
|
import { selfCommand } from "../self-command.js";
|
|
111
|
-
import {
|
|
118
|
+
import {
|
|
119
|
+
ABSENT_DEFAULT_FIREWALL_RULES,
|
|
120
|
+
type RemoteMcpIdentity,
|
|
121
|
+
sameRemoteMcpIdentity,
|
|
122
|
+
} from "../stack-contract.js";
|
|
112
123
|
import {
|
|
113
124
|
chooseInstallBackend,
|
|
114
125
|
describeBackend,
|
|
@@ -120,7 +131,6 @@ import {
|
|
|
120
131
|
resolveBackend,
|
|
121
132
|
} from "../state-backend.js";
|
|
122
133
|
import { acquireStateLock, GcsStateStore } from "../state-store.js";
|
|
123
|
-
import { lookupFailure, resolveGcpResourceName } from "../upgrade/gcp-lookup.js";
|
|
124
134
|
import { createReleaseImageForms, platformDigestsOf } from "../upgrade/image-forms.js";
|
|
125
135
|
import {
|
|
126
136
|
releaseVersionForDigest,
|
|
@@ -379,12 +389,22 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
379
389
|
// post-cleanup re-runs both pass through cleanly).
|
|
380
390
|
await stepDrainLegacyCaeloAdminUser(projectId, region);
|
|
381
391
|
|
|
392
|
+
// === 9.8. The remote-MCP identity the stack declares (#1082) ===
|
|
393
|
+
// The stack owns the identity env vars (stack-contract.ts
|
|
394
|
+
// RemoteMcpIdentity) and declares them from config the CLI resolves from
|
|
395
|
+
// the live services, so this `up` keeps what they carry. A fresh install
|
|
396
|
+
// has no services yet: the identity is resolved after the first `up` and
|
|
397
|
+
// applied by a second one below.
|
|
398
|
+
const identityBefore = deployed
|
|
399
|
+
? await stepResolveRemoteMcpIdentity(projectId, region, provider, domain, "before")
|
|
400
|
+
: null;
|
|
401
|
+
|
|
382
402
|
// === 10. Pulumi up via Automation SDK ===
|
|
383
403
|
// Pre-built signed images live at the Caelo-team public AR repo
|
|
384
404
|
// (caelo-website/caelo-cms-images by default). Cloud Run reads them
|
|
385
405
|
// directly with no operator-side IAM binding — anonymous public pull.
|
|
386
406
|
phase("Provisioning the infrastructure (pulumi up, 8-15 min)");
|
|
387
|
-
|
|
407
|
+
const upOpts: PulumiUpOpts = {
|
|
388
408
|
projectId,
|
|
389
409
|
domain,
|
|
390
410
|
// #650 — the wizard recorded it (or the install predates the setting).
|
|
@@ -406,9 +426,24 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
406
426
|
// matching config namespace.
|
|
407
427
|
provider,
|
|
408
428
|
nonInteractive: opts.nonInteractive,
|
|
409
|
-
}
|
|
429
|
+
};
|
|
430
|
+
await stepPulumiUp(installId, { ...upOpts, remoteMcpIdentity: identityBefore?.identity ?? null });
|
|
410
431
|
recordImageDigests(installId, imageDigests);
|
|
411
432
|
|
|
433
|
+
// === 10.05. The identity once the services exist (#1082) ===
|
|
434
|
+
// Read again after every `up`: on a fresh install (or one whose identity
|
|
435
|
+
// could not be read before) this is the first time it can be, and an `up`
|
|
436
|
+
// that replaced a service changed it. A second `up` then applies it
|
|
437
|
+
// through the stack — never through gcloud, which the next `up` would
|
|
438
|
+
// undo.
|
|
439
|
+
const identity = await stepResolveRemoteMcpIdentity(projectId, region, provider, domain, "after");
|
|
440
|
+
if (identity && !sameRemoteMcpIdentity(identity.identity, identityBefore?.identity ?? null)) {
|
|
441
|
+
log.info(
|
|
442
|
+
"Applying the remote-MCP URLs and IAP audience through the stack (second pulumi up)...",
|
|
443
|
+
);
|
|
444
|
+
await stepPulumiUp(installId, { ...upOpts, remoteMcpIdentity: identity.identity });
|
|
445
|
+
}
|
|
446
|
+
|
|
412
447
|
// === 10.1. Remove the default network's pre-populated firewall rules ===
|
|
413
448
|
// GCP creates them when Compute Engine is enabled (step 5). Run after
|
|
414
449
|
// Pulumi rather than right after enabling the API: the default network
|
|
@@ -489,11 +524,9 @@ export async function runGcpConverge(opts: GcpConvergeOpts): Promise<void> {
|
|
|
489
524
|
keepSigner ? [`serviceAccount:${upgraderServiceAccountEmail(projectId, GCP_STACK_ENV)}`] : [],
|
|
490
525
|
);
|
|
491
526
|
|
|
492
|
-
// === 11.6. Remote MCP (#806): LB rules (gcp) + the identity env ===
|
|
493
|
-
//
|
|
494
|
-
|
|
495
|
-
// service's template dropped it (remote-mcp.ts).
|
|
496
|
-
const remoteMcpReady = await stepRemoteMcp(projectId, region, provider, domain);
|
|
527
|
+
// === 11.6. Remote MCP (#806): LB rules (gcp) + a check of the identity env ===
|
|
528
|
+
// The env itself came with `pulumi up` (step 10.05, #1082).
|
|
529
|
+
const remoteMcpReady = await stepRemoteMcp(projectId, provider, domain, identity);
|
|
497
530
|
|
|
498
531
|
// A fresh install ran every converge step above, so it is at this CLI's
|
|
499
532
|
// infrastructure contract — recorded only when the upgrader and the
|
|
@@ -607,19 +640,20 @@ async function resolveImageDigests(): Promise<ImageDigests> {
|
|
|
607
640
|
// Per-step helpers
|
|
608
641
|
// =========================================================================
|
|
609
642
|
|
|
643
|
+
/**
|
|
644
|
+
* An active gcloud account that can mint a token now. `activeAccount` alone
|
|
645
|
+
* passes with an expired login (`auth list` reads local config), and the
|
|
646
|
+
* first real gcloud call of the run then failed with a reauth error deep in
|
|
647
|
+
* a later step (#630, #1078).
|
|
648
|
+
*/
|
|
610
649
|
async function stepActiveGcloud(): Promise<void> {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
log.warn(
|
|
616
|
-
`Run ${bold("gcloud auth login")} in another terminal, then re-run ${bold(selfCommand())}.`,
|
|
617
|
-
);
|
|
618
|
-
cancel("Aborted — no gcloud auth.");
|
|
650
|
+
const auth = await checkGcloudAuth();
|
|
651
|
+
if (!auth.ok) {
|
|
652
|
+
log.error(red(auth.error));
|
|
653
|
+
cancel("Aborted — nothing was changed.");
|
|
619
654
|
process.exit(2);
|
|
620
655
|
}
|
|
621
|
-
log.success(`gcloud auth: ${bold(account)}`);
|
|
622
|
-
void stepName;
|
|
656
|
+
log.success(`gcloud auth: ${bold(auth.account)}`);
|
|
623
657
|
}
|
|
624
658
|
|
|
625
659
|
async function stepProjectId(opts: GcpWizardOpts): Promise<string> {
|
|
@@ -1166,74 +1200,104 @@ async function stepOperatorAccessSync(
|
|
|
1166
1200
|
s.stop(green(`Operator-access sync job ready (${r.done.length} steps)`));
|
|
1167
1201
|
}
|
|
1168
1202
|
|
|
1203
|
+
/** The install's identity for the stack, with the deployed services it was read from. */
|
|
1204
|
+
interface ResolvedRemoteMcpIdentity {
|
|
1205
|
+
readonly target: RemoteMcpTarget;
|
|
1206
|
+
readonly identity: RemoteMcpIdentity;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1169
1209
|
/**
|
|
1170
|
-
* #
|
|
1171
|
-
*
|
|
1172
|
-
*
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1175
|
-
* or `upgrade` retries. Idempotent.
|
|
1210
|
+
* #1082 — resolve the remote-MCP identity (remote-mcp.ts
|
|
1211
|
+
* resolveStackRemoteMcpIdentity) for the stack config: the deployed
|
|
1212
|
+
* services' names, then their URLs / the IAP backend and the organisation.
|
|
1213
|
+
* Read-only. Not fatal: remote MCP stays off until the Owner sets it up, so
|
|
1214
|
+
* a failure warns and the `up` keeps the identity the config last recorded.
|
|
1176
1215
|
*
|
|
1177
|
-
* @
|
|
1216
|
+
* @param when `before` the `up` (the services exist), or `after` it
|
|
1217
|
+
* @returns the identity, or null when it could not be read
|
|
1178
1218
|
*/
|
|
1179
|
-
async function
|
|
1219
|
+
async function stepResolveRemoteMcpIdentity(
|
|
1180
1220
|
projectId: string,
|
|
1181
1221
|
region: string,
|
|
1182
1222
|
provider: "gcp" | "gcp-firebase",
|
|
1183
1223
|
domain: string,
|
|
1184
|
-
|
|
1224
|
+
when: "before" | "after",
|
|
1225
|
+
): Promise<ResolvedRemoteMcpIdentity | null> {
|
|
1185
1226
|
const s = spinner();
|
|
1186
1227
|
s.start(
|
|
1187
|
-
"
|
|
1228
|
+
"Reading the remote-MCP URLs and IAP audience (Google sign-in, ChatGPT / claude.ai connectors)...",
|
|
1188
1229
|
);
|
|
1189
|
-
const name = (slug: "admin" | "gateway") =>
|
|
1190
|
-
resolveGcpResourceName(
|
|
1191
|
-
gcloud,
|
|
1192
|
-
"run-service",
|
|
1193
|
-
`caelo-${GCP_STACK_ENV}-${slug}`,
|
|
1194
|
-
projectId,
|
|
1195
|
-
region,
|
|
1196
|
-
);
|
|
1197
|
-
const [adminLookup, gatewayLookup] = [await name("admin"), await name("gateway")];
|
|
1198
1230
|
const failed = (error: string) => {
|
|
1199
|
-
s.stop(yellow(`Remote
|
|
1231
|
+
s.stop(yellow(`Remote-MCP URLs not read: ${error}`));
|
|
1200
1232
|
log.warn(
|
|
1201
|
-
|
|
1233
|
+
when === "before"
|
|
1234
|
+
? "pulumi up keeps the remote-MCP env the stack recorded last; the installer reads it again after the update."
|
|
1235
|
+
: "The install works; Settings → Google sign-in and Settings → MCP report this install as not prepared until a re-run of the installer reads them.",
|
|
1202
1236
|
);
|
|
1203
|
-
return
|
|
1237
|
+
return null;
|
|
1204
1238
|
};
|
|
1205
|
-
|
|
1206
|
-
return failed(
|
|
1207
|
-
lookupFailure(adminLookup, `caelo-${GCP_STACK_ENV}-admin* Cloud Run service`, "operator"),
|
|
1208
|
-
);
|
|
1209
|
-
}
|
|
1210
|
-
if (gatewayLookup.status !== "found") {
|
|
1211
|
-
return failed(
|
|
1212
|
-
lookupFailure(gatewayLookup, `caelo-${GCP_STACK_ENV}-gateway* Cloud Run service`, "operator"),
|
|
1213
|
-
);
|
|
1214
|
-
}
|
|
1215
|
-
const [admin, gateway] = [adminLookup.name, gatewayLookup.name];
|
|
1216
|
-
const edge = await ensureRemoteMcpEdge({ provider, projectId, env: GCP_STACK_ENV, domain });
|
|
1217
|
-
if (!edge.ok) return failed(edge.error);
|
|
1218
|
-
const env = await ensureRemoteMcpIdentityEnv({
|
|
1239
|
+
const found = await findRemoteMcpTarget({
|
|
1219
1240
|
provider,
|
|
1220
1241
|
projectId,
|
|
1221
1242
|
region,
|
|
1222
1243
|
env: GCP_STACK_ENV,
|
|
1223
1244
|
domain,
|
|
1224
|
-
services: { admin, gateway },
|
|
1225
1245
|
});
|
|
1226
|
-
if (!
|
|
1227
|
-
|
|
1228
|
-
|
|
1246
|
+
if (!found.ok) return failed(found.error);
|
|
1247
|
+
const { target } = found;
|
|
1248
|
+
const resolved = await resolveStackRemoteMcpIdentity(target);
|
|
1249
|
+
if (!resolved.ok) return failed(resolved.error);
|
|
1250
|
+
s.stop(green(`Remote-MCP URLs read (${resolved.identity.mcpPublicUrl})`));
|
|
1251
|
+
if (resolved.orgDomainError && when === "after") {
|
|
1229
1252
|
// #914 — without the organisation's domain the admin cannot tell who
|
|
1230
1253
|
// IAP's built-in sign-in admits, so it refuses new users until the
|
|
1231
1254
|
// Owner sets up Google sign-in. Say so now rather than at the first
|
|
1232
1255
|
// invite.
|
|
1233
1256
|
log.warn(
|
|
1234
|
-
`Could not read this project's Google organisation (${
|
|
1257
|
+
`Could not read this project's Google organisation (${resolved.orgDomainError}). Set up Google sign-in under Settings → Google sign-in before adding users, or re-run ${bold(selfCommand("upgrade"))} as an account with the Organization Viewer role.`,
|
|
1258
|
+
);
|
|
1259
|
+
}
|
|
1260
|
+
return { target, identity: resolved.identity };
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
/**
|
|
1264
|
+
* #806 — the CLI's part of remote MCP (remote-mcp.ts): on `gcp` the load
|
|
1265
|
+
* balancer's discovery routes and Cloud Armor rule. The identity env vars
|
|
1266
|
+
* (admin URL, the broker's public URL, the IAP audience) come with `pulumi
|
|
1267
|
+
* up` from the stack config (#1082); this only checks that the services
|
|
1268
|
+
* carry them. Remote MCP stays off until the Owner sets it up under
|
|
1269
|
+
* Settings → MCP, so a failure does not stop the install: it warns, and the
|
|
1270
|
+
* next pass or `upgrade` retries. Idempotent.
|
|
1271
|
+
*
|
|
1272
|
+
* @returns whether every part is in place
|
|
1273
|
+
*/
|
|
1274
|
+
async function stepRemoteMcp(
|
|
1275
|
+
projectId: string,
|
|
1276
|
+
provider: "gcp" | "gcp-firebase",
|
|
1277
|
+
domain: string,
|
|
1278
|
+
identity: ResolvedRemoteMcpIdentity | null,
|
|
1279
|
+
): Promise<boolean> {
|
|
1280
|
+
const s = spinner();
|
|
1281
|
+
s.start(
|
|
1282
|
+
"Preparing remote MCP (ChatGPT / claude.ai connectors; off until the Owner enables it)...",
|
|
1283
|
+
);
|
|
1284
|
+
const failed = (error: string) => {
|
|
1285
|
+
s.stop(yellow(`Remote MCP not prepared: ${error}`));
|
|
1286
|
+
log.warn(
|
|
1287
|
+
`The install works; Settings → Google sign-in and Settings → MCP report this install as not prepared until ${bold(selfCommand("upgrade"))} prepares it.`,
|
|
1235
1288
|
);
|
|
1289
|
+
return false;
|
|
1290
|
+
};
|
|
1291
|
+
if (!identity) return failed("its URLs and IAP audience could not be read (see above)");
|
|
1292
|
+
const edge = await ensureRemoteMcpEdge({ provider, projectId, env: GCP_STACK_ENV, domain });
|
|
1293
|
+
if (!edge.ok) return failed(edge.error);
|
|
1294
|
+
const check = await checkRemoteMcpIdentityEnv(identity.target, identity.identity);
|
|
1295
|
+
if (!check.ok) return failed(check.error);
|
|
1296
|
+
const missing = [...check.drift.admin, ...check.drift.gateway].map((c) => c.name);
|
|
1297
|
+
if (missing.length > 0) {
|
|
1298
|
+
return failed(`pulumi up left ${[...new Set(missing)].join(", ")} out of the services' env`);
|
|
1236
1299
|
}
|
|
1300
|
+
s.stop(green(`Remote MCP and Google sign-in prepared (${identity.identity.mcpPublicUrl})`));
|
|
1237
1301
|
return true;
|
|
1238
1302
|
}
|
|
1239
1303
|
|
|
@@ -1323,8 +1387,18 @@ async function stepKeylessProvisioner(
|
|
|
1323
1387
|
const sAdc = spinner();
|
|
1324
1388
|
sAdc.start("Checking that your Application Default Credentials can impersonate it...");
|
|
1325
1389
|
let adc = await check();
|
|
1326
|
-
if (
|
|
1327
|
-
|
|
1390
|
+
if (
|
|
1391
|
+
!adc.ok &&
|
|
1392
|
+
(adc.reason === "no-adc" || adc.reason === "expired-adc") &&
|
|
1393
|
+
!opts.nonInteractive
|
|
1394
|
+
) {
|
|
1395
|
+
sAdc.stop(
|
|
1396
|
+
yellow(
|
|
1397
|
+
adc.reason === "expired-adc"
|
|
1398
|
+
? "Your Application Default Credentials have expired"
|
|
1399
|
+
: "No Application Default Credentials yet",
|
|
1400
|
+
),
|
|
1401
|
+
);
|
|
1328
1402
|
const login = await confirm({
|
|
1329
1403
|
message: `Run ${bold("gcloud auth application-default login")} now (opens your browser; sign in as ${account})?`,
|
|
1330
1404
|
initialValue: true,
|
|
@@ -1341,7 +1415,7 @@ async function stepKeylessProvisioner(
|
|
|
1341
1415
|
}
|
|
1342
1416
|
if (!adc.ok) {
|
|
1343
1417
|
sAdc.stop(red("Infrastructure credentials not ready"));
|
|
1344
|
-
log.error(adc.error);
|
|
1418
|
+
log.error(`${adc.error}\nPulumi was not started.`);
|
|
1345
1419
|
cancel("Aborted.");
|
|
1346
1420
|
process.exit(1);
|
|
1347
1421
|
}
|
|
@@ -1481,6 +1555,8 @@ interface PulumiUpOpts {
|
|
|
1481
1555
|
provider?: "gcp" | "gcp-firebase";
|
|
1482
1556
|
/** No terminal to ask (the install job, `--non-interactive`): see planGate. */
|
|
1483
1557
|
nonInteractive: boolean;
|
|
1558
|
+
/** #1082 — the remote-MCP identity the stack declares (gcp-pulumi.ts PulumiUpInputs). */
|
|
1559
|
+
remoteMcpIdentity?: RemoteMcpIdentity | null;
|
|
1484
1560
|
}
|
|
1485
1561
|
|
|
1486
1562
|
/**
|
package/stacks/gcp/index.ts
CHANGED
|
@@ -37,13 +37,16 @@ import {
|
|
|
37
37
|
adminEnvContract,
|
|
38
38
|
adminMediaVolume,
|
|
39
39
|
adminMediaVolumeTemplate,
|
|
40
|
+
CLOUD_RUN_IGNORE_CHANGES,
|
|
40
41
|
type CloudRunEnvVar,
|
|
41
42
|
type CloudRunSlug,
|
|
42
43
|
databaseUrls,
|
|
43
44
|
defaultStagingHost,
|
|
44
45
|
GATEWAY_TIMEOUT_SECONDS,
|
|
45
46
|
gatewayEnvContract,
|
|
47
|
+
REMOTE_MCP_IDENTITY_CONFIG,
|
|
46
48
|
type RuntimeSecret,
|
|
49
|
+
remoteMcpIdentityStackEnv,
|
|
47
50
|
runtimeSecretBindings,
|
|
48
51
|
STATIC_CDN_POLICY,
|
|
49
52
|
secretFileVolumesTemplate,
|
|
@@ -685,6 +688,12 @@ const envContractInputs = {
|
|
|
685
688
|
},
|
|
686
689
|
} as const;
|
|
687
690
|
|
|
691
|
+
// #1082 — the remote-MCP identity env (stack-contract.ts RemoteMcpIdentity):
|
|
692
|
+
// names generated identities no resource here can take as input, so the CLI
|
|
693
|
+
// resolves them from the live install and writes them as config before `up`.
|
|
694
|
+
// Declared here so the stack is their one owner and `up` keeps them.
|
|
695
|
+
const remoteMcpEnv = remoteMcpIdentityStackEnv(cfg.get(REMOTE_MCP_IDENTITY_CONFIG));
|
|
696
|
+
|
|
688
697
|
function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
|
|
689
698
|
return new gcp.cloudrunv2.Service(
|
|
690
699
|
`${namePrefix}-${args.serviceName}`,
|
|
@@ -740,6 +749,8 @@ function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
|
|
|
740
749
|
},
|
|
741
750
|
{
|
|
742
751
|
...opts,
|
|
752
|
+
// #1082 — which tool deployed last; see CLOUD_RUN_IGNORE_CHANGES.
|
|
753
|
+
ignoreChanges: [...CLOUD_RUN_IGNORE_CHANGES],
|
|
743
754
|
dependsOn: [
|
|
744
755
|
pgAdminUser,
|
|
745
756
|
cmsAdminDb,
|
|
@@ -771,7 +782,7 @@ const adminSvc = cloudRunService({
|
|
|
771
782
|
// in Cloud Run logs. 3600s is the platform max; no cost penalty for
|
|
772
783
|
// unused timeout (Cloud Run bills running time, not allocated time).
|
|
773
784
|
timeout: "3600s",
|
|
774
|
-
contractEnv: adminEnvContract(envContractInputs),
|
|
785
|
+
contractEnv: [...adminEnvContract(envContractInputs), ...remoteMcpEnv.admin],
|
|
775
786
|
mediaVolume: adminMediaVolumeTemplate(adminMediaVolume(project, env)),
|
|
776
787
|
});
|
|
777
788
|
const gatewaySvc = cloudRunService({
|
|
@@ -786,7 +797,7 @@ const gatewaySvc = cloudRunService({
|
|
|
786
797
|
timeout: `${GATEWAY_TIMEOUT_SECONDS}s`,
|
|
787
798
|
// public_role on cms_public only: no cms_admin login of any role
|
|
788
799
|
// (#613, #635, CLAUDE.md §2).
|
|
789
|
-
contractEnv: gatewayEnvContract(envContractInputs),
|
|
800
|
+
contractEnv: [...gatewayEnvContract(envContractInputs), ...remoteMcpEnv.gateway],
|
|
790
801
|
});
|
|
791
802
|
|
|
792
803
|
// =========================================================================
|
|
@@ -42,13 +42,16 @@ import {
|
|
|
42
42
|
adminEnvContract,
|
|
43
43
|
adminMediaVolume,
|
|
44
44
|
adminMediaVolumeTemplate,
|
|
45
|
+
CLOUD_RUN_IGNORE_CHANGES,
|
|
45
46
|
type CloudRunEnvVar,
|
|
46
47
|
type CloudRunSlug,
|
|
47
48
|
databaseUrls,
|
|
48
49
|
defaultStagingHost,
|
|
49
50
|
GATEWAY_TIMEOUT_SECONDS,
|
|
50
51
|
gatewayEnvContract,
|
|
52
|
+
REMOTE_MCP_IDENTITY_CONFIG,
|
|
51
53
|
type RuntimeSecret,
|
|
54
|
+
remoteMcpIdentityStackEnv,
|
|
52
55
|
runtimeSecretBindings,
|
|
53
56
|
secretFileVolumesTemplate,
|
|
54
57
|
} from "../../dist/stack-contract.js";
|
|
@@ -552,6 +555,12 @@ function imageRef(service: string): string {
|
|
|
552
555
|
return digest ? `${base}@${digest}` : `${base}:${cfg.get("image-tag") ?? "latest"}`;
|
|
553
556
|
}
|
|
554
557
|
|
|
558
|
+
// #1082 — the remote-MCP identity env (stack-contract.ts RemoteMcpIdentity):
|
|
559
|
+
// names generated identities no resource here can take as input, so the CLI
|
|
560
|
+
// resolves them from the live install and writes them as config before `up`.
|
|
561
|
+
// Declared here so the stack is their one owner and `up` keeps them.
|
|
562
|
+
const remoteMcpEnv = remoteMcpIdentityStackEnv(cfg.get(REMOTE_MCP_IDENTITY_CONFIG));
|
|
563
|
+
|
|
555
564
|
function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
|
|
556
565
|
return new gcp.cloudrunv2.Service(
|
|
557
566
|
`${namePrefix}-${args.serviceName}`,
|
|
@@ -600,6 +609,8 @@ function cloudRunService(args: CloudRunArgs): gcp.cloudrunv2.Service {
|
|
|
600
609
|
},
|
|
601
610
|
{
|
|
602
611
|
...opts,
|
|
612
|
+
// #1082 — which tool deployed last; see CLOUD_RUN_IGNORE_CHANGES.
|
|
613
|
+
ignoreChanges: [...CLOUD_RUN_IGNORE_CHANGES],
|
|
603
614
|
dependsOn: [
|
|
604
615
|
pgAdminUser,
|
|
605
616
|
cmsAdminDb,
|
|
@@ -654,7 +665,7 @@ const gatewaySvc = cloudRunService({
|
|
|
654
665
|
// request passes its rate limits, captcha and honeypot; staging requests
|
|
655
666
|
// pass the staging access gate.
|
|
656
667
|
ingress: "INGRESS_TRAFFIC_ALL",
|
|
657
|
-
contractEnv: gatewayEnvContract(envContractInputs),
|
|
668
|
+
contractEnv: [...gatewayEnvContract(envContractInputs), ...remoteMcpEnv.gateway],
|
|
658
669
|
});
|
|
659
670
|
|
|
660
671
|
const adminSvc = cloudRunService({
|
|
@@ -670,11 +681,14 @@ const adminSvc = cloudRunService({
|
|
|
670
681
|
// Use the input string (known at config time) for the site — the
|
|
671
682
|
// resource output is Output<string | undefined>, which Input<string>
|
|
672
683
|
// rejects.
|
|
673
|
-
contractEnv:
|
|
674
|
-
...
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
684
|
+
contractEnv: [
|
|
685
|
+
...adminEnvContract({
|
|
686
|
+
...envContractInputs,
|
|
687
|
+
firebaseSiteId,
|
|
688
|
+
gatewayService: gatewaySvc.name,
|
|
689
|
+
}),
|
|
690
|
+
...remoteMcpEnv.admin,
|
|
691
|
+
],
|
|
678
692
|
mediaVolume: adminMediaVolumeTemplate(adminMediaVolume(project, env)),
|
|
679
693
|
});
|
|
680
694
|
|