@caelo-cms/provisioning 0.10.71 → 0.11.0
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 +366 -83
- package/dist/state-command.d.ts.map +1 -1
- package/dist/state-command.js +9 -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/wizards/gcp.d.ts.map +1 -1
- package/dist/wizards/gcp.js +19 -12
- 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/state-command.test.ts +12 -0
- package/src/state-command.ts +11 -3
- package/src/state-migrate.test.ts +53 -0
- package/src/state-migrate.ts +11 -0
- package/src/wizards/gcp.ts +25 -13
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "bun:test";
|
|
4
|
+
import { adcProbeFromResult } from "./adc-check.js";
|
|
5
|
+
import type { GcloudResult } from "./gcloud.js";
|
|
6
|
+
|
|
7
|
+
const failed = (stderr: string): GcloudResult => ({ ok: false, stdout: "", stderr, exitCode: 1 });
|
|
8
|
+
|
|
9
|
+
describe("adcProbeFromResult (#1078)", () => {
|
|
10
|
+
it("a printed token is working ADC", () => {
|
|
11
|
+
expect(adcProbeFromResult({ ok: true, stdout: "ya29.x\n", stderr: "", exitCode: 0 })).toEqual({
|
|
12
|
+
ok: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("no ADC file reads as missing and names the login", () => {
|
|
17
|
+
const r = adcProbeFromResult(
|
|
18
|
+
failed(
|
|
19
|
+
"ERROR: (gcloud.auth.application-default.print-access-token) Your default credentials were not found. To set up Application Default Credentials, see https://cloud.google.com/docs/authentication/external/set-up-adc for more information.",
|
|
20
|
+
),
|
|
21
|
+
);
|
|
22
|
+
expect(r.ok).toBe(false);
|
|
23
|
+
if (r.ok) return;
|
|
24
|
+
expect(r.reason).toBe("missing");
|
|
25
|
+
expect(r.error).toContain("No Application Default Credentials");
|
|
26
|
+
expect(r.error).toContain("`gcloud auth application-default login`");
|
|
27
|
+
expect(r.error).not.toContain("\n");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
for (const stderr of [
|
|
31
|
+
"ERROR: (gcloud.auth.application-default.print-access-token) There was a problem refreshing your current auth tokens: ('invalid_grant: reauth related error (invalid_rapt)', {'error': 'invalid_grant', 'error_subtype': 'invalid_rapt'})",
|
|
32
|
+
"ERROR: (gcloud.auth.application-default.print-access-token) Reauthentication failed. cannot prompt during non-interactive execution.",
|
|
33
|
+
"ERROR: (gcloud.auth.application-default.print-access-token) ('invalid_grant: Bad Request', {'error': 'invalid_grant', 'error_description': 'Bad Request'})",
|
|
34
|
+
]) {
|
|
35
|
+
it(`reads as expired: ${stderr.slice(62, 110)}`, () => {
|
|
36
|
+
const r = adcProbeFromResult(failed(stderr));
|
|
37
|
+
expect(r.ok).toBe(false);
|
|
38
|
+
if (r.ok) return;
|
|
39
|
+
expect(r.reason).toBe("expired");
|
|
40
|
+
expect(r.error).toContain("have expired");
|
|
41
|
+
expect(r.error).toContain("`gcloud auth application-default login`");
|
|
42
|
+
expect(r.error).not.toContain("\n");
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
it("any other failure still names the login, with gcloud's line", () => {
|
|
47
|
+
const r = adcProbeFromResult(failed("ERROR: network unreachable"));
|
|
48
|
+
expect(r.ok).toBe(false);
|
|
49
|
+
if (r.ok) return;
|
|
50
|
+
expect(r.reason).toBe("error");
|
|
51
|
+
expect(r.error).toContain("network unreachable");
|
|
52
|
+
expect(r.error).toContain("gcloud auth application-default login");
|
|
53
|
+
});
|
|
54
|
+
});
|
package/src/adc-check.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Probe this machine's Application Default Credentials (#1078) — the
|
|
5
|
+
* credentials every local Pulumi run uses (the installer in local mode,
|
|
6
|
+
* `state migrate`). Without the probe, Pulumi starts with missing or
|
|
7
|
+
* expired ADC and prints one provider error per resource (`invalid_grant`
|
|
8
|
+
* … `invalid_rapt`) before failing, and the one line the Owner needs —
|
|
9
|
+
* `gcloud auth application-default login` — never appears.
|
|
10
|
+
*
|
|
11
|
+
* The probe runs `gcloud auth application-default print-access-token`,
|
|
12
|
+
* which refreshes the token exactly as Pulumi's Google provider would, so
|
|
13
|
+
* expired or reauth-blocked credentials fail here. The token is discarded.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { type GcloudResult, gcloudErrorLine, isGcloudAuthError } from "./gcloud.js";
|
|
17
|
+
|
|
18
|
+
/** The command that fixes both a missing and an expired login. */
|
|
19
|
+
export const ADC_LOGIN_COMMAND = "gcloud auth application-default login";
|
|
20
|
+
|
|
21
|
+
export type AdcProbe =
|
|
22
|
+
| { readonly ok: true }
|
|
23
|
+
| {
|
|
24
|
+
readonly ok: false;
|
|
25
|
+
/**
|
|
26
|
+
* `missing`: no ADC on this machine; `expired`: they exist but can no
|
|
27
|
+
* longer mint a token (expired/revoked refresh token, reauth policy);
|
|
28
|
+
* `error`: gcloud failed for another reason (its line is in `error`).
|
|
29
|
+
*/
|
|
30
|
+
readonly reason: "missing" | "expired" | "error";
|
|
31
|
+
/** One line naming {@link ADC_LOGIN_COMMAND}. */
|
|
32
|
+
readonly error: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** gcloud's wording when no ADC file exists (and google-auth's, for older SDKs). */
|
|
36
|
+
const ADC_MISSING = /credentials were not found|could not automatically determine credentials/i;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Classify the result of `gcloud auth application-default print-access-token`.
|
|
40
|
+
* Pure, so the async installer path and the sync attach path word it alike.
|
|
41
|
+
*/
|
|
42
|
+
export function adcProbeFromResult(r: GcloudResult): AdcProbe {
|
|
43
|
+
if (r.ok && r.stdout.trim().length > 0) return { ok: true };
|
|
44
|
+
if (ADC_MISSING.test(r.stderr) || (r.ok && r.stdout.trim().length === 0)) {
|
|
45
|
+
return {
|
|
46
|
+
ok: false,
|
|
47
|
+
reason: "missing",
|
|
48
|
+
error: `No Application Default Credentials on this machine; Pulumi needs them. Run \`${ADC_LOGIN_COMMAND}\` and re-run this command.`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (isGcloudAuthError(r.stderr)) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
reason: "expired",
|
|
55
|
+
error: `The Application Default Credentials on this machine have expired (gcloud: ${gcloudErrorLine(r.stderr)}). Run \`${ADC_LOGIN_COMMAND}\` and re-run this command.`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
reason: "error",
|
|
61
|
+
error: `Could not check the Application Default Credentials on this machine (gcloud: ${gcloudErrorLine(r.stderr)}). Run \`${ADC_LOGIN_COMMAND}\` if they are missing or expired, then re-run this command.`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The `print-access-token` arguments the probe runs. */
|
|
66
|
+
export const ADC_PROBE_ARGS: readonly string[] = [
|
|
67
|
+
"auth",
|
|
68
|
+
"application-default",
|
|
69
|
+
"print-access-token",
|
|
70
|
+
];
|
package/src/adc-impersonation.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* stay in local variables: never printed, logged, returned or written.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { ADC_PROBE_ARGS, adcProbeFromResult } from "./adc-check.js";
|
|
15
16
|
import { gcloud as defaultGcloud } from "./gcloud.js";
|
|
16
17
|
import { type GcloudRunner, realSleep, type Sleep } from "./gcloud-retry.js";
|
|
17
18
|
import {
|
|
@@ -30,8 +31,12 @@ export type AdcCheck =
|
|
|
30
31
|
| { readonly ok: true }
|
|
31
32
|
| {
|
|
32
33
|
readonly ok: false;
|
|
33
|
-
/**
|
|
34
|
-
|
|
34
|
+
/**
|
|
35
|
+
* `no-adc`: no Application Default Credentials; `expired-adc`: they
|
|
36
|
+
* exist but can no longer mint a token (#1078); `denied`: they can't
|
|
37
|
+
* impersonate.
|
|
38
|
+
*/
|
|
39
|
+
readonly reason: "no-adc" | "expired-adc" | "denied" | "error";
|
|
35
40
|
readonly error: string;
|
|
36
41
|
};
|
|
37
42
|
|
|
@@ -53,19 +58,17 @@ export async function checkAdcImpersonation(
|
|
|
53
58
|
const sa = provisionerServiceAccountEmail(target.projectId);
|
|
54
59
|
const env = withoutAmbientGoogleCredentials(deps.env ?? process.env);
|
|
55
60
|
|
|
56
|
-
const adc = await run([
|
|
57
|
-
const
|
|
58
|
-
if (!
|
|
61
|
+
const adc = await run([...ADC_PROBE_ARGS], { env });
|
|
62
|
+
const probe = adcProbeFromResult(adc);
|
|
63
|
+
if (!probe.ok) {
|
|
59
64
|
return {
|
|
60
65
|
ok: false,
|
|
61
|
-
reason: "no-adc",
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
`Infrastructure runs authenticate with your own Google login and impersonate ${sa}; no key file is used.`,
|
|
65
|
-
`Run \`gcloud auth application-default login\` (sign in as ${target.operatorAccount}), then re-run.`,
|
|
66
|
-
].join("\n"),
|
|
66
|
+
reason: probe.reason === "expired" ? "expired-adc" : "no-adc",
|
|
67
|
+
// One line (#1078): the login command must not drown in context.
|
|
68
|
+
error: `${probe.error} Sign in as ${target.operatorAccount}: Pulumi uses these credentials to impersonate ${sa}, no key file.`,
|
|
67
69
|
};
|
|
68
70
|
}
|
|
71
|
+
const sourceToken = adc.stdout.trim();
|
|
69
72
|
|
|
70
73
|
// The account to impersonate is the one IAM reports, not a string
|
|
71
74
|
// assembled from install.json: what goes into the request is an email
|
|
@@ -369,6 +369,31 @@ describe("checkAdcImpersonation", () => {
|
|
|
369
369
|
expect(env.GOOGLE_OAUTH_ACCESS_TOKEN).toBeUndefined();
|
|
370
370
|
});
|
|
371
371
|
|
|
372
|
+
it("expired ADC stop before IAM or the Credentials API is asked (#1078)", async () => {
|
|
373
|
+
const g = fakeGcloud({
|
|
374
|
+
"auth application-default print-access-token": [
|
|
375
|
+
fail(
|
|
376
|
+
"ERROR: (gcloud.auth.application-default.print-access-token) There was a problem refreshing your current auth tokens: ('invalid_grant: reauth related error (invalid_rapt)', {'error': 'invalid_grant'})",
|
|
377
|
+
),
|
|
378
|
+
],
|
|
379
|
+
});
|
|
380
|
+
const f = fakeFetch([]);
|
|
381
|
+
const r = await checkAdcImpersonation(
|
|
382
|
+
{ projectId: PROJECT, operatorAccount: OPERATOR, freshGrant: false },
|
|
383
|
+
{ run: g.run, sleep: noSleep, fetch: f.impl },
|
|
384
|
+
);
|
|
385
|
+
expect(r.ok).toBe(false);
|
|
386
|
+
if (r.ok) return;
|
|
387
|
+
expect(r.reason).toBe("expired-adc");
|
|
388
|
+
expect(r.error).toContain("have expired");
|
|
389
|
+
expect(r.error).toContain("`gcloud auth application-default login`");
|
|
390
|
+
expect(r.error).not.toContain("\n");
|
|
391
|
+
expect(g.calls.map((c) => c.args.join(" "))).toEqual([
|
|
392
|
+
"auth application-default print-access-token",
|
|
393
|
+
]);
|
|
394
|
+
expect(f.urls).toEqual([]);
|
|
395
|
+
});
|
|
396
|
+
|
|
372
397
|
it("succeeds when ADC may mint a token for the provisioner, and keeps no token", async () => {
|
|
373
398
|
const g = fakeGcloud({
|
|
374
399
|
"auth application-default print-access-token": [ok(`${SOURCE}\n`)],
|
|
@@ -28,4 +28,16 @@ describe("adcNote (#816)", () => {
|
|
|
28
28
|
expect(note).toContain("gcloud auth application-default login");
|
|
29
29
|
expect(note).toContain("upgrade, status and backup don't need them");
|
|
30
30
|
});
|
|
31
|
+
|
|
32
|
+
it("expired ADC say so instead of 'no ADC' (#1078)", () => {
|
|
33
|
+
const note = adcNote(() => ({
|
|
34
|
+
ok: false,
|
|
35
|
+
stdout: "",
|
|
36
|
+
stderr:
|
|
37
|
+
"ERROR: (gcloud.auth.application-default.print-access-token) Reauthentication failed. cannot prompt during non-interactive execution.",
|
|
38
|
+
exitCode: 1,
|
|
39
|
+
}));
|
|
40
|
+
expect(note).toContain("have expired");
|
|
41
|
+
expect(note).toContain("gcloud auth application-default login");
|
|
42
|
+
});
|
|
31
43
|
});
|
package/src/state-command.ts
CHANGED
|
@@ -14,6 +14,8 @@ import { homedir } from "node:os";
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { confirm, isCancel, log, note } from "@clack/prompts";
|
|
16
16
|
import { bold, red } from "kleur/colors";
|
|
17
|
+
import { ADC_LOGIN_COMMAND, ADC_PROBE_ARGS, adcProbeFromResult } from "./adc-check.js";
|
|
18
|
+
import { checkAdcImpersonation } from "./adc-impersonation.js";
|
|
17
19
|
import { adoptionCandidates, existingCandidates } from "./adopt-existing.js";
|
|
18
20
|
import { checkGcloudAuth, gcloud } from "./gcloud.js";
|
|
19
21
|
import { GCP_STACK_ENV } from "./gcp-names.js";
|
|
@@ -209,6 +211,8 @@ export async function stateCommand(argv: readonly string[]): Promise<void> {
|
|
|
209
211
|
}),
|
|
210
212
|
}),
|
|
211
213
|
report: clackReporter(),
|
|
214
|
+
checkAdc: () =>
|
|
215
|
+
checkAdcImpersonation({ projectId, operatorAccount: operator, freshGrant: false }),
|
|
212
216
|
resolve: (record) =>
|
|
213
217
|
resolveBackend(
|
|
214
218
|
{ installId, stateBackend: record },
|
|
@@ -334,9 +338,13 @@ export function attachProject(projectId: string): string {
|
|
|
334
338
|
* do not, so a note on every attach sent Owners on a detour (#816).
|
|
335
339
|
*/
|
|
336
340
|
export function adcNote(run: SyncGcloud): string | null {
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
339
|
-
|
|
341
|
+
const probe = adcProbeFromResult(run([...ADC_PROBE_ARGS]));
|
|
342
|
+
if (probe.ok) return null;
|
|
343
|
+
const state =
|
|
344
|
+
probe.reason === "expired"
|
|
345
|
+
? "The Application Default Credentials on this machine have expired."
|
|
346
|
+
: "No Application Default Credentials on this machine.";
|
|
347
|
+
return `${state} upgrade, status and backup don't need them; re-running the installer or \`state migrate\` does: ${ADC_LOGIN_COMMAND}`;
|
|
340
348
|
}
|
|
341
349
|
|
|
342
350
|
/**
|
|
@@ -156,6 +156,9 @@ function machine(initial: StateBackendRecord = { kind: "local" }) {
|
|
|
156
156
|
planFiles: [] as { label: string; steps: number }[],
|
|
157
157
|
/** What existingInProject answers (#1079); an Error is thrown. */
|
|
158
158
|
existing: [] as readonly string[] | Error,
|
|
159
|
+
/** What checkAdc answers (#1078). */
|
|
160
|
+
adc: { ok: true } as { ok: true } | { ok: false; error: string },
|
|
161
|
+
adcChecks: 0,
|
|
159
162
|
};
|
|
160
163
|
const meta = (): MigrateInstall => ({
|
|
161
164
|
installId: "gcp-firebase-acme",
|
|
@@ -169,6 +172,10 @@ function machine(initial: StateBackendRecord = { kind: "local" }) {
|
|
|
169
172
|
const deps: MigrateDeps = {
|
|
170
173
|
pulumi: p.ops,
|
|
171
174
|
report,
|
|
175
|
+
checkAdc: async () => {
|
|
176
|
+
m.adcChecks++;
|
|
177
|
+
return m.adc;
|
|
178
|
+
},
|
|
172
179
|
resolve: (record) =>
|
|
173
180
|
resolveBackend(
|
|
174
181
|
{ installId: "gcp-firebase-acme", stateBackend: record },
|
|
@@ -566,3 +573,49 @@ describe("helpers", () => {
|
|
|
566
573
|
expect(localStateNotice({ installId: "x", provider: "self-hosted" })).toBeNull();
|
|
567
574
|
});
|
|
568
575
|
});
|
|
576
|
+
|
|
577
|
+
describe("migrateState — Application Default Credentials (#1078)", () => {
|
|
578
|
+
/** A Pulumi workspace that fails the test on any call. */
|
|
579
|
+
const untouchable = (): StatePulumiOps =>
|
|
580
|
+
new Proxy({} as StatePulumiOps, {
|
|
581
|
+
get: (_t, name) => () => {
|
|
582
|
+
throw new Error(`Pulumi ${String(name)} must not run without working ADC`);
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
for (const [what, error] of [
|
|
587
|
+
[
|
|
588
|
+
"missing",
|
|
589
|
+
"No Application Default Credentials on this machine; Pulumi needs them. Run `gcloud auth application-default login` and re-run this command.",
|
|
590
|
+
],
|
|
591
|
+
[
|
|
592
|
+
"expired",
|
|
593
|
+
"The Application Default Credentials on this machine have expired (gcloud: invalid_grant: reauth related error (invalid_rapt)). Run `gcloud auth application-default login` and re-run this command.",
|
|
594
|
+
],
|
|
595
|
+
] as const) {
|
|
596
|
+
for (const dryRun of [false, true]) {
|
|
597
|
+
it(`${what} ADC stop the run before any Pulumi call${dryRun ? " (dry run)" : ""}`, async () => {
|
|
598
|
+
const t = machine();
|
|
599
|
+
t.m.adc = { ok: false, error };
|
|
600
|
+
const r = await migrateState(
|
|
601
|
+
t.meta(),
|
|
602
|
+
{ to: "remote", dryRun },
|
|
603
|
+
{ ...t.deps, pulumi: untouchable() },
|
|
604
|
+
);
|
|
605
|
+
expect(r).toEqual({ ok: false, error: `${error} Nothing was changed.` });
|
|
606
|
+
expect(t.m.adcChecks).toBe(1);
|
|
607
|
+
expect(t.m.backend).toEqual({ kind: "local" });
|
|
608
|
+
expect(t.m.backups).toBe(0);
|
|
609
|
+
expect(t.m.ensureRemoteCalls).toEqual([]);
|
|
610
|
+
expect(t.m.locked).toBe(false);
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
it("working ADC let the migration run", async () => {
|
|
616
|
+
const t = machine();
|
|
617
|
+
const r = await migrateState(t.meta(), { to: "remote", dryRun: false }, t.deps);
|
|
618
|
+
expect(r).toEqual({ ok: true, changed: true });
|
|
619
|
+
expect(t.m.adcChecks).toBe(1);
|
|
620
|
+
});
|
|
621
|
+
});
|
package/src/state-migrate.ts
CHANGED
|
@@ -82,6 +82,13 @@ export interface StatePulumiOps {
|
|
|
82
82
|
export interface MigrateDeps {
|
|
83
83
|
readonly pulumi: StatePulumiOps;
|
|
84
84
|
readonly report: UpgradeReporter;
|
|
85
|
+
/**
|
|
86
|
+
* #1078 — whether this machine's Application Default Credentials (which
|
|
87
|
+
* every Pulumi call here uses) work now. Checked before the first Pulumi
|
|
88
|
+
* call, so missing or expired ones stop the run with the login command
|
|
89
|
+
* instead of a page of per-resource provider errors.
|
|
90
|
+
*/
|
|
91
|
+
checkAdc(): Promise<{ readonly ok: true } | { readonly ok: false; readonly error: string }>;
|
|
85
92
|
/** The install's backend resolved for Pulumi. */
|
|
86
93
|
resolve(record: StateBackendRecord): ResolvedBackend;
|
|
87
94
|
/** Ask the operator to accept the ops backend's cost; false = cancel. */
|
|
@@ -241,6 +248,10 @@ export async function migrateState(
|
|
|
241
248
|
};
|
|
242
249
|
}
|
|
243
250
|
}
|
|
251
|
+
const adc = await deps.checkAdc();
|
|
252
|
+
if (!adc.ok) {
|
|
253
|
+
return { ok: false, error: `${adc.error} Nothing was changed.` };
|
|
254
|
+
}
|
|
244
255
|
const pre = report.step("Preflight: Pulumi CLI, source stack, pending operations, drift");
|
|
245
256
|
let from: ResolvedBackend;
|
|
246
257
|
let mode: "stack-migrate" | "export-import";
|
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,
|
|
@@ -639,19 +640,20 @@ async function resolveImageDigests(): Promise<ImageDigests> {
|
|
|
639
640
|
// Per-step helpers
|
|
640
641
|
// =========================================================================
|
|
641
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
|
+
*/
|
|
642
649
|
async function stepActiveGcloud(): Promise<void> {
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
log.warn(
|
|
648
|
-
`Run ${bold("gcloud auth login")} in another terminal, then re-run ${bold(selfCommand())}.`,
|
|
649
|
-
);
|
|
650
|
-
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.");
|
|
651
654
|
process.exit(2);
|
|
652
655
|
}
|
|
653
|
-
log.success(`gcloud auth: ${bold(account)}`);
|
|
654
|
-
void stepName;
|
|
656
|
+
log.success(`gcloud auth: ${bold(auth.account)}`);
|
|
655
657
|
}
|
|
656
658
|
|
|
657
659
|
async function stepProjectId(opts: GcpWizardOpts): Promise<string> {
|
|
@@ -1385,8 +1387,18 @@ async function stepKeylessProvisioner(
|
|
|
1385
1387
|
const sAdc = spinner();
|
|
1386
1388
|
sAdc.start("Checking that your Application Default Credentials can impersonate it...");
|
|
1387
1389
|
let adc = await check();
|
|
1388
|
-
if (
|
|
1389
|
-
|
|
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
|
+
);
|
|
1390
1402
|
const login = await confirm({
|
|
1391
1403
|
message: `Run ${bold("gcloud auth application-default login")} now (opens your browser; sign in as ${account})?`,
|
|
1392
1404
|
initialValue: true,
|
|
@@ -1403,7 +1415,7 @@ async function stepKeylessProvisioner(
|
|
|
1403
1415
|
}
|
|
1404
1416
|
if (!adc.ok) {
|
|
1405
1417
|
sAdc.stop(red("Infrastructure credentials not ready"));
|
|
1406
|
-
log.error(adc.error);
|
|
1418
|
+
log.error(`${adc.error}\nPulumi was not started.`);
|
|
1407
1419
|
cancel("Aborted.");
|
|
1408
1420
|
process.exit(1);
|
|
1409
1421
|
}
|