@kungfu-tech/buildchain 3.0.4-alpha.2 → 3.0.4-alpha.3
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/bin/buildchain.mjs +126 -72
- package/bin/internal/trust-release-cli.mjs +15 -537
- package/bin/internal/trust-release-command-handlers.mjs +14 -0
- package/bin/internal/trust-release-inspection-handlers.mjs +175 -0
- package/bin/internal/trust-release-release-handlers.mjs +317 -0
- package/bin/internal/trust-release-verification-handlers.mjs +306 -0
- package/dist/site/buildchain-contract.json +35 -24
- package/dist/site/buildchain-site.json +22 -10
- package/dist/site/controller-registry.json +16 -3
- package/dist/site/kfd-claims.json +9 -7
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +2 -2
- package/dist/site/node-api-registry.json +7 -7
- package/dist/site/page-registry.json +9 -4
- package/dist/site/public-surface-audit.json +56 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/release-model.json +7 -0
- package/dist/site/site-manifest.json +6 -6
- package/dist/site/workflow-registry.json +13 -5
- package/docs/MAP.md +1 -1
- package/docs/release-propagation.md +166 -8
- package/package.json +1 -1
- package/packages/core/buildchain-kfd-claims.js +1 -1
- package/packages/core/controller-evidence.js +8 -1
- package/packages/core/index.js +1 -13
- package/packages/core/paper-npm-bootstrap.js +492 -0
- package/packages/core/paper.js +271 -669
- package/packages/core/public-surface-cli.js +12 -1
- package/packages/core/release-passport.js +67 -71
- package/packages/core/release-propagation-common.js +64 -0
- package/packages/core/release-propagation-execution-profile.js +59 -0
- package/packages/core/release-propagation-release.js +196 -0
- package/packages/core/release-propagation-stage-evidence.js +364 -0
- package/packages/core/release-propagation-work-capture.js +64 -0
- package/packages/core/release-propagation-work-constants.js +34 -0
- package/packages/core/release-propagation-work-control.js +203 -0
- package/packages/core/release-propagation-work-transitions.js +145 -0
- package/packages/core/release-propagation-work.js +517 -0
- package/packages/core/release-propagation.js +34 -158
- package/scripts/aws-windows-jit-controller-core.mjs +269 -0
- package/scripts/aws-windows-jit-controller.mjs +502 -0
- package/scripts/check-internal-architecture.mjs +178 -52
- package/scripts/check-maintainability.mjs +76 -17
- package/scripts/generate-site-bundle.mjs +16 -7
- package/scripts/maintainability-metrics.mjs +24 -4
- package/scripts/release-propagation.mjs +126 -0
- package/scripts/resolve-artifact-transfer-mode.mjs +117 -0
- package/scripts/web-surface-core.mjs +46 -207
- package/scripts/web-surface-routing.mjs +286 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
import {
|
|
10
|
+
AWS_WINDOWS_JIT_CONTROLLER_CONTRACT,
|
|
11
|
+
createWindowsJitLaunchPlan,
|
|
12
|
+
windowsRunInstancesArgs,
|
|
13
|
+
} from "./aws-windows-jit-controller-core.mjs";
|
|
14
|
+
import { renderWindowsJitBootstrap } from "./aws-windows-jit-core.mjs";
|
|
15
|
+
|
|
16
|
+
function arg(name, fallback = "") {
|
|
17
|
+
const index = process.argv.indexOf(`--${name}`);
|
|
18
|
+
return index === -1 ? fallback : process.argv[index + 1] || "";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function flag(name) {
|
|
22
|
+
return process.argv.includes(`--${name}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function commandResult(command, args, options = {}) {
|
|
26
|
+
const result = spawnSync(command, args, {
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
29
|
+
stdio: [options.input === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
30
|
+
input: options.input,
|
|
31
|
+
env: process.env,
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
status: result.status,
|
|
35
|
+
stdout: String(result.stdout || ""),
|
|
36
|
+
stderr: String(result.stderr || result.error?.message || ""),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function requireSuccess(result, label) {
|
|
41
|
+
if (result.status !== 0) {
|
|
42
|
+
const detail = (result.stderr || result.stdout).trim().slice(0, 2000);
|
|
43
|
+
throw new Error(`${label} failed${detail ? `: ${detail}` : ""}`);
|
|
44
|
+
}
|
|
45
|
+
return result.stdout;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function jsonResult(result, label) {
|
|
49
|
+
const output = requireSuccess(result, label);
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(output);
|
|
52
|
+
} catch {
|
|
53
|
+
throw new Error(`${label} returned invalid JSON`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function awsArgs(plan, profile, serviceArgs) {
|
|
58
|
+
return [
|
|
59
|
+
...(profile ? ["--profile", profile] : []),
|
|
60
|
+
"--region",
|
|
61
|
+
plan.aws.region,
|
|
62
|
+
...serviceArgs,
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ghJson(args, input, label) {
|
|
67
|
+
return jsonResult(commandResult("gh", args, { input }), label);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function assertLivePreflight(plan, profile) {
|
|
71
|
+
const run = ghJson(
|
|
72
|
+
["api", `repos/${plan.repository}/actions/runs/${plan.github.runId}`],
|
|
73
|
+
undefined,
|
|
74
|
+
"GitHub run preflight",
|
|
75
|
+
);
|
|
76
|
+
if (
|
|
77
|
+
run.event !== plan.github.event ||
|
|
78
|
+
run.head_sha !== plan.source.sha ||
|
|
79
|
+
run.head_repository?.full_name !== plan.repository ||
|
|
80
|
+
!["queued", "in_progress"].includes(run.status)
|
|
81
|
+
) {
|
|
82
|
+
throw new Error("GitHub run is not the trusted exact-source dispatch");
|
|
83
|
+
}
|
|
84
|
+
const job = ghJson(
|
|
85
|
+
["api", `repos/${plan.repository}/actions/jobs/${plan.github.jobId}`],
|
|
86
|
+
undefined,
|
|
87
|
+
"GitHub job preflight",
|
|
88
|
+
);
|
|
89
|
+
if (job.status !== "queued") {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`GitHub job must be queued, observed ${job.status || "unknown"}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (
|
|
95
|
+
Array.isArray(job.labels) &&
|
|
96
|
+
!plan.runner.labels.every((label) => job.labels.includes(label))
|
|
97
|
+
) {
|
|
98
|
+
throw new Error("GitHub job labels do not match the JIT launch plan");
|
|
99
|
+
}
|
|
100
|
+
const active = jsonResult(
|
|
101
|
+
commandResult(
|
|
102
|
+
"aws",
|
|
103
|
+
awsArgs(plan, profile, [
|
|
104
|
+
"ec2",
|
|
105
|
+
"describe-instances",
|
|
106
|
+
"--filters",
|
|
107
|
+
"Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
|
|
108
|
+
"Name=tag:kungfu:provider,Values=windows-ec2-jit",
|
|
109
|
+
"Name=instance-state-name,Values=pending,running,stopping,stopped,shutting-down",
|
|
110
|
+
"--output",
|
|
111
|
+
"json",
|
|
112
|
+
]),
|
|
113
|
+
),
|
|
114
|
+
"active Windows JIT instance preflight",
|
|
115
|
+
);
|
|
116
|
+
const activeInstances = (active.Reservations || []).flatMap(
|
|
117
|
+
(reservation) => reservation.Instances || [],
|
|
118
|
+
);
|
|
119
|
+
if (activeInstances.length >= plan.safety.activeInstanceCeiling) {
|
|
120
|
+
throw new Error("Windows JIT active instance ceiling is already reached");
|
|
121
|
+
}
|
|
122
|
+
const parameters = jsonResult(
|
|
123
|
+
commandResult(
|
|
124
|
+
"aws",
|
|
125
|
+
awsArgs(plan, profile, [
|
|
126
|
+
"ssm",
|
|
127
|
+
"describe-parameters",
|
|
128
|
+
"--parameter-filters",
|
|
129
|
+
`Key=Name,Option=Equals,Values=${plan.aws.jitParameterName}`,
|
|
130
|
+
"--output",
|
|
131
|
+
"json",
|
|
132
|
+
]),
|
|
133
|
+
),
|
|
134
|
+
"Windows JIT parameter preflight",
|
|
135
|
+
);
|
|
136
|
+
if ((parameters.Parameters || []).length !== 0) {
|
|
137
|
+
throw new Error("Windows JIT parameter already exists");
|
|
138
|
+
}
|
|
139
|
+
const image = jsonResult(
|
|
140
|
+
commandResult(
|
|
141
|
+
"aws",
|
|
142
|
+
awsArgs(plan, profile, [
|
|
143
|
+
"ec2",
|
|
144
|
+
"describe-images",
|
|
145
|
+
"--image-ids",
|
|
146
|
+
plan.aws.amiId,
|
|
147
|
+
"--output",
|
|
148
|
+
"json",
|
|
149
|
+
]),
|
|
150
|
+
),
|
|
151
|
+
"Windows AMI preflight",
|
|
152
|
+
).Images?.[0];
|
|
153
|
+
if (
|
|
154
|
+
!image ||
|
|
155
|
+
image.Name !== plan.aws.amiName ||
|
|
156
|
+
image.Architecture !== "x86_64" ||
|
|
157
|
+
image.State !== "available"
|
|
158
|
+
) {
|
|
159
|
+
throw new Error("Windows AMI identity or availability mismatch");
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
runStatus: run.status,
|
|
163
|
+
jobStatus: job.status,
|
|
164
|
+
activeInstances: activeInstances.length,
|
|
165
|
+
amiOwnerId: image.OwnerId,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function deleteRunnerRegistration(plan) {
|
|
170
|
+
const runners = ghJson(
|
|
171
|
+
["api", `repos/${plan.repository}/actions/runners?per_page=100`],
|
|
172
|
+
undefined,
|
|
173
|
+
"GitHub runner cleanup lookup",
|
|
174
|
+
).runners;
|
|
175
|
+
const runner = (runners || []).find(
|
|
176
|
+
(entry) => entry.name === plan.runner.name,
|
|
177
|
+
);
|
|
178
|
+
if (!runner) return false;
|
|
179
|
+
requireSuccess(
|
|
180
|
+
commandResult("gh", [
|
|
181
|
+
"api",
|
|
182
|
+
"--method",
|
|
183
|
+
"DELETE",
|
|
184
|
+
`repos/${plan.repository}/actions/runners/${runner.id}`,
|
|
185
|
+
]),
|
|
186
|
+
"GitHub runner cleanup",
|
|
187
|
+
);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function terminatePlanInstances(plan, profile) {
|
|
192
|
+
const instances = jsonResult(
|
|
193
|
+
commandResult(
|
|
194
|
+
"aws",
|
|
195
|
+
awsArgs(plan, profile, [
|
|
196
|
+
"ec2",
|
|
197
|
+
"describe-instances",
|
|
198
|
+
"--filters",
|
|
199
|
+
"Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
|
|
200
|
+
"Name=tag:kungfu:provider,Values=windows-ec2-jit",
|
|
201
|
+
`Name=tag:kungfu:github-run-id,Values=${plan.github.runId}`,
|
|
202
|
+
`Name=tag:kungfu:github-run-attempt,Values=${plan.github.runAttempt}`,
|
|
203
|
+
`Name=tag:kungfu:qualification-id,Values=${plan.github.qualificationId}`,
|
|
204
|
+
`Name=tag:kungfu:source-sha,Values=${plan.source.sha}`,
|
|
205
|
+
"Name=instance-state-name,Values=pending,running,stopping,stopped,shutting-down",
|
|
206
|
+
"--output",
|
|
207
|
+
"json",
|
|
208
|
+
]),
|
|
209
|
+
),
|
|
210
|
+
"failed-launch instance lookup",
|
|
211
|
+
);
|
|
212
|
+
const instanceIds = (instances.Reservations || [])
|
|
213
|
+
.flatMap((reservation) => reservation.Instances || [])
|
|
214
|
+
.map((instance) => instance.InstanceId)
|
|
215
|
+
.filter((instanceId) => /^i-[0-9a-f]+$/.test(String(instanceId || "")));
|
|
216
|
+
if (instanceIds.length === 0) return [];
|
|
217
|
+
requireSuccess(
|
|
218
|
+
commandResult(
|
|
219
|
+
"aws",
|
|
220
|
+
awsArgs(plan, profile, [
|
|
221
|
+
"ec2",
|
|
222
|
+
"terminate-instances",
|
|
223
|
+
"--instance-ids",
|
|
224
|
+
...instanceIds,
|
|
225
|
+
"--output",
|
|
226
|
+
"json",
|
|
227
|
+
]),
|
|
228
|
+
),
|
|
229
|
+
"failed-launch instance termination",
|
|
230
|
+
);
|
|
231
|
+
return instanceIds;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function cleanupFailedLaunch(
|
|
235
|
+
plan,
|
|
236
|
+
profile,
|
|
237
|
+
{ launchAttempted, parameterCreated },
|
|
238
|
+
) {
|
|
239
|
+
const failures = [];
|
|
240
|
+
const attempt = (cleanup) => {
|
|
241
|
+
try {
|
|
242
|
+
cleanup();
|
|
243
|
+
} catch (error) {
|
|
244
|
+
failures.push(error.message || String(error));
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
if (launchAttempted) {
|
|
248
|
+
attempt(() => terminatePlanInstances(plan, profile));
|
|
249
|
+
}
|
|
250
|
+
if (parameterCreated) {
|
|
251
|
+
attempt(() =>
|
|
252
|
+
requireSuccess(
|
|
253
|
+
commandResult(
|
|
254
|
+
"aws",
|
|
255
|
+
awsArgs(plan, profile, [
|
|
256
|
+
"ssm",
|
|
257
|
+
"delete-parameter",
|
|
258
|
+
"--name",
|
|
259
|
+
plan.aws.jitParameterName,
|
|
260
|
+
]),
|
|
261
|
+
),
|
|
262
|
+
"SSM JIT parameter cleanup",
|
|
263
|
+
),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
attempt(() => deleteRunnerRegistration(plan));
|
|
267
|
+
return failures;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function executeWindowsJitLaunch(plan, { profile = "" } = {}) {
|
|
271
|
+
if (plan?.contract !== AWS_WINDOWS_JIT_CONTROLLER_CONTRACT) {
|
|
272
|
+
throw new Error("Windows JIT launch plan contract is invalid");
|
|
273
|
+
}
|
|
274
|
+
const preflight = assertLivePreflight(plan, profile);
|
|
275
|
+
const tempRoot = fs.mkdtempSync(
|
|
276
|
+
path.join(os.tmpdir(), "buildchain-windows-jit-controller-"),
|
|
277
|
+
);
|
|
278
|
+
fs.chmodSync(tempRoot, 0o700);
|
|
279
|
+
const parameterInputPath = path.join(tempRoot, "ssm-input.json");
|
|
280
|
+
const bootstrapPath = path.join(tempRoot, "bootstrap.ps1");
|
|
281
|
+
let parameterCreated = false;
|
|
282
|
+
let launchAttempted = false;
|
|
283
|
+
let launchSucceeded = false;
|
|
284
|
+
let result;
|
|
285
|
+
let failure;
|
|
286
|
+
const cleanupFailures = [];
|
|
287
|
+
try {
|
|
288
|
+
const jit = ghJson(
|
|
289
|
+
[
|
|
290
|
+
"api",
|
|
291
|
+
"--method",
|
|
292
|
+
"POST",
|
|
293
|
+
`repos/${plan.repository}/actions/runners/generate-jitconfig`,
|
|
294
|
+
"--input",
|
|
295
|
+
"-",
|
|
296
|
+
],
|
|
297
|
+
JSON.stringify({
|
|
298
|
+
name: plan.runner.name,
|
|
299
|
+
runner_group_id: 1,
|
|
300
|
+
labels: plan.runner.labels,
|
|
301
|
+
work_folder: "_work",
|
|
302
|
+
}),
|
|
303
|
+
"GitHub JIT configuration",
|
|
304
|
+
).encoded_jit_config;
|
|
305
|
+
if (!jit || typeof jit !== "string") {
|
|
306
|
+
throw new Error("GitHub JIT configuration was empty");
|
|
307
|
+
}
|
|
308
|
+
fs.writeFileSync(
|
|
309
|
+
parameterInputPath,
|
|
310
|
+
JSON.stringify({
|
|
311
|
+
Name: plan.aws.jitParameterName,
|
|
312
|
+
Description: `One-shot GitHub Actions JIT config for ${plan.repository} run ${plan.github.runId} attempt ${plan.github.runAttempt}`,
|
|
313
|
+
Type: "SecureString",
|
|
314
|
+
Tier: "Advanced",
|
|
315
|
+
Value: jit,
|
|
316
|
+
Tags: [
|
|
317
|
+
{ Key: "kungfu:owner", Value: "buildchain" },
|
|
318
|
+
{ Key: "kungfu:plane", Value: "aws-us-elastic-runner-burst" },
|
|
319
|
+
{ Key: "kungfu:provider", Value: "windows-ec2-jit" },
|
|
320
|
+
{ Key: "kungfu:github-run-id", Value: plan.github.runId },
|
|
321
|
+
{
|
|
322
|
+
Key: "kungfu:qualification-id",
|
|
323
|
+
Value: plan.github.qualificationId,
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
}),
|
|
327
|
+
{ mode: 0o600 },
|
|
328
|
+
);
|
|
329
|
+
requireSuccess(
|
|
330
|
+
commandResult(
|
|
331
|
+
"aws",
|
|
332
|
+
awsArgs(plan, profile, [
|
|
333
|
+
"ssm",
|
|
334
|
+
"put-parameter",
|
|
335
|
+
"--cli-input-json",
|
|
336
|
+
`file://${parameterInputPath}`,
|
|
337
|
+
"--output",
|
|
338
|
+
"json",
|
|
339
|
+
]),
|
|
340
|
+
),
|
|
341
|
+
"SSM JIT parameter creation",
|
|
342
|
+
);
|
|
343
|
+
parameterCreated = true;
|
|
344
|
+
fs.writeFileSync(
|
|
345
|
+
bootstrapPath,
|
|
346
|
+
renderWindowsJitBootstrap(
|
|
347
|
+
fs.readFileSync(
|
|
348
|
+
path.resolve(
|
|
349
|
+
"infra/aws-us-elastic-runner-burst-plane/windows-jit-bootstrap.ps1",
|
|
350
|
+
),
|
|
351
|
+
"utf8",
|
|
352
|
+
),
|
|
353
|
+
{
|
|
354
|
+
region: plan.aws.region,
|
|
355
|
+
jitParameterName: plan.aws.jitParameterName,
|
|
356
|
+
evidenceBucket: plan.aws.evidenceBucket,
|
|
357
|
+
runnerLabel: plan.runner.label,
|
|
358
|
+
sourceSha: plan.source.sha,
|
|
359
|
+
githubRunId: plan.github.runId,
|
|
360
|
+
githubRunAttempt: plan.github.runAttempt,
|
|
361
|
+
amiId: plan.aws.amiId,
|
|
362
|
+
amiName: plan.aws.amiName,
|
|
363
|
+
instanceType: plan.aws.instanceType,
|
|
364
|
+
launchedAt: plan.aws.launchedAt,
|
|
365
|
+
},
|
|
366
|
+
),
|
|
367
|
+
{ mode: 0o600 },
|
|
368
|
+
);
|
|
369
|
+
const dryRun = commandResult(
|
|
370
|
+
"aws",
|
|
371
|
+
awsArgs(
|
|
372
|
+
plan,
|
|
373
|
+
profile,
|
|
374
|
+
windowsRunInstancesArgs(plan, { bootstrapPath, dryRun: true }),
|
|
375
|
+
),
|
|
376
|
+
);
|
|
377
|
+
if (
|
|
378
|
+
dryRun.status === 0 ||
|
|
379
|
+
!/DryRunOperation/.test(`${dryRun.stdout}\n${dryRun.stderr}`)
|
|
380
|
+
) {
|
|
381
|
+
throw new Error("EC2 RunInstances DryRun did not return DryRunOperation");
|
|
382
|
+
}
|
|
383
|
+
launchAttempted = true;
|
|
384
|
+
const launched = jsonResult(
|
|
385
|
+
commandResult(
|
|
386
|
+
"aws",
|
|
387
|
+
awsArgs(
|
|
388
|
+
plan,
|
|
389
|
+
profile,
|
|
390
|
+
windowsRunInstancesArgs(plan, { bootstrapPath, dryRun: false }),
|
|
391
|
+
),
|
|
392
|
+
),
|
|
393
|
+
"EC2 RunInstances",
|
|
394
|
+
);
|
|
395
|
+
const instance = launched.Instances?.[0];
|
|
396
|
+
if (!/^i-[0-9a-f]+$/.test(String(instance?.InstanceId || ""))) {
|
|
397
|
+
throw new Error("EC2 RunInstances returned no instance identity");
|
|
398
|
+
}
|
|
399
|
+
launchSucceeded = true;
|
|
400
|
+
result = {
|
|
401
|
+
schemaVersion: 1,
|
|
402
|
+
contract: AWS_WINDOWS_JIT_CONTROLLER_CONTRACT,
|
|
403
|
+
kind: "launch-result",
|
|
404
|
+
status: "launched",
|
|
405
|
+
source: plan.source,
|
|
406
|
+
github: plan.github,
|
|
407
|
+
runner: plan.runner,
|
|
408
|
+
aws: {
|
|
409
|
+
region: plan.aws.region,
|
|
410
|
+
instanceId: instance.InstanceId,
|
|
411
|
+
imageId: instance.ImageId,
|
|
412
|
+
instanceType: instance.InstanceType,
|
|
413
|
+
launchTime: instance.LaunchTime,
|
|
414
|
+
jitParameterName: plan.aws.jitParameterName,
|
|
415
|
+
},
|
|
416
|
+
preflight,
|
|
417
|
+
dryRun: "DryRunOperation",
|
|
418
|
+
planDigest: plan.digest,
|
|
419
|
+
};
|
|
420
|
+
} catch (error) {
|
|
421
|
+
failure = error;
|
|
422
|
+
} finally {
|
|
423
|
+
if (!launchSucceeded) {
|
|
424
|
+
cleanupFailures.push(
|
|
425
|
+
...cleanupFailedLaunch(plan, profile, {
|
|
426
|
+
launchAttempted,
|
|
427
|
+
parameterCreated,
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
for (const file of [parameterInputPath, bootstrapPath]) {
|
|
432
|
+
if (fs.existsSync(file)) fs.unlinkSync(file);
|
|
433
|
+
}
|
|
434
|
+
fs.rmdirSync(tempRoot);
|
|
435
|
+
}
|
|
436
|
+
if (failure) {
|
|
437
|
+
if (cleanupFailures.length > 0) {
|
|
438
|
+
throw new Error(
|
|
439
|
+
`${failure.message || failure}; cleanup failed: ${cleanupFailures.join("; ")}`,
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
throw failure;
|
|
443
|
+
}
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function planFromArgs(execute) {
|
|
448
|
+
return createWindowsJitLaunchPlan({
|
|
449
|
+
execute,
|
|
450
|
+
repository: arg("repository", "kungfu-systems/kungfu"),
|
|
451
|
+
runId: arg("run-id"),
|
|
452
|
+
runAttempt: arg("run-attempt", "1"),
|
|
453
|
+
jobId: arg("job-id"),
|
|
454
|
+
qualificationId: arg("qualification-id"),
|
|
455
|
+
runnerLabel: arg("runner-label"),
|
|
456
|
+
runnerName: arg("runner-name"),
|
|
457
|
+
sourceSha: arg("source-sha"),
|
|
458
|
+
sourceRef: arg("source-ref"),
|
|
459
|
+
region: arg("region", "us-east-1"),
|
|
460
|
+
instanceType: arg("instance-type", "c7i.4xlarge"),
|
|
461
|
+
amiId: arg("ami-id"),
|
|
462
|
+
amiName: arg("ami-name"),
|
|
463
|
+
subnetId: arg("subnet-id"),
|
|
464
|
+
securityGroupId: arg("security-group-id"),
|
|
465
|
+
instanceProfileName: arg("instance-profile-name"),
|
|
466
|
+
evidenceBucket: arg("evidence-bucket"),
|
|
467
|
+
jitParameterName: arg("jit-parameter"),
|
|
468
|
+
launchedAt: arg("launched-at", new Date().toISOString()),
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function main() {
|
|
473
|
+
const execute = flag("execute");
|
|
474
|
+
const plan = planFromArgs(execute);
|
|
475
|
+
if (!execute) {
|
|
476
|
+
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
477
|
+
return plan;
|
|
478
|
+
}
|
|
479
|
+
if (arg("confirm-source-sha") !== plan.source.sha) {
|
|
480
|
+
throw new Error("--confirm-source-sha must equal the exact source SHA");
|
|
481
|
+
}
|
|
482
|
+
if (arg("confirm-run-id") !== plan.github.runId) {
|
|
483
|
+
throw new Error("--confirm-run-id must equal the exact GitHub run id");
|
|
484
|
+
}
|
|
485
|
+
const result = executeWindowsJitLaunch(plan, {
|
|
486
|
+
profile: arg("aws-profile"),
|
|
487
|
+
});
|
|
488
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
489
|
+
return result;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (
|
|
493
|
+
process.argv[1] &&
|
|
494
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
495
|
+
) {
|
|
496
|
+
try {
|
|
497
|
+
main();
|
|
498
|
+
} catch (error) {
|
|
499
|
+
console.error(`::error::${error.message || error}`);
|
|
500
|
+
process.exitCode = 1;
|
|
501
|
+
}
|
|
502
|
+
}
|