@continuous-excellence/ze-great-dashboard-aws 0.14.3 → 0.14.5
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/bootstrap-check.d.ts +2 -0
- package/dist/bootstrap.d.ts +2 -0
- package/dist/cli.js +278 -139
- package/dist/doctor.d.ts +2 -0
- package/dist/guided.d.ts +9 -1
- package/dist/handoff.d.ts +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +117 -14
- package/dist/remediation.d.ts +23 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type BootstrapConfig, type BootstrapConsistency, type BootstrapKind } from './bootstrap.js';
|
|
2
|
+
import { type BootstrapRemediation } from './remediation.js';
|
|
2
3
|
export type BootstrapResourceDifference = {
|
|
3
4
|
path?: string;
|
|
4
5
|
differenceType?: string;
|
|
@@ -32,6 +33,7 @@ export type BootstrapCheck = {
|
|
|
32
33
|
ok: boolean;
|
|
33
34
|
packageVersion: string;
|
|
34
35
|
stacks: BootstrapStackCheck[];
|
|
36
|
+
remediation: BootstrapRemediation;
|
|
35
37
|
};
|
|
36
38
|
export type BootstrapCheckDependencies = {
|
|
37
39
|
execute(command: string, args: string[]): Promise<string>;
|
package/dist/bootstrap.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ComputeMode } from './compute-mode.js';
|
|
2
|
+
import { type BootstrapRemediation } from './remediation.js';
|
|
2
3
|
export { type ComputeMode, computeMode, resolveComputeMode } from './compute-mode.js';
|
|
3
4
|
export type BootstrapKind = 'core' | 'github-oidc';
|
|
4
5
|
export type CloudFormationParameterValue = {
|
|
@@ -55,6 +56,7 @@ export type BootstrapPlan = {
|
|
|
55
56
|
packageTemplates: BootstrapTemplateInspection[];
|
|
56
57
|
configuration: BootstrapConfig;
|
|
57
58
|
notes: string[];
|
|
59
|
+
remediation: BootstrapRemediation;
|
|
58
60
|
};
|
|
59
61
|
export type BootstrapConsistency = {
|
|
60
62
|
ok: boolean;
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// packages/aws/src/cli.ts
|
|
4
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
4
5
|
import { readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
|
|
5
6
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7
|
+
import { promisify as promisify3 } from "node:util";
|
|
6
8
|
import { parse as parse3 } from "yaml";
|
|
7
9
|
|
|
8
10
|
// packages/aws/src/doctor.ts
|
|
@@ -159,6 +161,46 @@ function sha256(value2) {
|
|
|
159
161
|
return createHash("sha256").update(value2).digest("hex");
|
|
160
162
|
}
|
|
161
163
|
|
|
164
|
+
// packages/aws/src/remediation.ts
|
|
165
|
+
var safetyNote = "AWS mutations are emitted for explicit administrator review and invocation; this library never executes them.";
|
|
166
|
+
function bootstrapRemediation(config, input = {}) {
|
|
167
|
+
const affectedStacks = input.affectedStacks ?? [
|
|
168
|
+
["core", config.core?.stackName],
|
|
169
|
+
["github-oidc", config.githubOidc?.stackName]
|
|
170
|
+
].filter((entry) => Boolean(entry[1])).map(([kind, stackName2]) => ({ kind, stackName: stackName2 }));
|
|
171
|
+
const configPath = input.configPath ?? "manifest.json";
|
|
172
|
+
const checkCommand = `ze-great-dashboard-aws bootstrap check --config ${configPath} --format json`;
|
|
173
|
+
const issues = input.issues ?? [];
|
|
174
|
+
const summary = input.summary ?? (issues.length ? `Bootstrap validation found ${issues.length} issue${issues.length === 1 ? "" : "s"}.` : "Bootstrap validation is ready for the next reviewed operation.");
|
|
175
|
+
return {
|
|
176
|
+
failureSummary: summary,
|
|
177
|
+
affectedStacks,
|
|
178
|
+
immediateSteps: [
|
|
179
|
+
...issues.length ? ["Review each reported mismatch, access error, or drift finding."] : [],
|
|
180
|
+
input.nextOperation ?? "Run the next bootstrap operation from the generated handoff.",
|
|
181
|
+
"Capture the resulting CloudFormation stack output for the next validation step."
|
|
182
|
+
],
|
|
183
|
+
upgradeSteps: [
|
|
184
|
+
"If the contract or template revision is stale, generate parameters from this installed package and create a reviewed UPDATE change set for each affected stack.",
|
|
185
|
+
"Review IAM actions, retained resources, parameters, and the change set before an administrator executes it."
|
|
186
|
+
],
|
|
187
|
+
revalidateCommand: checkCommand,
|
|
188
|
+
safetyNote
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function formatBootstrapRemediationText(remediation) {
|
|
192
|
+
return [
|
|
193
|
+
`Remediation: ${remediation.failureSummary}`,
|
|
194
|
+
`Affected stacks: ${remediation.affectedStacks.length ? remediation.affectedStacks.map(({ kind, stackName: stackName2, issue }) => `${kind}=${stackName2}${issue ? ` (${issue})` : ""}`).join(", ") : "none identified"}`,
|
|
195
|
+
"Immediate steps:",
|
|
196
|
+
...remediation.immediateSteps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
197
|
+
"Upgrade steps:",
|
|
198
|
+
...remediation.upgradeSteps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
199
|
+
`Revalidate: ${remediation.revalidateCommand}`,
|
|
200
|
+
`Safety: ${remediation.safetyNote}`
|
|
201
|
+
];
|
|
202
|
+
}
|
|
203
|
+
|
|
162
204
|
// packages/aws/src/bootstrap.ts
|
|
163
205
|
var templates = {
|
|
164
206
|
core: {
|
|
@@ -231,7 +273,10 @@ async function bootstrapPlan(config) {
|
|
|
231
273
|
"Templates are owned by the installed npm package; package.json and package-lock.json pin the source version.",
|
|
232
274
|
"Generated CloudFormation parameter files and describe-stacks captures are deployment artifacts, not source configuration.",
|
|
233
275
|
"This plan performs no AWS or GitHub mutations."
|
|
234
|
-
]
|
|
276
|
+
],
|
|
277
|
+
remediation: bootstrapRemediation(config, {
|
|
278
|
+
nextOperation: "Review the installed templates, then run bootstrap preflight before creating a change set."
|
|
279
|
+
})
|
|
235
280
|
};
|
|
236
281
|
}
|
|
237
282
|
function capturedStack(input) {
|
|
@@ -1118,6 +1163,7 @@ function formatBootstrapCheckText(result) {
|
|
|
1118
1163
|
);
|
|
1119
1164
|
}
|
|
1120
1165
|
}
|
|
1166
|
+
lines.push("", ...formatBootstrapRemediationText(result.remediation));
|
|
1121
1167
|
return `${lines.join("\n")}
|
|
1122
1168
|
`;
|
|
1123
1169
|
}
|
|
@@ -1337,12 +1383,30 @@ async function checkBootstrap(config, options, dependencies) {
|
|
|
1337
1383
|
return checked;
|
|
1338
1384
|
})
|
|
1339
1385
|
);
|
|
1386
|
+
const failed = stacks.filter(
|
|
1387
|
+
({ consistency, resourceDrift: resourceDrift2 }) => !consistency.ok || Boolean(resourceDrift2 && !resourceDrift2.ok)
|
|
1388
|
+
);
|
|
1340
1389
|
return {
|
|
1341
1390
|
ok: stacks.every(
|
|
1342
1391
|
({ consistency, resourceDrift: drift }) => consistency.ok && (!drift || drift.ok)
|
|
1343
1392
|
),
|
|
1344
1393
|
packageVersion: plan.packageVersion,
|
|
1345
|
-
stacks
|
|
1394
|
+
stacks,
|
|
1395
|
+
remediation: bootstrapRemediation(config, {
|
|
1396
|
+
summary: failed.length ? `Bootstrap validation failed for ${failed.length} stack${failed.length === 1 ? "" : "s"}.` : "Bootstrap stacks are consistent and ready for the deployment check.",
|
|
1397
|
+
affectedStacks: (failed.length ? failed : stacks).map(
|
|
1398
|
+
({ kind, stackName: stackName2, consistency, resourceDrift: resourceDrift2 }) => ({
|
|
1399
|
+
kind,
|
|
1400
|
+
stackName: stackName2,
|
|
1401
|
+
...!consistency.ok ? { issue: consistency.mismatches[0] ?? "consistency mismatch" } : resourceDrift2 && !resourceDrift2.ok ? { issue: `resource drift: ${resourceDrift2.status}` } : {}
|
|
1402
|
+
})
|
|
1403
|
+
),
|
|
1404
|
+
nextOperation: failed.length ? "Apply only reviewed administrator-approved bootstrap updates, then capture both stacks again." : "Run the deployment or release check that consumes these bootstrap outputs.",
|
|
1405
|
+
issues: failed.flatMap(({ consistency, resourceDrift: resourceDrift2 }) => [
|
|
1406
|
+
...consistency.mismatches,
|
|
1407
|
+
...resourceDrift2 && !resourceDrift2.ok ? [`resource drift: ${resourceDrift2.status}`] : []
|
|
1408
|
+
])
|
|
1409
|
+
})
|
|
1346
1410
|
};
|
|
1347
1411
|
}
|
|
1348
1412
|
|
|
@@ -1476,7 +1540,7 @@ function changeSetCommands(input) {
|
|
|
1476
1540
|
}
|
|
1477
1541
|
var githubOidcProvider = {
|
|
1478
1542
|
name: "github-oidc",
|
|
1479
|
-
async prerequisite(config,
|
|
1543
|
+
async prerequisite(config, runner2) {
|
|
1480
1544
|
const repository = config.githubOidc?.repository;
|
|
1481
1545
|
if (!repository)
|
|
1482
1546
|
return {
|
|
@@ -1484,7 +1548,7 @@ var githubOidcProvider = {
|
|
|
1484
1548
|
blocking: false,
|
|
1485
1549
|
detail: "GitHub repository is absent from the manifest."
|
|
1486
1550
|
};
|
|
1487
|
-
if (!
|
|
1551
|
+
if (!runner2)
|
|
1488
1552
|
return {
|
|
1489
1553
|
status: "unverified",
|
|
1490
1554
|
blocking: false,
|
|
@@ -1492,7 +1556,7 @@ var githubOidcProvider = {
|
|
|
1492
1556
|
};
|
|
1493
1557
|
try {
|
|
1494
1558
|
const response = JSON.parse(
|
|
1495
|
-
await
|
|
1559
|
+
await runner2.execute("gh", ["api", `repos/${repository}/actions/oidc/customization/sub`])
|
|
1496
1560
|
);
|
|
1497
1561
|
const keys = response.include_claim_keys;
|
|
1498
1562
|
if (response.use_default === false && Array.isArray(keys) && githubSubjectKeys.every((key) => keys.includes(key)))
|
|
@@ -1559,6 +1623,10 @@ async function bootstrapHandoff(input) {
|
|
|
1559
1623
|
templatePath,
|
|
1560
1624
|
parameterPath: workFile(input.workDir, "core-bootstrap.json"),
|
|
1561
1625
|
workDir: input.workDir
|
|
1626
|
+
}),
|
|
1627
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1628
|
+
configPath: input.configPath,
|
|
1629
|
+
nextOperation: "Review and execute the core bootstrap change set, then capture the core stack."
|
|
1562
1630
|
})
|
|
1563
1631
|
};
|
|
1564
1632
|
}
|
|
@@ -1602,7 +1670,11 @@ async function bootstrapHandoff(input) {
|
|
|
1602
1670
|
prerequisite: await (input.provider ?? githubOidcProvider).prerequisite(
|
|
1603
1671
|
input.config,
|
|
1604
1672
|
input.runner
|
|
1605
|
-
)
|
|
1673
|
+
),
|
|
1674
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1675
|
+
configPath: input.configPath,
|
|
1676
|
+
nextOperation: "Review and execute the GitHub OIDC change set, then capture the GitHub OIDC stack."
|
|
1677
|
+
})
|
|
1606
1678
|
};
|
|
1607
1679
|
}
|
|
1608
1680
|
const prerequisite = await (input.provider ?? githubOidcProvider).prerequisite(
|
|
@@ -1620,7 +1692,12 @@ async function bootstrapHandoff(input) {
|
|
|
1620
1692
|
"A GitHub administrator must complete and verify the immutable-subject migration before deployments."
|
|
1621
1693
|
],
|
|
1622
1694
|
commands: [],
|
|
1623
|
-
prerequisite
|
|
1695
|
+
prerequisite,
|
|
1696
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1697
|
+
configPath: input.configPath,
|
|
1698
|
+
summary: "The GitHub Environment immutable-subject prerequisite is not verified.",
|
|
1699
|
+
nextOperation: "A GitHub administrator must complete and verify the immutable-subject migration, then rerun bootstrap handoff."
|
|
1700
|
+
})
|
|
1624
1701
|
};
|
|
1625
1702
|
return {
|
|
1626
1703
|
phase: "application-gateway",
|
|
@@ -1631,7 +1708,11 @@ async function bootstrapHandoff(input) {
|
|
|
1631
1708
|
"Consumer owns gateway selection, private Lambda permission, authentication, and smoke tests."
|
|
1632
1709
|
],
|
|
1633
1710
|
commands: [],
|
|
1634
|
-
prerequisite
|
|
1711
|
+
prerequisite,
|
|
1712
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1713
|
+
configPath: input.configPath,
|
|
1714
|
+
nextOperation: "Configure and verify the consumer gateway, then run bootstrap verify and the deployment check."
|
|
1715
|
+
})
|
|
1635
1716
|
};
|
|
1636
1717
|
}
|
|
1637
1718
|
function assertEqual(actual, expected, label) {
|
|
@@ -1751,7 +1832,10 @@ async function verifyBootstrap(input) {
|
|
|
1751
1832
|
"--body",
|
|
1752
1833
|
executionRole
|
|
1753
1834
|
]
|
|
1754
|
-
]
|
|
1835
|
+
],
|
|
1836
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1837
|
+
nextOperation: "Set the reviewed GitHub Environment variables, then run bootstrap check before deployment."
|
|
1838
|
+
})
|
|
1755
1839
|
};
|
|
1756
1840
|
}
|
|
1757
1841
|
|
|
@@ -1768,10 +1852,10 @@ function repositoryParts(repository) {
|
|
|
1768
1852
|
function accountFromArn(arn) {
|
|
1769
1853
|
return arn.match(/^arn:[^:]+:iam::(\d+):oidc-provider\//)?.[1];
|
|
1770
1854
|
}
|
|
1771
|
-
async function optional(
|
|
1772
|
-
if (!
|
|
1855
|
+
async function optional(runner2, command, args2) {
|
|
1856
|
+
if (!runner2) return void 0;
|
|
1773
1857
|
try {
|
|
1774
|
-
return await
|
|
1858
|
+
return await runner2.execute(command, args2);
|
|
1775
1859
|
} catch {
|
|
1776
1860
|
return void 0;
|
|
1777
1861
|
}
|
|
@@ -1869,9 +1953,18 @@ async function bootstrapPreflight(input) {
|
|
|
1869
1953
|
checks.push(
|
|
1870
1954
|
missing.length ? { name: "manifest", status: "missing", detail: `Missing: ${missing.join(", ")}` } : { name: "manifest", status: "ready", detail: "Manifest has all bootstrap fields." }
|
|
1871
1955
|
);
|
|
1872
|
-
if (missing.length)
|
|
1873
|
-
|
|
1874
|
-
|
|
1956
|
+
if (missing.length)
|
|
1957
|
+
return {
|
|
1958
|
+
ready: false,
|
|
1959
|
+
checks,
|
|
1960
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1961
|
+
summary: "Bootstrap manifest configuration is incomplete.",
|
|
1962
|
+
issues: missing,
|
|
1963
|
+
nextOperation: "Complete the missing manifest fields, then rerun bootstrap preflight."
|
|
1964
|
+
})
|
|
1965
|
+
};
|
|
1966
|
+
const runner2 = input.runner;
|
|
1967
|
+
const identityRaw = await optional(runner2, "aws", [
|
|
1875
1968
|
"sts",
|
|
1876
1969
|
"get-caller-identity",
|
|
1877
1970
|
"--output",
|
|
@@ -1897,7 +1990,7 @@ async function bootstrapPreflight(input) {
|
|
|
1897
1990
|
detail: `AWS account ${identity.Account} does not match provider account ${providerAccount}.`
|
|
1898
1991
|
}
|
|
1899
1992
|
);
|
|
1900
|
-
const regionRaw = await optional(
|
|
1993
|
+
const regionRaw = await optional(runner2, "aws", ["configure", "get", "region"]);
|
|
1901
1994
|
if (!regionRaw?.trim())
|
|
1902
1995
|
checks.push({
|
|
1903
1996
|
name: "aws-region",
|
|
@@ -1912,7 +2005,7 @@ async function bootstrapPreflight(input) {
|
|
|
1912
2005
|
detail: `AWS Region ${regionRaw.trim()} does not match manifest ${input.config.region}.`
|
|
1913
2006
|
}
|
|
1914
2007
|
);
|
|
1915
|
-
const providerRaw = await optional(
|
|
2008
|
+
const providerRaw = await optional(runner2, "aws", [
|
|
1916
2009
|
"iam",
|
|
1917
2010
|
"get-open-id-connect-provider",
|
|
1918
2011
|
"--open-id-connect-provider-arn",
|
|
@@ -1926,7 +2019,7 @@ async function bootstrapPreflight(input) {
|
|
|
1926
2019
|
} : absent(providerRaw) ? { name: "oidc-provider", status: "missing", detail: "OIDC provider does not exist." } : { name: "oidc-provider", status: "ready", detail: "OIDC provider exists." }
|
|
1927
2020
|
);
|
|
1928
2021
|
const repository = input.config.githubOidc?.repository ?? "";
|
|
1929
|
-
const repoRaw = await optional(
|
|
2022
|
+
const repoRaw = await optional(runner2, "gh", ["api", `repos/${repository}`]);
|
|
1930
2023
|
const repo = parseOrUnavailable(repoRaw);
|
|
1931
2024
|
if (!repoRaw)
|
|
1932
2025
|
checks.push({
|
|
@@ -1952,7 +2045,7 @@ async function bootstrapPreflight(input) {
|
|
|
1952
2045
|
status: "ready",
|
|
1953
2046
|
detail: "GitHub repository identity matches the manifest."
|
|
1954
2047
|
});
|
|
1955
|
-
const environmentRaw = await optional(
|
|
2048
|
+
const environmentRaw = await optional(runner2, "gh", [
|
|
1956
2049
|
"api",
|
|
1957
2050
|
`repos/${repository}/environments/${input.config.githubOidc?.environment}`
|
|
1958
2051
|
]);
|
|
@@ -1971,23 +2064,28 @@ async function bootstrapPreflight(input) {
|
|
|
1971
2064
|
detail: "GitHub Environment exists; its policy is administrator-owned context."
|
|
1972
2065
|
}
|
|
1973
2066
|
);
|
|
1974
|
-
const subject = await (input.provider ?? githubOidcProvider).prerequisite(input.config,
|
|
2067
|
+
const subject = await (input.provider ?? githubOidcProvider).prerequisite(input.config, runner2);
|
|
1975
2068
|
checks.push({
|
|
1976
2069
|
name: "immutable-subject",
|
|
1977
2070
|
status: subject.status === "immutable-subject-required" ? "mismatch" : subject.status,
|
|
1978
2071
|
detail: subject.detail
|
|
1979
2072
|
});
|
|
2073
|
+
const ready = !checks.some(({ status }) => status === "missing" || status === "mismatch");
|
|
1980
2074
|
return {
|
|
1981
|
-
ready
|
|
1982
|
-
checks
|
|
2075
|
+
ready,
|
|
2076
|
+
checks,
|
|
2077
|
+
remediation: bootstrapRemediation(input.config, {
|
|
2078
|
+
summary: ready ? "Bootstrap preflight is ready." : "Bootstrap preflight found configuration or identity issues.",
|
|
2079
|
+
issues: checks.filter(({ status }) => status === "missing" || status === "mismatch").map(({ detail }) => detail),
|
|
2080
|
+
nextOperation: ready ? "Run bootstrap plan, then create and review the next bootstrap change set." : "Resolve the reported configuration or identity issues, then rerun bootstrap preflight."
|
|
2081
|
+
})
|
|
1983
2082
|
};
|
|
1984
2083
|
}
|
|
1985
2084
|
function quote(args2) {
|
|
1986
2085
|
return args2.map((arg) => `'${arg.replaceAll("'", `'\\"'\\"'`)}'`).join(" ");
|
|
1987
2086
|
}
|
|
1988
|
-
async function
|
|
1989
|
-
const handoff = await bootstrapHandoff(input);
|
|
1990
|
-
const plan = await bootstrapPlan(input.config);
|
|
2087
|
+
async function bootstrapGuideReport(input) {
|
|
2088
|
+
const [handoff, plan] = await Promise.all([bootstrapHandoff(input), bootstrapPlan(input.config)]);
|
|
1991
2089
|
const lines = [
|
|
1992
2090
|
`Phase: ${handoff.phase}`,
|
|
1993
2091
|
`Package version: ${plan.packageVersion}`,
|
|
@@ -2024,8 +2122,9 @@ async function bootstrapGuide(input) {
|
|
|
2024
2122
|
);
|
|
2025
2123
|
for (const command of verified.githubEnvironmentInstructions) lines.push(quote(command));
|
|
2026
2124
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2125
|
+
lines.push("", ...formatBootstrapRemediationText(plan.remediation));
|
|
2126
|
+
return { handoff, plan, guide: `${lines.join("\n")}
|
|
2127
|
+
` };
|
|
2029
2128
|
}
|
|
2030
2129
|
|
|
2031
2130
|
// packages/aws/src/index.ts
|
|
@@ -2357,11 +2456,20 @@ async function runDoctor(options, dependencies = actualDependencies) {
|
|
|
2357
2456
|
checkResult.detail = `WARNING: ${checkResult.detail}`;
|
|
2358
2457
|
}
|
|
2359
2458
|
}
|
|
2360
|
-
|
|
2459
|
+
const remediation = bootstrapRemediation(
|
|
2460
|
+
{},
|
|
2461
|
+
{
|
|
2462
|
+
summary: checks.some(({ ok }) => !ok) ? "Deployment doctor found issues that require operator review." : "Deployment doctor is healthy; proceed to the deployment check.",
|
|
2463
|
+
issues: checks.filter(({ ok }) => !ok).map(({ detail }) => detail),
|
|
2464
|
+
nextOperation: checks.some(({ ok }) => !ok) ? "Resolve failed checks, then rerun the deployment doctor and bootstrap check." : "Run bootstrap check before deploying the release."
|
|
2465
|
+
}
|
|
2466
|
+
);
|
|
2467
|
+
return checks.map((check2) => ({ ...check2, remediation }));
|
|
2361
2468
|
}
|
|
2362
2469
|
|
|
2363
2470
|
// packages/aws/src/cli.ts
|
|
2364
2471
|
var args = process.argv.slice(2);
|
|
2472
|
+
var runCommand = promisify3(execFile3);
|
|
2365
2473
|
var option = (name, fallback) => {
|
|
2366
2474
|
const i2 = args.indexOf(name);
|
|
2367
2475
|
return i2 >= 0 ? args[i2 + 1] : fallback;
|
|
@@ -2451,6 +2559,38 @@ function bootstrapStackName(kind, config) {
|
|
|
2451
2559
|
function shellCommand(command) {
|
|
2452
2560
|
return command.map((argument) => `'${argument.replaceAll("'", `'\\"'\\"'`)}'`).join(" ");
|
|
2453
2561
|
}
|
|
2562
|
+
function outputFormat(defaultFormat = "json") {
|
|
2563
|
+
if (args.includes("--format-shell")) return "shell";
|
|
2564
|
+
const format = option("--format", defaultFormat);
|
|
2565
|
+
if (format !== "json" && format !== "text") throw new Error("--format must be json or text");
|
|
2566
|
+
return format;
|
|
2567
|
+
}
|
|
2568
|
+
function printBootstrap(value2, text, shell, defaultFormat = "json") {
|
|
2569
|
+
const format = outputFormat(defaultFormat);
|
|
2570
|
+
if (format === "shell") {
|
|
2571
|
+
if (!shell) throw new Error("--format-shell is only supported for command handoffs");
|
|
2572
|
+
console.log(shell);
|
|
2573
|
+
} else if (format === "text") console.log(text);
|
|
2574
|
+
else console.log(JSON.stringify(value2));
|
|
2575
|
+
}
|
|
2576
|
+
var runner = {
|
|
2577
|
+
async execute(command, commandArgs) {
|
|
2578
|
+
return (await runCommand(command, commandArgs)).stdout.trim();
|
|
2579
|
+
}
|
|
2580
|
+
};
|
|
2581
|
+
var preflightRunner = {
|
|
2582
|
+
async execute(command, commandArgs) {
|
|
2583
|
+
try {
|
|
2584
|
+
return await runner.execute(command, commandArgs);
|
|
2585
|
+
} catch (error) {
|
|
2586
|
+
if (error && typeof error === "object" && "stderr" in error) {
|
|
2587
|
+
const stderr = error.stderr;
|
|
2588
|
+
if (typeof stderr === "string" && /not found|404|nosuchentity/i.test(stderr)) return stderr;
|
|
2589
|
+
}
|
|
2590
|
+
throw error;
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
};
|
|
2454
2594
|
async function templateParameters(mode = "lambda") {
|
|
2455
2595
|
const template = await cloudFormationTemplate(mode);
|
|
2456
2596
|
const block = template.match(/^Parameters:\n[\s\S]*?(?=^[A-Za-z][A-Za-z0-9]*:\s*$)/m)?.[0];
|
|
@@ -2574,11 +2714,7 @@ try {
|
|
|
2574
2714
|
githubOidcStackPath: option("--github-oidc-stack-json")
|
|
2575
2715
|
},
|
|
2576
2716
|
{
|
|
2577
|
-
|
|
2578
|
-
const { execFile: execFile3 } = await import("node:child_process");
|
|
2579
|
-
const { promisify: promisify3 } = await import("node:util");
|
|
2580
|
-
return (await promisify3(execFile3)(command, commandArgs)).stdout.trim();
|
|
2581
|
-
},
|
|
2717
|
+
execute: runner.execute,
|
|
2582
2718
|
async fetch(url) {
|
|
2583
2719
|
return fetch(url);
|
|
2584
2720
|
},
|
|
@@ -2586,10 +2722,14 @@ try {
|
|
|
2586
2722
|
packageVersion
|
|
2587
2723
|
}
|
|
2588
2724
|
);
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2725
|
+
const remediation = checks[0]?.remediation;
|
|
2726
|
+
if (outputFormat("text") === "text") {
|
|
2727
|
+
for (const check of checks)
|
|
2728
|
+
console.log(
|
|
2729
|
+
`${check.warning ? "WARN" : check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`
|
|
2730
|
+
);
|
|
2731
|
+
if (remediation) console.log(formatBootstrapRemediationText(remediation).join("\n"));
|
|
2732
|
+
} else console.log(JSON.stringify({ checks, remediation }));
|
|
2593
2733
|
if (checks.some(({ ok }) => !ok)) process.exitCode = 1;
|
|
2594
2734
|
} else if (args[0] === "parameters") {
|
|
2595
2735
|
const output = option("--output", "aws-dashboard-parameters.json") ?? "aws-dashboard-parameters.json";
|
|
@@ -2636,7 +2776,7 @@ try {
|
|
|
2636
2776
|
});
|
|
2637
2777
|
await writeFile3(output, `${JSON.stringify(parameters, null, 2)}
|
|
2638
2778
|
`);
|
|
2639
|
-
|
|
2779
|
+
printBootstrap({ output }, `Wrote ${output}. Next: run the generated deployment handoff.`);
|
|
2640
2780
|
} else if (args[0] === "bootstrap") {
|
|
2641
2781
|
const action = args[1];
|
|
2642
2782
|
if (action === "init") {
|
|
@@ -2648,13 +2788,6 @@ try {
|
|
|
2648
2788
|
if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT"))
|
|
2649
2789
|
throw error;
|
|
2650
2790
|
}
|
|
2651
|
-
const runner = {
|
|
2652
|
-
async execute(command, commandArgs) {
|
|
2653
|
-
const { execFile: execFile3 } = await import("node:child_process");
|
|
2654
|
-
const { promisify: promisify3 } = await import("node:util");
|
|
2655
|
-
return (await promisify3(execFile3)(command, commandArgs)).stdout.trim();
|
|
2656
|
-
}
|
|
2657
|
-
};
|
|
2658
2791
|
const manifest = await scaffoldBootstrapManifest({
|
|
2659
2792
|
slug: requiredOption("--slug"),
|
|
2660
2793
|
repository: requiredOption("--repository"),
|
|
@@ -2670,109 +2803,105 @@ try {
|
|
|
2670
2803
|
});
|
|
2671
2804
|
await writeFile3(output, `${JSON.stringify(manifest, null, 2)}
|
|
2672
2805
|
`, { flag: "wx" });
|
|
2673
|
-
|
|
2806
|
+
printBootstrap(
|
|
2807
|
+
{ output, manifest, remediation: (await bootstrapPlan(manifest)).remediation },
|
|
2808
|
+
`Wrote ${output}. Next: run bootstrap preflight, then bootstrap handoff.`
|
|
2809
|
+
);
|
|
2674
2810
|
} else {
|
|
2675
2811
|
const config = await bootstrapConfig();
|
|
2676
2812
|
if (action === "preflight") {
|
|
2677
2813
|
requiredOption("--config");
|
|
2678
|
-
const
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
}
|
|
2690
|
-
throw error;
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
};
|
|
2694
|
-
const result = await bootstrapPreflight({ config, runner });
|
|
2695
|
-
if (option("--format") === "text")
|
|
2696
|
-
for (const check of result.checks)
|
|
2697
|
-
console.log(`${check.status.toUpperCase()} ${check.name}: ${check.detail}`);
|
|
2698
|
-
else console.log(JSON.stringify(result));
|
|
2814
|
+
const result = await bootstrapPreflight({ config, runner: preflightRunner });
|
|
2815
|
+
printBootstrap(
|
|
2816
|
+
result,
|
|
2817
|
+
[
|
|
2818
|
+
...result.checks.map(
|
|
2819
|
+
(check) => `${check.status.toUpperCase()} ${check.name}: ${check.detail}`
|
|
2820
|
+
),
|
|
2821
|
+
"",
|
|
2822
|
+
...formatBootstrapRemediationText(result.remediation)
|
|
2823
|
+
].join("\n")
|
|
2824
|
+
);
|
|
2699
2825
|
if (!result.ready) process.exitCode = 1;
|
|
2700
2826
|
} else if (action === "guide") {
|
|
2701
2827
|
const configPath = requiredOption("--config");
|
|
2702
2828
|
const coreStackPath = option("--core-stack-json");
|
|
2703
2829
|
const githubStackPath = option("--github-oidc-stack-json");
|
|
2704
|
-
const
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2830
|
+
const report = await bootstrapGuideReport({
|
|
2831
|
+
config,
|
|
2832
|
+
configPath,
|
|
2833
|
+
workDir: option("--work-dir"),
|
|
2834
|
+
coreStack: coreStackPath ? JSON.parse(await readFile5(coreStackPath, "utf8")) : void 0,
|
|
2835
|
+
coreStackPath,
|
|
2836
|
+
githubOidcStack: githubStackPath ? JSON.parse(await readFile5(githubStackPath, "utf8")) : void 0,
|
|
2837
|
+
githubOidcStackPath: githubStackPath,
|
|
2838
|
+
runner
|
|
2839
|
+
});
|
|
2840
|
+
printBootstrap(
|
|
2841
|
+
{
|
|
2842
|
+
handoff: report.handoff,
|
|
2843
|
+
guide: report.guide,
|
|
2844
|
+
remediation: report.handoff.remediation
|
|
2845
|
+
},
|
|
2846
|
+
report.guide,
|
|
2847
|
+
void 0,
|
|
2848
|
+
"text"
|
|
2722
2849
|
);
|
|
2723
2850
|
} else if (action === "handoff") {
|
|
2724
2851
|
const configPath = requiredOption("--config");
|
|
2725
2852
|
const coreStackPath = option("--core-stack-json");
|
|
2726
2853
|
const githubStackPath = option("--github-oidc-stack-json");
|
|
2727
|
-
const
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
coreStackPath,
|
|
2742
|
-
githubOidcStack: githubStackPath ? JSON.parse(await readFile5(githubStackPath, "utf8")) : void 0,
|
|
2743
|
-
githubOidcStackPath: githubStackPath,
|
|
2744
|
-
runner
|
|
2745
|
-
})
|
|
2746
|
-
)
|
|
2854
|
+
const handoff = await bootstrapHandoff({
|
|
2855
|
+
config,
|
|
2856
|
+
configPath,
|
|
2857
|
+
workDir: option("--work-dir"),
|
|
2858
|
+
coreStack: coreStackPath ? JSON.parse(await readFile5(coreStackPath, "utf8")) : void 0,
|
|
2859
|
+
coreStackPath,
|
|
2860
|
+
githubOidcStack: githubStackPath ? JSON.parse(await readFile5(githubStackPath, "utf8")) : void 0,
|
|
2861
|
+
githubOidcStackPath: githubStackPath,
|
|
2862
|
+
runner
|
|
2863
|
+
});
|
|
2864
|
+
printBootstrap(
|
|
2865
|
+
handoff,
|
|
2866
|
+
`${JSON.stringify(handoff, null, 2)}
|
|
2867
|
+
${formatBootstrapRemediationText(handoff.remediation).join("\n")}`
|
|
2747
2868
|
);
|
|
2748
2869
|
} else if (action === "verify") {
|
|
2749
2870
|
requiredOption("--config");
|
|
2750
2871
|
const coreStackPath = requiredOption("--core-stack-json");
|
|
2751
2872
|
const githubStackPath = requiredOption("--github-oidc-stack-json");
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2873
|
+
const verified = await verifyBootstrap({
|
|
2874
|
+
config,
|
|
2875
|
+
coreStack: JSON.parse(await readFile5(coreStackPath, "utf8")),
|
|
2876
|
+
githubOidcStack: JSON.parse(await readFile5(githubStackPath, "utf8"))
|
|
2877
|
+
});
|
|
2878
|
+
printBootstrap(
|
|
2879
|
+
verified,
|
|
2880
|
+
`Bootstrap verified.
|
|
2881
|
+
${formatBootstrapRemediationText(verified.remediation).join("\n")}`
|
|
2760
2882
|
);
|
|
2761
2883
|
} else if (action === "template") {
|
|
2762
2884
|
const kind = bootstrapKind();
|
|
2763
2885
|
const mode = requireComputeMode(config, option("--mode"));
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2886
|
+
const template = {
|
|
2887
|
+
kind,
|
|
2888
|
+
mode,
|
|
2889
|
+
template: await bootstrapTemplatePath(kind, mode),
|
|
2890
|
+
contractVersion: bootstrapContractVersion(await bootstrapTemplate(kind, mode)),
|
|
2891
|
+
remediation: bootstrapRemediation(config, {
|
|
2892
|
+
nextOperation: `Review the ${kind} template, then run bootstrap parameters and create a reviewed change set.`
|
|
2770
2893
|
})
|
|
2894
|
+
};
|
|
2895
|
+
printBootstrap(
|
|
2896
|
+
template,
|
|
2897
|
+
`Template: ${template.template}
|
|
2898
|
+
Contract: ${template.contractVersion}
|
|
2899
|
+
${formatBootstrapRemediationText(template.remediation).join("\n")}`
|
|
2771
2900
|
);
|
|
2772
2901
|
} else if (action === "plan") {
|
|
2773
2902
|
requiredOption("--config");
|
|
2774
2903
|
const plan = await bootstrapPlan(config);
|
|
2775
|
-
if (
|
|
2904
|
+
if (outputFormat() === "text") {
|
|
2776
2905
|
console.log("AWS bootstrap plan (read-only)");
|
|
2777
2906
|
console.log(`Package version: ${plan.packageVersion}`);
|
|
2778
2907
|
for (const template of plan.packageTemplates) {
|
|
@@ -2788,21 +2917,18 @@ ${template.kind}: ${template.path}`);
|
|
|
2788
2917
|
}
|
|
2789
2918
|
console.log(`
|
|
2790
2919
|
${plan.notes.join("\n")}`);
|
|
2920
|
+
console.log(formatBootstrapRemediationText(plan.remediation).join("\n"));
|
|
2791
2921
|
} else console.log(JSON.stringify(plan));
|
|
2792
2922
|
} else if (action === "check") {
|
|
2793
2923
|
requiredOption("--config");
|
|
2794
|
-
const { execFile: execFile3 } = await import("node:child_process");
|
|
2795
|
-
const { promisify: promisify3 } = await import("node:util");
|
|
2796
2924
|
const result = await checkBootstrap(
|
|
2797
2925
|
config,
|
|
2798
2926
|
{ resourceDrift: args.includes("--resource-drift") },
|
|
2799
2927
|
{
|
|
2800
|
-
|
|
2801
|
-
return (await promisify3(execFile3)(command, commandArgs)).stdout.trim();
|
|
2802
|
-
}
|
|
2928
|
+
execute: runner.execute
|
|
2803
2929
|
}
|
|
2804
2930
|
);
|
|
2805
|
-
if (
|
|
2931
|
+
if (outputFormat() === "text") process.stdout.write(formatBootstrapCheckText(result));
|
|
2806
2932
|
else console.log(JSON.stringify(result));
|
|
2807
2933
|
if (!result.ok) process.exitCode = 1;
|
|
2808
2934
|
} else if (action === "parameters") {
|
|
@@ -2840,8 +2966,16 @@ ${plan.notes.join("\n")}`);
|
|
|
2840
2966
|
}
|
|
2841
2967
|
await writeFile3(output, `${JSON.stringify(parameters, null, 2)}
|
|
2842
2968
|
`);
|
|
2843
|
-
|
|
2844
|
-
|
|
2969
|
+
const remediation = (await bootstrapPlan(config)).remediation;
|
|
2970
|
+
printBootstrap(
|
|
2971
|
+
{
|
|
2972
|
+
output,
|
|
2973
|
+
kind,
|
|
2974
|
+
preservedDeployedValues: Boolean(deployedStackPath),
|
|
2975
|
+
remediation
|
|
2976
|
+
},
|
|
2977
|
+
`Wrote ${output}. Next: review and execute the generated ${kind} change set.
|
|
2978
|
+
${formatBootstrapRemediationText(remediation).join("\n")}`
|
|
2845
2979
|
);
|
|
2846
2980
|
} else if (action === "change-set") {
|
|
2847
2981
|
const kind = bootstrapKind();
|
|
@@ -2871,17 +3005,22 @@ ${plan.notes.join("\n")}`);
|
|
|
2871
3005
|
...region2 ? ["--region", region2] : [],
|
|
2872
3006
|
"--no-cli-pager"
|
|
2873
3007
|
];
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
3008
|
+
const handoff = {
|
|
3009
|
+
kind,
|
|
3010
|
+
packageVersion: plan.packageVersion,
|
|
3011
|
+
contractVersion: template?.contractVersion,
|
|
3012
|
+
templateRevision: template?.templateRevision,
|
|
3013
|
+
templateSha256: template?.sha256,
|
|
3014
|
+
reviewRequired: true,
|
|
3015
|
+
awsCommand,
|
|
3016
|
+
shellCommand: args.includes("--format-shell") ? shellCommand(awsCommand) : void 0,
|
|
3017
|
+
remediation: plan.remediation
|
|
3018
|
+
};
|
|
3019
|
+
printBootstrap(
|
|
3020
|
+
handoff,
|
|
3021
|
+
`Run the reviewed change set command:
|
|
3022
|
+
${shellCommand(awsCommand)}`,
|
|
3023
|
+
shellCommand(awsCommand)
|
|
2885
3024
|
);
|
|
2886
3025
|
} else {
|
|
2887
3026
|
throw new Error(
|
package/dist/doctor.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type BootstrapRemediation } from './remediation.js';
|
|
1
2
|
export type DoctorCheck = {
|
|
2
3
|
name: string;
|
|
3
4
|
ok: boolean;
|
|
4
5
|
detail: string;
|
|
5
6
|
warning?: boolean;
|
|
7
|
+
remediation?: BootstrapRemediation;
|
|
6
8
|
};
|
|
7
9
|
export type DoctorDependencies = {
|
|
8
10
|
execute(command: string, args: string[]): Promise<string>;
|
package/dist/guided.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type BootstrapConfig, type ComputeMode } from './bootstrap.js';
|
|
1
|
+
import { type BootstrapConfig, bootstrapPlan, type ComputeMode } from './bootstrap.js';
|
|
2
2
|
import { type BootstrapProvider, bootstrapHandoff, type CommandRunner } from './handoff.js';
|
|
3
|
+
import { type BootstrapRemediation } from './remediation.js';
|
|
3
4
|
export type BootstrapCheckStatus = 'ready' | 'missing' | 'mismatch' | 'unverified';
|
|
4
5
|
export type BootstrapPreflightCheck = {
|
|
5
6
|
name: string;
|
|
@@ -9,6 +10,12 @@ export type BootstrapPreflightCheck = {
|
|
|
9
10
|
export type BootstrapPreflight = {
|
|
10
11
|
ready: boolean;
|
|
11
12
|
checks: BootstrapPreflightCheck[];
|
|
13
|
+
remediation: BootstrapRemediation;
|
|
14
|
+
};
|
|
15
|
+
export type BootstrapGuideReport = {
|
|
16
|
+
handoff: Awaited<ReturnType<typeof bootstrapHandoff>>;
|
|
17
|
+
plan: Awaited<ReturnType<typeof bootstrapPlan>>;
|
|
18
|
+
guide: string;
|
|
12
19
|
};
|
|
13
20
|
export type BootstrapInitInput = {
|
|
14
21
|
mode?: ComputeMode;
|
|
@@ -31,4 +38,5 @@ export declare function bootstrapPreflight(input: {
|
|
|
31
38
|
runner?: CommandRunner;
|
|
32
39
|
provider?: BootstrapProvider;
|
|
33
40
|
}): Promise<BootstrapPreflight>;
|
|
41
|
+
export declare function bootstrapGuideReport(input: Parameters<typeof bootstrapHandoff>[0]): Promise<BootstrapGuideReport>;
|
|
34
42
|
export declare function bootstrapGuide(input: Parameters<typeof bootstrapHandoff>[0]): Promise<string>;
|
package/dist/handoff.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type BootstrapConfig } from './bootstrap.js';
|
|
2
|
+
import { type BootstrapRemediation } from './remediation.js';
|
|
2
3
|
export type BootstrapPhase = 'core' | 'github-oidc' | 'github-environment' | 'application-gateway';
|
|
3
4
|
export type CommandRunner = {
|
|
4
5
|
execute(command: string, args: string[]): Promise<string>;
|
|
@@ -30,6 +31,7 @@ export type BootstrapHandoff = {
|
|
|
30
31
|
reviewCheckpoints: string[];
|
|
31
32
|
commands: HandoffCommand[];
|
|
32
33
|
prerequisite?: ProviderPrerequisite;
|
|
34
|
+
remediation: BootstrapRemediation;
|
|
33
35
|
};
|
|
34
36
|
export type BootstrapVerification = {
|
|
35
37
|
verified: true;
|
|
@@ -43,6 +45,7 @@ export type BootstrapVerification = {
|
|
|
43
45
|
cloudFormationExecutionRoleArn: string;
|
|
44
46
|
};
|
|
45
47
|
githubEnvironmentInstructions: string[][];
|
|
48
|
+
remediation: BootstrapRemediation;
|
|
46
49
|
};
|
|
47
50
|
export declare const githubOidcProvider: BootstrapProvider;
|
|
48
51
|
export declare function bootstrapHandoff(input: {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ComputeMode } from './bootstrap.js';
|
|
2
2
|
export { type BootstrapConfig, type BootstrapConsistency, type BootstrapKind, type BootstrapPlan, type BootstrapTemplateInspection, bootstrapConsistency, bootstrapContractVersion, bootstrapPlan, bootstrapTemplate, bootstrapTemplatePath, bootstrapTemplateRevision, type CloudFormationParameterValue, type ComputeMode, computeMode, coreBootstrapOutputs, type DeployedBootstrapStack, deployedBootstrapStack, mergeBootstrapParameters, requireComputeMode, requiredBootstrapParameters, resolveComputeMode, } from './bootstrap.js';
|
|
3
3
|
export { type BootstrapCheck, type BootstrapCheckDependencies, type BootstrapResourceDifference, type BootstrapResourceDrift, type BootstrapResourceDriftResult, type BootstrapStackCheck, checkBootstrap, formatBootstrapCheckText, } from './bootstrap-check.js';
|
|
4
|
-
export { type BootstrapCheckStatus, type BootstrapInitInput, type BootstrapPreflight, type BootstrapPreflightCheck, bootstrapGuide, bootstrapPreflight, scaffoldBootstrapManifest, } from './guided.js';
|
|
4
|
+
export { type BootstrapCheckStatus, type BootstrapGuideReport, type BootstrapInitInput, type BootstrapPreflight, type BootstrapPreflightCheck, bootstrapGuide, bootstrapGuideReport, bootstrapPreflight, scaffoldBootstrapManifest, } from './guided.js';
|
|
5
5
|
export { type BootstrapHandoff, type BootstrapPhase, type BootstrapProvider, type BootstrapVerification, bootstrapHandoff, type CommandRunner, githubOidcProvider, verifyBootstrap, } from './handoff.js';
|
|
6
|
+
export { type BootstrapAffectedStack, type BootstrapRemediation, type BootstrapRemediationInput, bootstrapRemediation, formatBootstrapRemediationText, } from './remediation.js';
|
|
6
7
|
export type ReleaseMetadata = {
|
|
7
8
|
computeMode: ComputeMode;
|
|
8
9
|
dashboardVersion: string;
|
package/dist/index.js
CHANGED
|
@@ -872,6 +872,46 @@ function computeMode(config) {
|
|
|
872
872
|
return resolveComputeMode({ persisted: config.mode });
|
|
873
873
|
}
|
|
874
874
|
|
|
875
|
+
// packages/aws/src/remediation.ts
|
|
876
|
+
var safetyNote = "AWS mutations are emitted for explicit administrator review and invocation; this library never executes them.";
|
|
877
|
+
function bootstrapRemediation(config, input = {}) {
|
|
878
|
+
const affectedStacks = input.affectedStacks ?? [
|
|
879
|
+
["core", config.core?.stackName],
|
|
880
|
+
["github-oidc", config.githubOidc?.stackName]
|
|
881
|
+
].filter((entry) => Boolean(entry[1])).map(([kind, stackName2]) => ({ kind, stackName: stackName2 }));
|
|
882
|
+
const configPath = input.configPath ?? "manifest.json";
|
|
883
|
+
const checkCommand = `ze-great-dashboard-aws bootstrap check --config ${configPath} --format json`;
|
|
884
|
+
const issues = input.issues ?? [];
|
|
885
|
+
const summary = input.summary ?? (issues.length ? `Bootstrap validation found ${issues.length} issue${issues.length === 1 ? "" : "s"}.` : "Bootstrap validation is ready for the next reviewed operation.");
|
|
886
|
+
return {
|
|
887
|
+
failureSummary: summary,
|
|
888
|
+
affectedStacks,
|
|
889
|
+
immediateSteps: [
|
|
890
|
+
...issues.length ? ["Review each reported mismatch, access error, or drift finding."] : [],
|
|
891
|
+
input.nextOperation ?? "Run the next bootstrap operation from the generated handoff.",
|
|
892
|
+
"Capture the resulting CloudFormation stack output for the next validation step."
|
|
893
|
+
],
|
|
894
|
+
upgradeSteps: [
|
|
895
|
+
"If the contract or template revision is stale, generate parameters from this installed package and create a reviewed UPDATE change set for each affected stack.",
|
|
896
|
+
"Review IAM actions, retained resources, parameters, and the change set before an administrator executes it."
|
|
897
|
+
],
|
|
898
|
+
revalidateCommand: checkCommand,
|
|
899
|
+
safetyNote
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function formatBootstrapRemediationText(remediation) {
|
|
903
|
+
return [
|
|
904
|
+
`Remediation: ${remediation.failureSummary}`,
|
|
905
|
+
`Affected stacks: ${remediation.affectedStacks.length ? remediation.affectedStacks.map(({ kind, stackName: stackName2, issue }) => `${kind}=${stackName2}${issue ? ` (${issue})` : ""}`).join(", ") : "none identified"}`,
|
|
906
|
+
"Immediate steps:",
|
|
907
|
+
...remediation.immediateSteps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
908
|
+
"Upgrade steps:",
|
|
909
|
+
...remediation.upgradeSteps.map((step, index) => ` ${index + 1}. ${step}`),
|
|
910
|
+
`Revalidate: ${remediation.revalidateCommand}`,
|
|
911
|
+
`Safety: ${remediation.safetyNote}`
|
|
912
|
+
];
|
|
913
|
+
}
|
|
914
|
+
|
|
875
915
|
// packages/aws/src/bootstrap.ts
|
|
876
916
|
var templates = {
|
|
877
917
|
core: {
|
|
@@ -944,7 +984,10 @@ async function bootstrapPlan(config) {
|
|
|
944
984
|
"Templates are owned by the installed npm package; package.json and package-lock.json pin the source version.",
|
|
945
985
|
"Generated CloudFormation parameter files and describe-stacks captures are deployment artifacts, not source configuration.",
|
|
946
986
|
"This plan performs no AWS or GitHub mutations."
|
|
947
|
-
]
|
|
987
|
+
],
|
|
988
|
+
remediation: bootstrapRemediation(config, {
|
|
989
|
+
nextOperation: "Review the installed templates, then run bootstrap preflight before creating a change set."
|
|
990
|
+
})
|
|
948
991
|
};
|
|
949
992
|
}
|
|
950
993
|
function capturedStack(input) {
|
|
@@ -1105,6 +1148,7 @@ function formatBootstrapCheckText(result) {
|
|
|
1105
1148
|
);
|
|
1106
1149
|
}
|
|
1107
1150
|
}
|
|
1151
|
+
lines.push("", ...formatBootstrapRemediationText(result.remediation));
|
|
1108
1152
|
return `${lines.join("\n")}
|
|
1109
1153
|
`;
|
|
1110
1154
|
}
|
|
@@ -1324,12 +1368,30 @@ async function checkBootstrap(config, options, dependencies) {
|
|
|
1324
1368
|
return checked;
|
|
1325
1369
|
})
|
|
1326
1370
|
);
|
|
1371
|
+
const failed = stacks.filter(
|
|
1372
|
+
({ consistency, resourceDrift: resourceDrift2 }) => !consistency.ok || Boolean(resourceDrift2 && !resourceDrift2.ok)
|
|
1373
|
+
);
|
|
1327
1374
|
return {
|
|
1328
1375
|
ok: stacks.every(
|
|
1329
1376
|
({ consistency, resourceDrift: drift }) => consistency.ok && (!drift || drift.ok)
|
|
1330
1377
|
),
|
|
1331
1378
|
packageVersion: plan.packageVersion,
|
|
1332
|
-
stacks
|
|
1379
|
+
stacks,
|
|
1380
|
+
remediation: bootstrapRemediation(config, {
|
|
1381
|
+
summary: failed.length ? `Bootstrap validation failed for ${failed.length} stack${failed.length === 1 ? "" : "s"}.` : "Bootstrap stacks are consistent and ready for the deployment check.",
|
|
1382
|
+
affectedStacks: (failed.length ? failed : stacks).map(
|
|
1383
|
+
({ kind, stackName: stackName2, consistency, resourceDrift: resourceDrift2 }) => ({
|
|
1384
|
+
kind,
|
|
1385
|
+
stackName: stackName2,
|
|
1386
|
+
...!consistency.ok ? { issue: consistency.mismatches[0] ?? "consistency mismatch" } : resourceDrift2 && !resourceDrift2.ok ? { issue: `resource drift: ${resourceDrift2.status}` } : {}
|
|
1387
|
+
})
|
|
1388
|
+
),
|
|
1389
|
+
nextOperation: failed.length ? "Apply only reviewed administrator-approved bootstrap updates, then capture both stacks again." : "Run the deployment or release check that consumes these bootstrap outputs.",
|
|
1390
|
+
issues: failed.flatMap(({ consistency, resourceDrift: resourceDrift2 }) => [
|
|
1391
|
+
...consistency.mismatches,
|
|
1392
|
+
...resourceDrift2 && !resourceDrift2.ok ? [`resource drift: ${resourceDrift2.status}`] : []
|
|
1393
|
+
])
|
|
1394
|
+
})
|
|
1333
1395
|
};
|
|
1334
1396
|
}
|
|
1335
1397
|
|
|
@@ -1546,6 +1608,10 @@ async function bootstrapHandoff(input) {
|
|
|
1546
1608
|
templatePath,
|
|
1547
1609
|
parameterPath: workFile(input.workDir, "core-bootstrap.json"),
|
|
1548
1610
|
workDir: input.workDir
|
|
1611
|
+
}),
|
|
1612
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1613
|
+
configPath: input.configPath,
|
|
1614
|
+
nextOperation: "Review and execute the core bootstrap change set, then capture the core stack."
|
|
1549
1615
|
})
|
|
1550
1616
|
};
|
|
1551
1617
|
}
|
|
@@ -1589,7 +1655,11 @@ async function bootstrapHandoff(input) {
|
|
|
1589
1655
|
prerequisite: await (input.provider ?? githubOidcProvider).prerequisite(
|
|
1590
1656
|
input.config,
|
|
1591
1657
|
input.runner
|
|
1592
|
-
)
|
|
1658
|
+
),
|
|
1659
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1660
|
+
configPath: input.configPath,
|
|
1661
|
+
nextOperation: "Review and execute the GitHub OIDC change set, then capture the GitHub OIDC stack."
|
|
1662
|
+
})
|
|
1593
1663
|
};
|
|
1594
1664
|
}
|
|
1595
1665
|
const prerequisite = await (input.provider ?? githubOidcProvider).prerequisite(
|
|
@@ -1607,7 +1677,12 @@ async function bootstrapHandoff(input) {
|
|
|
1607
1677
|
"A GitHub administrator must complete and verify the immutable-subject migration before deployments."
|
|
1608
1678
|
],
|
|
1609
1679
|
commands: [],
|
|
1610
|
-
prerequisite
|
|
1680
|
+
prerequisite,
|
|
1681
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1682
|
+
configPath: input.configPath,
|
|
1683
|
+
summary: "The GitHub Environment immutable-subject prerequisite is not verified.",
|
|
1684
|
+
nextOperation: "A GitHub administrator must complete and verify the immutable-subject migration, then rerun bootstrap handoff."
|
|
1685
|
+
})
|
|
1611
1686
|
};
|
|
1612
1687
|
return {
|
|
1613
1688
|
phase: "application-gateway",
|
|
@@ -1618,7 +1693,11 @@ async function bootstrapHandoff(input) {
|
|
|
1618
1693
|
"Consumer owns gateway selection, private Lambda permission, authentication, and smoke tests."
|
|
1619
1694
|
],
|
|
1620
1695
|
commands: [],
|
|
1621
|
-
prerequisite
|
|
1696
|
+
prerequisite,
|
|
1697
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1698
|
+
configPath: input.configPath,
|
|
1699
|
+
nextOperation: "Configure and verify the consumer gateway, then run bootstrap verify and the deployment check."
|
|
1700
|
+
})
|
|
1622
1701
|
};
|
|
1623
1702
|
}
|
|
1624
1703
|
function assertEqual(actual, expected, label) {
|
|
@@ -1738,7 +1817,10 @@ async function verifyBootstrap(input) {
|
|
|
1738
1817
|
"--body",
|
|
1739
1818
|
executionRole
|
|
1740
1819
|
]
|
|
1741
|
-
]
|
|
1820
|
+
],
|
|
1821
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1822
|
+
nextOperation: "Set the reviewed GitHub Environment variables, then run bootstrap check before deployment."
|
|
1823
|
+
})
|
|
1742
1824
|
};
|
|
1743
1825
|
}
|
|
1744
1826
|
|
|
@@ -1856,7 +1938,16 @@ async function bootstrapPreflight(input) {
|
|
|
1856
1938
|
checks.push(
|
|
1857
1939
|
missing.length ? { name: "manifest", status: "missing", detail: `Missing: ${missing.join(", ")}` } : { name: "manifest", status: "ready", detail: "Manifest has all bootstrap fields." }
|
|
1858
1940
|
);
|
|
1859
|
-
if (missing.length)
|
|
1941
|
+
if (missing.length)
|
|
1942
|
+
return {
|
|
1943
|
+
ready: false,
|
|
1944
|
+
checks,
|
|
1945
|
+
remediation: bootstrapRemediation(input.config, {
|
|
1946
|
+
summary: "Bootstrap manifest configuration is incomplete.",
|
|
1947
|
+
issues: missing,
|
|
1948
|
+
nextOperation: "Complete the missing manifest fields, then rerun bootstrap preflight."
|
|
1949
|
+
})
|
|
1950
|
+
};
|
|
1860
1951
|
const runner = input.runner;
|
|
1861
1952
|
const identityRaw = await optional(runner, "aws", [
|
|
1862
1953
|
"sts",
|
|
@@ -1964,17 +2055,22 @@ async function bootstrapPreflight(input) {
|
|
|
1964
2055
|
status: subject.status === "immutable-subject-required" ? "mismatch" : subject.status,
|
|
1965
2056
|
detail: subject.detail
|
|
1966
2057
|
});
|
|
2058
|
+
const ready = !checks.some(({ status }) => status === "missing" || status === "mismatch");
|
|
1967
2059
|
return {
|
|
1968
|
-
ready
|
|
1969
|
-
checks
|
|
2060
|
+
ready,
|
|
2061
|
+
checks,
|
|
2062
|
+
remediation: bootstrapRemediation(input.config, {
|
|
2063
|
+
summary: ready ? "Bootstrap preflight is ready." : "Bootstrap preflight found configuration or identity issues.",
|
|
2064
|
+
issues: checks.filter(({ status }) => status === "missing" || status === "mismatch").map(({ detail }) => detail),
|
|
2065
|
+
nextOperation: ready ? "Run bootstrap plan, then create and review the next bootstrap change set." : "Resolve the reported configuration or identity issues, then rerun bootstrap preflight."
|
|
2066
|
+
})
|
|
1970
2067
|
};
|
|
1971
2068
|
}
|
|
1972
2069
|
function quote(args) {
|
|
1973
2070
|
return args.map((arg) => `'${arg.replaceAll("'", `'\\"'\\"'`)}'`).join(" ");
|
|
1974
2071
|
}
|
|
1975
|
-
async function
|
|
1976
|
-
const handoff = await bootstrapHandoff(input);
|
|
1977
|
-
const plan = await bootstrapPlan(input.config);
|
|
2072
|
+
async function bootstrapGuideReport(input) {
|
|
2073
|
+
const [handoff, plan] = await Promise.all([bootstrapHandoff(input), bootstrapPlan(input.config)]);
|
|
1978
2074
|
const lines = [
|
|
1979
2075
|
`Phase: ${handoff.phase}`,
|
|
1980
2076
|
`Package version: ${plan.packageVersion}`,
|
|
@@ -2011,8 +2107,12 @@ async function bootstrapGuide(input) {
|
|
|
2011
2107
|
);
|
|
2012
2108
|
for (const command of verified.githubEnvironmentInstructions) lines.push(quote(command));
|
|
2013
2109
|
}
|
|
2014
|
-
|
|
2015
|
-
|
|
2110
|
+
lines.push("", ...formatBootstrapRemediationText(plan.remediation));
|
|
2111
|
+
return { handoff, plan, guide: `${lines.join("\n")}
|
|
2112
|
+
` };
|
|
2113
|
+
}
|
|
2114
|
+
async function bootstrapGuide(input) {
|
|
2115
|
+
return (await bootstrapGuideReport(input)).guide;
|
|
2016
2116
|
}
|
|
2017
2117
|
|
|
2018
2118
|
// packages/aws/src/index.ts
|
|
@@ -2190,9 +2290,11 @@ export {
|
|
|
2190
2290
|
bootstrapConsistency,
|
|
2191
2291
|
bootstrapContractVersion,
|
|
2192
2292
|
bootstrapGuide,
|
|
2293
|
+
bootstrapGuideReport,
|
|
2193
2294
|
bootstrapHandoff,
|
|
2194
2295
|
bootstrapPlan,
|
|
2195
2296
|
bootstrapPreflight,
|
|
2297
|
+
bootstrapRemediation,
|
|
2196
2298
|
bootstrapTemplate,
|
|
2197
2299
|
bootstrapTemplatePath,
|
|
2198
2300
|
bootstrapTemplateRevision,
|
|
@@ -2202,6 +2304,7 @@ export {
|
|
|
2202
2304
|
coreBootstrapOutputs,
|
|
2203
2305
|
deployedBootstrapStack,
|
|
2204
2306
|
formatBootstrapCheckText,
|
|
2307
|
+
formatBootstrapRemediationText,
|
|
2205
2308
|
githubOidcProvider,
|
|
2206
2309
|
mergeBootstrapParameters,
|
|
2207
2310
|
packageEcs,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { BootstrapConfig, BootstrapKind } from './bootstrap.js';
|
|
2
|
+
export type BootstrapAffectedStack = {
|
|
3
|
+
kind: BootstrapKind;
|
|
4
|
+
stackName: string;
|
|
5
|
+
issue?: string;
|
|
6
|
+
};
|
|
7
|
+
export type BootstrapRemediation = {
|
|
8
|
+
failureSummary: string;
|
|
9
|
+
affectedStacks: BootstrapAffectedStack[];
|
|
10
|
+
immediateSteps: string[];
|
|
11
|
+
upgradeSteps: string[];
|
|
12
|
+
revalidateCommand: string;
|
|
13
|
+
safetyNote: string;
|
|
14
|
+
};
|
|
15
|
+
export type BootstrapRemediationInput = {
|
|
16
|
+
summary?: string;
|
|
17
|
+
affectedStacks?: BootstrapAffectedStack[];
|
|
18
|
+
configPath?: string;
|
|
19
|
+
nextOperation?: string;
|
|
20
|
+
issues?: string[];
|
|
21
|
+
};
|
|
22
|
+
export declare function bootstrapRemediation(config: BootstrapConfig, input?: BootstrapRemediationInput): BootstrapRemediation;
|
|
23
|
+
export declare function formatBootstrapRemediationText(remediation: BootstrapRemediation): string[];
|