@kungfu-tech/buildchain 3.0.4 → 3.0.5-alpha.1
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/site/buildchain-contract.json +23 -18
- package/dist/site/buildchain-site.json +29 -24
- package/dist/site/controller-registry.json +6 -2
- package/dist/site/kfd-claims.json +6 -4
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +6 -6
- package/dist/site/node-api-registry.json +96 -5
- package/dist/site/page-registry.json +19 -14
- package/dist/site/public-surface-audit.json +12 -8
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +10 -10
- package/dist/site/workflow-registry.json +10 -6
- package/docs/auditable-demo.md +7 -0
- package/docs/aws-us-elastic-runner-burst-plane.md +32 -1
- package/docs/node-api-reference.md +12 -3
- package/docs/publish-transaction.md +7 -5
- package/docs/release-flow.md +4 -0
- package/docs/release-governance.md +7 -0
- package/docs/release-propagation.md +34 -0
- package/package.json +1 -1
- package/packages/core/release-propagation-capture.js +150 -0
- package/packages/core/release-propagation-stage-evidence.js +1 -1
- package/packages/core/release-propagation.js +5 -0
- package/scripts/auditable-demo-renditions.mjs +2 -3
- package/scripts/aws-macos-jit-controller-core.mjs +389 -0
- package/scripts/aws-macos-jit-controller-runtime.mjs +82 -0
- package/scripts/aws-macos-jit-controller.mjs +558 -0
- package/scripts/aws-macos-jit-job-controller.mjs +401 -0
- package/scripts/capture-package-release-propagation.mjs +240 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import {
|
|
6
|
+
AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
7
|
+
createMacosJitCampaignPlan,
|
|
8
|
+
createMacosJitClosePlan,
|
|
9
|
+
createMacosJitJobPlan,
|
|
10
|
+
macosAllocateHostsArgs,
|
|
11
|
+
macosReleaseHostsArgs,
|
|
12
|
+
macosRunInstancesArgs,
|
|
13
|
+
} from "./aws-macos-jit-controller-core.mjs";
|
|
14
|
+
import { executeMacosJitJob } from "./aws-macos-jit-job-controller.mjs";
|
|
15
|
+
import {
|
|
16
|
+
assertDryRun,
|
|
17
|
+
assertOwnership,
|
|
18
|
+
awsArgs,
|
|
19
|
+
awsJson,
|
|
20
|
+
commandResult,
|
|
21
|
+
ghJson,
|
|
22
|
+
requireSuccess,
|
|
23
|
+
} from "./aws-macos-jit-controller-runtime.mjs";
|
|
24
|
+
|
|
25
|
+
function arg(name, fallback = "") {
|
|
26
|
+
const index = process.argv.indexOf(`--${name}`);
|
|
27
|
+
return index === -1 ? fallback : process.argv[index + 1] || "";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function flag(name) {
|
|
31
|
+
return process.argv.includes(`--${name}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function assertCampaignLaunchPreflight(plan, profile) {
|
|
35
|
+
const commit = ghJson(
|
|
36
|
+
["api", `repos/${plan.repository}/commits/${plan.source.sha}`],
|
|
37
|
+
undefined,
|
|
38
|
+
"GitHub exact-source preflight",
|
|
39
|
+
);
|
|
40
|
+
if (String(commit.sha || "").toLowerCase() !== plan.source.sha) {
|
|
41
|
+
throw new Error("GitHub did not resolve the exact campaign source SHA");
|
|
42
|
+
}
|
|
43
|
+
const activeHosts =
|
|
44
|
+
awsJson(
|
|
45
|
+
plan,
|
|
46
|
+
profile,
|
|
47
|
+
[
|
|
48
|
+
"ec2",
|
|
49
|
+
"describe-hosts",
|
|
50
|
+
"--filter",
|
|
51
|
+
"Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
|
|
52
|
+
"Name=tag:kungfu:provider,Values=macos-ec2-jit",
|
|
53
|
+
"Name=state,Values=available,pending,under-assessment",
|
|
54
|
+
"--output",
|
|
55
|
+
"json",
|
|
56
|
+
],
|
|
57
|
+
"active macOS JIT host preflight",
|
|
58
|
+
).Hosts || [];
|
|
59
|
+
if (activeHosts.length >= plan.safety.activeHostCeiling) {
|
|
60
|
+
throw new Error("macOS JIT active Dedicated Host ceiling is reached");
|
|
61
|
+
}
|
|
62
|
+
const active = awsJson(
|
|
63
|
+
plan,
|
|
64
|
+
profile,
|
|
65
|
+
[
|
|
66
|
+
"ec2",
|
|
67
|
+
"describe-instances",
|
|
68
|
+
"--filters",
|
|
69
|
+
"Name=tag:kungfu:plane,Values=aws-us-elastic-runner-burst",
|
|
70
|
+
"Name=tag:kungfu:provider,Values=macos-ec2-jit",
|
|
71
|
+
"Name=instance-state-name,Values=pending,running,stopping,stopped,shutting-down",
|
|
72
|
+
"--output",
|
|
73
|
+
"json",
|
|
74
|
+
],
|
|
75
|
+
"active macOS JIT instance preflight",
|
|
76
|
+
);
|
|
77
|
+
const instances = (active.Reservations || []).flatMap(
|
|
78
|
+
(reservation) => reservation.Instances || [],
|
|
79
|
+
);
|
|
80
|
+
if (instances.length >= plan.safety.activeInstanceCeiling) {
|
|
81
|
+
throw new Error("macOS JIT active instance ceiling is reached");
|
|
82
|
+
}
|
|
83
|
+
const image = awsJson(
|
|
84
|
+
plan,
|
|
85
|
+
profile,
|
|
86
|
+
[
|
|
87
|
+
"ec2",
|
|
88
|
+
"describe-images",
|
|
89
|
+
"--image-ids",
|
|
90
|
+
plan.aws.amiId,
|
|
91
|
+
"--output",
|
|
92
|
+
"json",
|
|
93
|
+
],
|
|
94
|
+
"macOS AMI preflight",
|
|
95
|
+
).Images?.[0];
|
|
96
|
+
if (
|
|
97
|
+
!image ||
|
|
98
|
+
image.Name !== plan.aws.amiName ||
|
|
99
|
+
image.Architecture !== "arm64_mac" ||
|
|
100
|
+
image.State !== "available"
|
|
101
|
+
) {
|
|
102
|
+
throw new Error("macOS AMI identity or availability mismatch");
|
|
103
|
+
}
|
|
104
|
+
const subnet = awsJson(
|
|
105
|
+
plan,
|
|
106
|
+
profile,
|
|
107
|
+
[
|
|
108
|
+
"ec2",
|
|
109
|
+
"describe-subnets",
|
|
110
|
+
"--subnet-ids",
|
|
111
|
+
plan.aws.subnetId,
|
|
112
|
+
"--output",
|
|
113
|
+
"json",
|
|
114
|
+
],
|
|
115
|
+
"macOS subnet preflight",
|
|
116
|
+
).Subnets?.[0];
|
|
117
|
+
if (!subnet || subnet.AvailabilityZone !== plan.aws.availabilityZone) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
"macOS subnet and Dedicated Host availability zone mismatch",
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
exactCommit: plan.source.sha,
|
|
124
|
+
activeHosts: activeHosts.length,
|
|
125
|
+
activeInstances: instances.length,
|
|
126
|
+
amiOwnerId: image.OwnerId,
|
|
127
|
+
subnetAvailabilityZone: subnet.AvailabilityZone,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function executeMacosJitCampaignLaunch(plan, { profile = "" } = {}) {
|
|
132
|
+
if (
|
|
133
|
+
plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
|
|
134
|
+
plan.kind !== "campaign-launch-plan"
|
|
135
|
+
) {
|
|
136
|
+
throw new Error("macOS JIT campaign launch plan contract is invalid");
|
|
137
|
+
}
|
|
138
|
+
const preflight = assertCampaignLaunchPreflight(plan, profile);
|
|
139
|
+
assertDryRun(
|
|
140
|
+
commandResult(
|
|
141
|
+
"aws",
|
|
142
|
+
awsArgs(plan, profile, macosAllocateHostsArgs(plan, { dryRun: true })),
|
|
143
|
+
),
|
|
144
|
+
"EC2 AllocateHosts DryRun",
|
|
145
|
+
);
|
|
146
|
+
const hostId = awsJson(
|
|
147
|
+
plan,
|
|
148
|
+
profile,
|
|
149
|
+
macosAllocateHostsArgs(plan),
|
|
150
|
+
"EC2 AllocateHosts",
|
|
151
|
+
).HostIds?.[0];
|
|
152
|
+
if (!/^h-[0-9a-f]+$/.test(String(hostId || ""))) {
|
|
153
|
+
throw new Error("EC2 AllocateHosts returned no Dedicated Host identity");
|
|
154
|
+
}
|
|
155
|
+
let launched;
|
|
156
|
+
try {
|
|
157
|
+
assertDryRun(
|
|
158
|
+
commandResult(
|
|
159
|
+
"aws",
|
|
160
|
+
awsArgs(
|
|
161
|
+
plan,
|
|
162
|
+
profile,
|
|
163
|
+
macosRunInstancesArgs(plan, { hostId, dryRun: true }),
|
|
164
|
+
),
|
|
165
|
+
),
|
|
166
|
+
"EC2 RunInstances DryRun",
|
|
167
|
+
);
|
|
168
|
+
launched = awsJson(
|
|
169
|
+
plan,
|
|
170
|
+
profile,
|
|
171
|
+
macosRunInstancesArgs(plan, { hostId }),
|
|
172
|
+
"EC2 RunInstances",
|
|
173
|
+
);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`${error.message || error}; Dedicated Host ${hostId} remains card-owned and must be retained for the 24-hour minimum before the reaper releases it`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const instance = launched.Instances?.[0];
|
|
180
|
+
if (!/^i-[0-9a-f]+$/.test(String(instance?.InstanceId || ""))) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
`EC2 RunInstances returned no instance identity; Dedicated Host ${hostId} remains card-owned for reaper cleanup`,
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
requireSuccess(
|
|
186
|
+
commandResult(
|
|
187
|
+
"aws",
|
|
188
|
+
awsArgs(plan, profile, [
|
|
189
|
+
"ec2",
|
|
190
|
+
"wait",
|
|
191
|
+
"instance-running",
|
|
192
|
+
"--instance-ids",
|
|
193
|
+
instance.InstanceId,
|
|
194
|
+
]),
|
|
195
|
+
),
|
|
196
|
+
"macOS campaign instance-running waiter",
|
|
197
|
+
);
|
|
198
|
+
const host = awsJson(
|
|
199
|
+
plan,
|
|
200
|
+
profile,
|
|
201
|
+
["ec2", "describe-hosts", "--host-ids", hostId, "--output", "json"],
|
|
202
|
+
"macOS Dedicated Host identity readback",
|
|
203
|
+
).Hosts?.[0];
|
|
204
|
+
if (!host || host.HostId !== hostId) {
|
|
205
|
+
throw new Error("macOS Dedicated Host readback failed");
|
|
206
|
+
}
|
|
207
|
+
assertOwnership(host.Tags, plan);
|
|
208
|
+
return {
|
|
209
|
+
schemaVersion: 1,
|
|
210
|
+
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
211
|
+
kind: "campaign-launch-result",
|
|
212
|
+
status: "launched",
|
|
213
|
+
repository: plan.repository,
|
|
214
|
+
campaign: plan.campaign,
|
|
215
|
+
source: plan.source,
|
|
216
|
+
aws: {
|
|
217
|
+
region: plan.aws.region,
|
|
218
|
+
availabilityZone: plan.aws.availabilityZone,
|
|
219
|
+
hostId,
|
|
220
|
+
hostAllocatedAt: new Date(host.AllocationTime).toISOString(),
|
|
221
|
+
instanceId: instance.InstanceId,
|
|
222
|
+
instanceType: instance.InstanceType,
|
|
223
|
+
imageId: instance.ImageId,
|
|
224
|
+
launchTime: new Date(instance.LaunchTime).toISOString(),
|
|
225
|
+
},
|
|
226
|
+
preflight,
|
|
227
|
+
dryRuns: ["AllocateHosts:DryRunOperation", "RunInstances:DryRunOperation"],
|
|
228
|
+
planDigest: plan.digest,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function deleteCampaignResidue(plan, profile) {
|
|
233
|
+
const prefix = `/kungfu/burst/macos/${plan.campaign.id}/`;
|
|
234
|
+
const parameters =
|
|
235
|
+
awsJson(
|
|
236
|
+
plan,
|
|
237
|
+
profile,
|
|
238
|
+
[
|
|
239
|
+
"ssm",
|
|
240
|
+
"describe-parameters",
|
|
241
|
+
"--parameter-filters",
|
|
242
|
+
`Key=Name,Option=BeginsWith,Values=${prefix}`,
|
|
243
|
+
"--output",
|
|
244
|
+
"json",
|
|
245
|
+
],
|
|
246
|
+
"macOS campaign parameter cleanup lookup",
|
|
247
|
+
).Parameters || [];
|
|
248
|
+
const names = parameters
|
|
249
|
+
.map((parameter) => String(parameter.Name || ""))
|
|
250
|
+
.filter((name) => name.startsWith(prefix));
|
|
251
|
+
for (const name of names) {
|
|
252
|
+
requireSuccess(
|
|
253
|
+
commandResult(
|
|
254
|
+
"aws",
|
|
255
|
+
awsArgs(plan, profile, ["ssm", "delete-parameter", "--name", name]),
|
|
256
|
+
),
|
|
257
|
+
`macOS campaign parameter ${name} cleanup`,
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const runners = ghJson(
|
|
261
|
+
["api", `repos/${plan.repository}/actions/runners?per_page=100`],
|
|
262
|
+
undefined,
|
|
263
|
+
"macOS campaign runner cleanup lookup",
|
|
264
|
+
).runners;
|
|
265
|
+
const candidates = (runners || []).filter((runner) =>
|
|
266
|
+
(runner.labels || []).some((label) =>
|
|
267
|
+
String(label.name || label).startsWith("aws-us-ec2-macos-jit-"),
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
for (const runner of candidates) {
|
|
271
|
+
requireSuccess(
|
|
272
|
+
commandResult("gh", [
|
|
273
|
+
"api",
|
|
274
|
+
"--method",
|
|
275
|
+
"DELETE",
|
|
276
|
+
`repos/${plan.repository}/actions/runners/${runner.id}`,
|
|
277
|
+
]),
|
|
278
|
+
`macOS campaign runner ${runner.id} cleanup`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
deletedParameters: names,
|
|
283
|
+
deletedRunners: candidates.map(({ id }) => id),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function assertCampaignClosePreflight(plan, profile) {
|
|
288
|
+
const host = awsJson(
|
|
289
|
+
plan,
|
|
290
|
+
profile,
|
|
291
|
+
[
|
|
292
|
+
"ec2",
|
|
293
|
+
"describe-hosts",
|
|
294
|
+
"--host-ids",
|
|
295
|
+
plan.aws.hostId,
|
|
296
|
+
"--output",
|
|
297
|
+
"json",
|
|
298
|
+
],
|
|
299
|
+
"macOS campaign close host preflight",
|
|
300
|
+
).Hosts?.[0];
|
|
301
|
+
if (!host || host.HostId !== plan.aws.hostId) {
|
|
302
|
+
throw new Error("macOS campaign close host identity mismatch");
|
|
303
|
+
}
|
|
304
|
+
assertOwnership(host.Tags, plan);
|
|
305
|
+
const allocationHours =
|
|
306
|
+
(new Date(plan.lifecycle.observedAt).getTime() -
|
|
307
|
+
new Date(host.AllocationTime).getTime()) /
|
|
308
|
+
3_600_000;
|
|
309
|
+
if (allocationHours < plan.lifecycle.minimumHostAllocationHours) {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Dedicated Host actual allocation age ${allocationHours.toFixed(3)}h is below the 24-hour minimum`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const instance = awsJson(
|
|
315
|
+
plan,
|
|
316
|
+
profile,
|
|
317
|
+
[
|
|
318
|
+
"ec2",
|
|
319
|
+
"describe-instances",
|
|
320
|
+
"--instance-ids",
|
|
321
|
+
plan.aws.instanceId,
|
|
322
|
+
"--output",
|
|
323
|
+
"json",
|
|
324
|
+
],
|
|
325
|
+
"macOS campaign close instance preflight",
|
|
326
|
+
).Reservations?.[0]?.Instances?.[0];
|
|
327
|
+
if (!instance || instance.Placement?.HostId !== plan.aws.hostId) {
|
|
328
|
+
throw new Error("macOS campaign close instance identity mismatch");
|
|
329
|
+
}
|
|
330
|
+
assertOwnership(instance.Tags, plan);
|
|
331
|
+
const root = (instance.BlockDeviceMappings || []).find(
|
|
332
|
+
({ Ebs }) => Ebs?.DeleteOnTermination === true,
|
|
333
|
+
);
|
|
334
|
+
if (!root?.Ebs?.VolumeId) {
|
|
335
|
+
throw new Error("macOS campaign root volume is not delete-on-termination");
|
|
336
|
+
}
|
|
337
|
+
const volume = awsJson(
|
|
338
|
+
plan,
|
|
339
|
+
profile,
|
|
340
|
+
[
|
|
341
|
+
"ec2",
|
|
342
|
+
"describe-volumes",
|
|
343
|
+
"--volume-ids",
|
|
344
|
+
root.Ebs.VolumeId,
|
|
345
|
+
"--output",
|
|
346
|
+
"json",
|
|
347
|
+
],
|
|
348
|
+
"macOS campaign root volume preflight",
|
|
349
|
+
).Volumes?.[0];
|
|
350
|
+
if (!volume || volume.Encrypted !== true) {
|
|
351
|
+
throw new Error("macOS campaign root volume is not encrypted");
|
|
352
|
+
}
|
|
353
|
+
return { host, instance, volumeId: root.Ebs.VolumeId, allocationHours };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
export function executeMacosJitCampaignClose(plan, { profile = "" } = {}) {
|
|
357
|
+
if (
|
|
358
|
+
plan?.contract !== AWS_MACOS_JIT_CONTROLLER_CONTRACT ||
|
|
359
|
+
plan.kind !== "campaign-close-plan"
|
|
360
|
+
) {
|
|
361
|
+
throw new Error("macOS JIT campaign close plan contract is invalid");
|
|
362
|
+
}
|
|
363
|
+
const checked = assertCampaignClosePreflight(plan, profile);
|
|
364
|
+
const cleanup = deleteCampaignResidue(plan, profile);
|
|
365
|
+
if (checked.instance.State?.Name !== "terminated") {
|
|
366
|
+
requireSuccess(
|
|
367
|
+
commandResult(
|
|
368
|
+
"aws",
|
|
369
|
+
awsArgs(plan, profile, [
|
|
370
|
+
"ec2",
|
|
371
|
+
"terminate-instances",
|
|
372
|
+
"--instance-ids",
|
|
373
|
+
plan.aws.instanceId,
|
|
374
|
+
"--output",
|
|
375
|
+
"json",
|
|
376
|
+
]),
|
|
377
|
+
),
|
|
378
|
+
"macOS campaign instance termination",
|
|
379
|
+
);
|
|
380
|
+
requireSuccess(
|
|
381
|
+
commandResult(
|
|
382
|
+
"aws",
|
|
383
|
+
awsArgs(plan, profile, [
|
|
384
|
+
"ec2",
|
|
385
|
+
"wait",
|
|
386
|
+
"instance-terminated",
|
|
387
|
+
"--instance-ids",
|
|
388
|
+
plan.aws.instanceId,
|
|
389
|
+
]),
|
|
390
|
+
),
|
|
391
|
+
"macOS campaign instance-terminated waiter",
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const host = awsJson(
|
|
395
|
+
plan,
|
|
396
|
+
profile,
|
|
397
|
+
[
|
|
398
|
+
"ec2",
|
|
399
|
+
"describe-hosts",
|
|
400
|
+
"--host-ids",
|
|
401
|
+
plan.aws.hostId,
|
|
402
|
+
"--output",
|
|
403
|
+
"json",
|
|
404
|
+
],
|
|
405
|
+
"macOS campaign host release preflight",
|
|
406
|
+
).Hosts?.[0];
|
|
407
|
+
const result = {
|
|
408
|
+
schemaVersion: 1,
|
|
409
|
+
contract: AWS_MACOS_JIT_CONTROLLER_CONTRACT,
|
|
410
|
+
kind: "campaign-close-result",
|
|
411
|
+
campaign: plan.campaign,
|
|
412
|
+
source: plan.source,
|
|
413
|
+
aws: {
|
|
414
|
+
region: plan.aws.region,
|
|
415
|
+
hostId: plan.aws.hostId,
|
|
416
|
+
instanceId: plan.aws.instanceId,
|
|
417
|
+
volumeId: checked.volumeId,
|
|
418
|
+
},
|
|
419
|
+
cleanup,
|
|
420
|
+
planDigest: plan.digest,
|
|
421
|
+
};
|
|
422
|
+
if (host?.State !== "available" || (host.Instances || []).length !== 0) {
|
|
423
|
+
return {
|
|
424
|
+
...result,
|
|
425
|
+
status: "release-pending",
|
|
426
|
+
aws: { ...result.aws, hostState: host?.State || "unknown" },
|
|
427
|
+
fallback: "scheduled-card-scoped-reaper",
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
assertDryRun(
|
|
431
|
+
commandResult(
|
|
432
|
+
"aws",
|
|
433
|
+
awsArgs(plan, profile, macosReleaseHostsArgs(plan, { dryRun: true })),
|
|
434
|
+
),
|
|
435
|
+
"EC2 ReleaseHosts DryRun",
|
|
436
|
+
);
|
|
437
|
+
const released = awsJson(
|
|
438
|
+
plan,
|
|
439
|
+
profile,
|
|
440
|
+
macosReleaseHostsArgs(plan),
|
|
441
|
+
"EC2 ReleaseHosts",
|
|
442
|
+
);
|
|
443
|
+
if (!(released.Successful || []).includes(plan.aws.hostId)) {
|
|
444
|
+
throw new Error(
|
|
445
|
+
"EC2 ReleaseHosts did not confirm the exact Dedicated Host",
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
...result,
|
|
450
|
+
status: "released",
|
|
451
|
+
lifecycle: {
|
|
452
|
+
...plan.lifecycle,
|
|
453
|
+
actualAllocationHours: checked.allocationHours,
|
|
454
|
+
instanceTerminated: true,
|
|
455
|
+
encryptedDeleteOnTerminationVolume: true,
|
|
456
|
+
hostReleased: true,
|
|
457
|
+
},
|
|
458
|
+
dryRun: "ReleaseHosts:DryRunOperation",
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function commonValues(execute) {
|
|
463
|
+
return {
|
|
464
|
+
execute,
|
|
465
|
+
repository: arg("repository", "kungfu-systems/kungfu"),
|
|
466
|
+
campaignId: arg("campaign-id"),
|
|
467
|
+
sourceSha: arg("source-sha"),
|
|
468
|
+
sourceRef: arg("source-ref"),
|
|
469
|
+
region: arg("region", "us-east-1"),
|
|
470
|
+
availabilityZone: arg("availability-zone"),
|
|
471
|
+
instanceType: arg("instance-type", "mac2.metal"),
|
|
472
|
+
amiId: arg("ami-id"),
|
|
473
|
+
amiName: arg("ami-name"),
|
|
474
|
+
subnetId: arg("subnet-id"),
|
|
475
|
+
securityGroupId: arg("security-group-id"),
|
|
476
|
+
instanceProfileName: arg("instance-profile-name"),
|
|
477
|
+
evidenceBucket: arg("evidence-bucket"),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function confirm(plan) {
|
|
482
|
+
if (arg("confirm-source-sha") !== plan.source.sha) {
|
|
483
|
+
throw new Error("--confirm-source-sha must equal the exact source SHA");
|
|
484
|
+
}
|
|
485
|
+
if (arg("confirm-campaign-id") !== plan.campaign.id) {
|
|
486
|
+
throw new Error("--confirm-campaign-id must equal the campaign id");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function emit(plan, mode, execute, operation) {
|
|
491
|
+
if (!execute || mode.startsWith("plan-")) {
|
|
492
|
+
process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
|
|
493
|
+
return plan;
|
|
494
|
+
}
|
|
495
|
+
confirm(plan);
|
|
496
|
+
const result = operation();
|
|
497
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
498
|
+
return result;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
export function main() {
|
|
502
|
+
const mode = process.argv[2] || "plan-campaign";
|
|
503
|
+
const execute = flag("execute");
|
|
504
|
+
if (["plan-campaign", "launch-campaign"].includes(mode)) {
|
|
505
|
+
const plan = createMacosJitCampaignPlan({
|
|
506
|
+
...commonValues(execute),
|
|
507
|
+
createdAt: arg("created-at", new Date().toISOString()),
|
|
508
|
+
});
|
|
509
|
+
return emit(plan, mode, execute, () =>
|
|
510
|
+
executeMacosJitCampaignLaunch(plan, { profile: arg("aws-profile") }),
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
if (["plan-job", "run-job"].includes(mode)) {
|
|
514
|
+
const plan = createMacosJitJobPlan({
|
|
515
|
+
...commonValues(execute),
|
|
516
|
+
runId: arg("run-id"),
|
|
517
|
+
runAttempt: arg("run-attempt", "1"),
|
|
518
|
+
jobId: arg("job-id"),
|
|
519
|
+
qualificationId: arg("qualification-id"),
|
|
520
|
+
runnerLabel: arg("runner-label"),
|
|
521
|
+
runnerName: arg("runner-name"),
|
|
522
|
+
hostId: arg("host-id"),
|
|
523
|
+
instanceId: arg("instance-id"),
|
|
524
|
+
hostAllocatedAt: arg("host-allocated-at"),
|
|
525
|
+
});
|
|
526
|
+
if (execute && arg("confirm-run-id") !== plan.github.runId) {
|
|
527
|
+
throw new Error("--confirm-run-id must equal the exact GitHub run id");
|
|
528
|
+
}
|
|
529
|
+
return emit(plan, mode, execute, () =>
|
|
530
|
+
executeMacosJitJob(plan, { profile: arg("aws-profile") }),
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
if (["plan-close", "close-campaign"].includes(mode)) {
|
|
534
|
+
const plan = createMacosJitClosePlan({
|
|
535
|
+
...commonValues(execute),
|
|
536
|
+
hostId: arg("host-id"),
|
|
537
|
+
instanceId: arg("instance-id"),
|
|
538
|
+
hostAllocatedAt: arg("host-allocated-at"),
|
|
539
|
+
observedAt: arg("observed-at", new Date().toISOString()),
|
|
540
|
+
});
|
|
541
|
+
return emit(plan, mode, execute, () =>
|
|
542
|
+
executeMacosJitCampaignClose(plan, { profile: arg("aws-profile") }),
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
throw new Error(`unsupported aws-macos-jit-controller mode: ${mode}`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (
|
|
549
|
+
process.argv[1] &&
|
|
550
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
551
|
+
) {
|
|
552
|
+
try {
|
|
553
|
+
main();
|
|
554
|
+
} catch (error) {
|
|
555
|
+
console.error(`::error::${error.message || error}`);
|
|
556
|
+
process.exitCode = 1;
|
|
557
|
+
}
|
|
558
|
+
}
|