@kungfu-tech/buildchain 3.0.5-alpha.1 → 3.0.5-alpha.2
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 +30 -20
- package/dist/site/buildchain-site.json +14 -9
- package/dist/site/controller-registry.json +11 -3
- package/dist/site/kfd-claims.json +6 -4
- package/dist/site/kfd-upstream-aggregate.json +1 -1
- package/dist/site/manual-registry.json +1 -1
- package/dist/site/page-registry.json +9 -4
- package/dist/site/public-surface-audit.json +6 -4
- package/dist/site/publication-registry.json +4 -4
- package/dist/site/site-manifest.json +5 -5
- package/dist/site/workflow-registry.json +4 -2
- package/docs/aws-us-elastic-runner-burst-plane.md +102 -17
- package/docs/reusable-build-surface.md +9 -0
- package/package.json +1 -1
- package/scripts/aws-windows-jit-campaign-core.mjs +241 -0
- package/scripts/aws-windows-jit-campaign.mjs +167 -0
- package/scripts/aws-windows-jit-controller-core.mjs +20 -2
- package/scripts/aws-windows-jit-controller.mjs +184 -109
- package/scripts/aws-windows-jit-core.mjs +18 -0
- package/scripts/aws-windows-jit.mjs +2 -0
- package/scripts/locked-source-checkout.mjs +17 -3
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
import { digest } from "./aws-runner-burst-core.mjs";
|
|
4
4
|
import {
|
|
5
5
|
WINDOWS_EC2_JIT,
|
|
6
|
+
windowsJitCampaignId,
|
|
6
7
|
windowsJitRunnerLabel,
|
|
7
8
|
windowsJitRunnerLabels,
|
|
8
9
|
} from "./aws-windows-jit-core.mjs";
|
|
9
10
|
|
|
10
11
|
export const AWS_WINDOWS_JIT_CONTROLLER_CONTRACT =
|
|
11
|
-
"kungfu-buildchain-aws-windows-jit-controller/
|
|
12
|
+
"kungfu-buildchain-aws-windows-jit-controller/v2";
|
|
12
13
|
|
|
13
14
|
function exact(value, pattern, label) {
|
|
14
15
|
const normalized = String(value || "").trim();
|
|
@@ -59,8 +60,9 @@ export function createWindowsJitLaunchPlan(values = {}) {
|
|
|
59
60
|
);
|
|
60
61
|
const jobId = exact(values.jobId, /^\d+$/, "jobId");
|
|
61
62
|
const qualification = qualificationId(values.qualificationId);
|
|
63
|
+
const campaign = windowsJitCampaignId(values.campaignId);
|
|
62
64
|
const runnerLabel = windowsJitRunnerLabel(values.runnerLabel);
|
|
63
|
-
const expectedLabel = `${WINDOWS_EC2_JIT.labelPrefix}${qualification}`;
|
|
65
|
+
const expectedLabel = `${WINDOWS_EC2_JIT.labelPrefix}${campaign}-${qualification}`;
|
|
64
66
|
if (runnerLabel !== expectedLabel) {
|
|
65
67
|
throw new Error(`runnerLabel must be ${expectedLabel}`);
|
|
66
68
|
}
|
|
@@ -109,6 +111,11 @@ export function createWindowsJitLaunchPlan(values = {}) {
|
|
|
109
111
|
/^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/,
|
|
110
112
|
"evidenceBucket",
|
|
111
113
|
);
|
|
114
|
+
const stateTable = exact(
|
|
115
|
+
values.stateTable,
|
|
116
|
+
/^kungfu-buildchain-windows-jit(?:-[A-Za-z0-9_.-]+)?$/,
|
|
117
|
+
"stateTable",
|
|
118
|
+
);
|
|
112
119
|
const launchedAt = iso(values.launchedAt, "launchedAt");
|
|
113
120
|
const clientToken = `kungfu-${runId}-${runAttempt}-${qualification}`;
|
|
114
121
|
const jitParameterName =
|
|
@@ -125,6 +132,7 @@ export function createWindowsJitLaunchPlan(values = {}) {
|
|
|
125
132
|
tag("kungfu:owner", "buildchain"),
|
|
126
133
|
tag("kungfu:plane", "aws-us-elastic-runner-burst"),
|
|
127
134
|
tag("kungfu:provider", "windows-ec2-jit"),
|
|
135
|
+
tag("kungfu:campaign-id", campaign),
|
|
128
136
|
tag("kungfu:github-run-id", runId),
|
|
129
137
|
tag("kungfu:github-run-attempt", runAttempt),
|
|
130
138
|
tag("kungfu:qualification-id", qualification),
|
|
@@ -141,6 +149,7 @@ export function createWindowsJitLaunchPlan(values = {}) {
|
|
|
141
149
|
contract: AWS_WINDOWS_JIT_CONTROLLER_CONTRACT,
|
|
142
150
|
kind: "launch-plan",
|
|
143
151
|
repository,
|
|
152
|
+
campaign: { id: campaign },
|
|
144
153
|
source: { sha: sourceSha, ref: sourceRef },
|
|
145
154
|
github: {
|
|
146
155
|
runId,
|
|
@@ -148,6 +157,7 @@ export function createWindowsJitLaunchPlan(values = {}) {
|
|
|
148
157
|
jobId,
|
|
149
158
|
qualificationId: qualification,
|
|
150
159
|
event: "workflow_dispatch",
|
|
160
|
+
displayTitle: `AWS Windows JIT ${campaign} ${qualification}`,
|
|
151
161
|
},
|
|
152
162
|
runner: {
|
|
153
163
|
name: runnerName,
|
|
@@ -164,6 +174,7 @@ export function createWindowsJitLaunchPlan(values = {}) {
|
|
|
164
174
|
securityGroupId,
|
|
165
175
|
instanceProfileName,
|
|
166
176
|
evidenceBucket,
|
|
177
|
+
stateTable,
|
|
167
178
|
jitParameterName,
|
|
168
179
|
launchedAt,
|
|
169
180
|
clientToken,
|
|
@@ -188,6 +199,13 @@ export function createWindowsJitLaunchPlan(values = {}) {
|
|
|
188
199
|
exactSourceRequired: true,
|
|
189
200
|
queuedJobRequired: true,
|
|
190
201
|
activeInstanceCeiling: WINDOWS_EC2_JIT.maxConcurrentInstances,
|
|
202
|
+
campaignAcceptedInstanceCeiling: WINDOWS_EC2_JIT.maxAcceptedInstances,
|
|
203
|
+
campaignReservationUsd:
|
|
204
|
+
(WINDOWS_EC2_JIT.pricePerHourUsd *
|
|
205
|
+
WINDOWS_EC2_JIT.maximumInstanceLifetimeMinutes) /
|
|
206
|
+
60,
|
|
207
|
+
campaignBudgetLimitUsd: WINDOWS_EC2_JIT.budgetLimitUsd,
|
|
208
|
+
persistentCampaignLedgerRequired: true,
|
|
191
209
|
awsDryRunRequiredBeforeLaunch: true,
|
|
192
210
|
userDataTransport: "fileb://rendered-bootstrap",
|
|
193
211
|
jitConfigTransport: "0600-temporary-file-to-ssm-secure-string",
|
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
createWindowsJitLaunchPlan,
|
|
12
12
|
windowsRunInstancesArgs,
|
|
13
13
|
} from "./aws-windows-jit-controller-core.mjs";
|
|
14
|
+
import {
|
|
15
|
+
windowsCampaignMarkLaunchedArgs,
|
|
16
|
+
windowsCampaignReservationItems,
|
|
17
|
+
} from "./aws-windows-jit-campaign-core.mjs";
|
|
14
18
|
import { renderWindowsJitBootstrap } from "./aws-windows-jit-core.mjs";
|
|
15
19
|
|
|
16
20
|
function arg(name, fallback = "") {
|
|
@@ -75,6 +79,7 @@ function assertLivePreflight(plan, profile) {
|
|
|
75
79
|
);
|
|
76
80
|
if (
|
|
77
81
|
run.event !== plan.github.event ||
|
|
82
|
+
run.display_title !== plan.github.displayTitle ||
|
|
78
83
|
run.head_sha !== plan.source.sha ||
|
|
79
84
|
run.head_repository?.full_name !== plan.repository ||
|
|
80
85
|
!["queued", "in_progress"].includes(run.status)
|
|
@@ -267,6 +272,169 @@ function cleanupFailedLaunch(
|
|
|
267
272
|
return failures;
|
|
268
273
|
}
|
|
269
274
|
|
|
275
|
+
function generateJitConfiguration(plan) {
|
|
276
|
+
const jit = ghJson(
|
|
277
|
+
[
|
|
278
|
+
"api",
|
|
279
|
+
"--method",
|
|
280
|
+
"POST",
|
|
281
|
+
`repos/${plan.repository}/actions/runners/generate-jitconfig`,
|
|
282
|
+
"--input",
|
|
283
|
+
"-",
|
|
284
|
+
],
|
|
285
|
+
JSON.stringify({
|
|
286
|
+
name: plan.runner.name,
|
|
287
|
+
runner_group_id: 1,
|
|
288
|
+
labels: plan.runner.labels,
|
|
289
|
+
work_folder: "_work",
|
|
290
|
+
}),
|
|
291
|
+
"GitHub JIT configuration",
|
|
292
|
+
).encoded_jit_config;
|
|
293
|
+
if (!jit || typeof jit !== "string") {
|
|
294
|
+
throw new Error("GitHub JIT configuration was empty");
|
|
295
|
+
}
|
|
296
|
+
return jit;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function createJitParameter(plan, profile, parameterInputPath, jit) {
|
|
300
|
+
fs.writeFileSync(
|
|
301
|
+
parameterInputPath,
|
|
302
|
+
JSON.stringify({
|
|
303
|
+
Name: plan.aws.jitParameterName,
|
|
304
|
+
Description: `One-shot GitHub Actions JIT config for ${plan.repository} run ${plan.github.runId} attempt ${plan.github.runAttempt}`,
|
|
305
|
+
Type: "SecureString",
|
|
306
|
+
Tier: "Advanced",
|
|
307
|
+
Value: jit,
|
|
308
|
+
Tags: [
|
|
309
|
+
{ Key: "kungfu:owner", Value: "buildchain" },
|
|
310
|
+
{ Key: "kungfu:plane", Value: "aws-us-elastic-runner-burst" },
|
|
311
|
+
{ Key: "kungfu:provider", Value: "windows-ec2-jit" },
|
|
312
|
+
{ Key: "kungfu:campaign-id", Value: plan.campaign.id },
|
|
313
|
+
{ Key: "kungfu:github-run-id", Value: plan.github.runId },
|
|
314
|
+
{
|
|
315
|
+
Key: "kungfu:qualification-id",
|
|
316
|
+
Value: plan.github.qualificationId,
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
}),
|
|
320
|
+
{ mode: 0o600 },
|
|
321
|
+
);
|
|
322
|
+
requireSuccess(
|
|
323
|
+
commandResult(
|
|
324
|
+
"aws",
|
|
325
|
+
awsArgs(plan, profile, [
|
|
326
|
+
"ssm",
|
|
327
|
+
"put-parameter",
|
|
328
|
+
"--cli-input-json",
|
|
329
|
+
`file://${parameterInputPath}`,
|
|
330
|
+
"--output",
|
|
331
|
+
"json",
|
|
332
|
+
]),
|
|
333
|
+
),
|
|
334
|
+
"SSM JIT parameter creation",
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function writeBootstrap(plan, bootstrapPath) {
|
|
339
|
+
const template = fs.readFileSync(
|
|
340
|
+
path.resolve(
|
|
341
|
+
"infra/aws-us-elastic-runner-burst-plane/windows-jit-bootstrap.ps1",
|
|
342
|
+
),
|
|
343
|
+
"utf8",
|
|
344
|
+
);
|
|
345
|
+
fs.writeFileSync(
|
|
346
|
+
bootstrapPath,
|
|
347
|
+
renderWindowsJitBootstrap(template, {
|
|
348
|
+
region: plan.aws.region,
|
|
349
|
+
campaignId: plan.campaign.id,
|
|
350
|
+
jitParameterName: plan.aws.jitParameterName,
|
|
351
|
+
evidenceBucket: plan.aws.evidenceBucket,
|
|
352
|
+
runnerLabel: plan.runner.label,
|
|
353
|
+
sourceSha: plan.source.sha,
|
|
354
|
+
githubRunId: plan.github.runId,
|
|
355
|
+
githubRunAttempt: plan.github.runAttempt,
|
|
356
|
+
amiId: plan.aws.amiId,
|
|
357
|
+
amiName: plan.aws.amiName,
|
|
358
|
+
instanceType: plan.aws.instanceType,
|
|
359
|
+
launchedAt: plan.aws.launchedAt,
|
|
360
|
+
}),
|
|
361
|
+
{ mode: 0o600 },
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function assertLaunchDryRun(plan, profile, bootstrapPath) {
|
|
366
|
+
const dryRun = commandResult(
|
|
367
|
+
"aws",
|
|
368
|
+
awsArgs(
|
|
369
|
+
plan,
|
|
370
|
+
profile,
|
|
371
|
+
windowsRunInstancesArgs(plan, { bootstrapPath, dryRun: true }),
|
|
372
|
+
),
|
|
373
|
+
);
|
|
374
|
+
if (
|
|
375
|
+
dryRun.status === 0 ||
|
|
376
|
+
!/DryRunOperation/.test(`${dryRun.stdout}\n${dryRun.stderr}`)
|
|
377
|
+
) {
|
|
378
|
+
throw new Error("EC2 RunInstances DryRun did not return DryRunOperation");
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function reserveCampaign(plan, profile) {
|
|
383
|
+
requireSuccess(
|
|
384
|
+
commandResult(
|
|
385
|
+
"aws",
|
|
386
|
+
awsArgs(plan, profile, [
|
|
387
|
+
"dynamodb",
|
|
388
|
+
"transact-write-items",
|
|
389
|
+
"--transact-items",
|
|
390
|
+
JSON.stringify(
|
|
391
|
+
windowsCampaignReservationItems(plan, new Date().toISOString()),
|
|
392
|
+
),
|
|
393
|
+
"--output",
|
|
394
|
+
"json",
|
|
395
|
+
]),
|
|
396
|
+
),
|
|
397
|
+
"Windows campaign atomic reservation",
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function launchInstance(plan, profile, bootstrapPath) {
|
|
402
|
+
const launched = jsonResult(
|
|
403
|
+
commandResult(
|
|
404
|
+
"aws",
|
|
405
|
+
awsArgs(
|
|
406
|
+
plan,
|
|
407
|
+
profile,
|
|
408
|
+
windowsRunInstancesArgs(plan, { bootstrapPath, dryRun: false }),
|
|
409
|
+
),
|
|
410
|
+
),
|
|
411
|
+
"EC2 RunInstances",
|
|
412
|
+
);
|
|
413
|
+
const instance = launched.Instances?.[0];
|
|
414
|
+
if (!/^i-[0-9a-f]+$/.test(String(instance?.InstanceId || ""))) {
|
|
415
|
+
throw new Error("EC2 RunInstances returned no instance identity");
|
|
416
|
+
}
|
|
417
|
+
return instance;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function markCampaignLaunched(plan, profile, instanceId) {
|
|
421
|
+
requireSuccess(
|
|
422
|
+
commandResult(
|
|
423
|
+
"aws",
|
|
424
|
+
awsArgs(
|
|
425
|
+
plan,
|
|
426
|
+
profile,
|
|
427
|
+
windowsCampaignMarkLaunchedArgs(
|
|
428
|
+
plan,
|
|
429
|
+
instanceId,
|
|
430
|
+
new Date().toISOString(),
|
|
431
|
+
),
|
|
432
|
+
),
|
|
433
|
+
),
|
|
434
|
+
"Windows campaign launch ledger update",
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
|
|
270
438
|
export function executeWindowsJitLaunch(plan, { profile = "" } = {}) {
|
|
271
439
|
if (plan?.contract !== AWS_WINDOWS_JIT_CONTROLLER_CONTRACT) {
|
|
272
440
|
throw new Error("Windows JIT launch plan contract is invalid");
|
|
@@ -285,124 +453,23 @@ export function executeWindowsJitLaunch(plan, { profile = "" } = {}) {
|
|
|
285
453
|
let failure;
|
|
286
454
|
const cleanupFailures = [];
|
|
287
455
|
try {
|
|
288
|
-
const jit =
|
|
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
|
-
);
|
|
456
|
+
const jit = generateJitConfiguration(plan);
|
|
457
|
+
createJitParameter(plan, profile, parameterInputPath, jit);
|
|
343
458
|
parameterCreated = true;
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
}
|
|
459
|
+
writeBootstrap(plan, bootstrapPath);
|
|
460
|
+
assertLaunchDryRun(plan, profile, bootstrapPath);
|
|
461
|
+
reserveCampaign(plan, profile);
|
|
383
462
|
launchAttempted = true;
|
|
384
|
-
const
|
|
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
|
-
}
|
|
463
|
+
const instance = launchInstance(plan, profile, bootstrapPath);
|
|
399
464
|
launchSucceeded = true;
|
|
465
|
+
markCampaignLaunched(plan, profile, instance.InstanceId);
|
|
400
466
|
result = {
|
|
401
467
|
schemaVersion: 1,
|
|
402
468
|
contract: AWS_WINDOWS_JIT_CONTROLLER_CONTRACT,
|
|
403
469
|
kind: "launch-result",
|
|
404
470
|
status: "launched",
|
|
405
471
|
source: plan.source,
|
|
472
|
+
campaign: plan.campaign,
|
|
406
473
|
github: plan.github,
|
|
407
474
|
runner: plan.runner,
|
|
408
475
|
aws: {
|
|
@@ -452,6 +519,7 @@ function planFromArgs(execute) {
|
|
|
452
519
|
runAttempt: arg("run-attempt", "1"),
|
|
453
520
|
jobId: arg("job-id"),
|
|
454
521
|
qualificationId: arg("qualification-id"),
|
|
522
|
+
campaignId: arg("campaign-id"),
|
|
455
523
|
runnerLabel: arg("runner-label"),
|
|
456
524
|
runnerName: arg("runner-name"),
|
|
457
525
|
sourceSha: arg("source-sha"),
|
|
@@ -464,6 +532,7 @@ function planFromArgs(execute) {
|
|
|
464
532
|
securityGroupId: arg("security-group-id"),
|
|
465
533
|
instanceProfileName: arg("instance-profile-name"),
|
|
466
534
|
evidenceBucket: arg("evidence-bucket"),
|
|
535
|
+
stateTable: arg("state-table"),
|
|
467
536
|
jitParameterName: arg("jit-parameter"),
|
|
468
537
|
launchedAt: arg("launched-at", new Date().toISOString()),
|
|
469
538
|
});
|
|
@@ -482,6 +551,12 @@ export function main() {
|
|
|
482
551
|
if (arg("confirm-run-id") !== plan.github.runId) {
|
|
483
552
|
throw new Error("--confirm-run-id must equal the exact GitHub run id");
|
|
484
553
|
}
|
|
554
|
+
if (arg("confirm-campaign-id") !== plan.campaign.id) {
|
|
555
|
+
throw new Error("--confirm-campaign-id must equal the campaign id");
|
|
556
|
+
}
|
|
557
|
+
if (arg("confirm-state-table") !== plan.aws.stateTable) {
|
|
558
|
+
throw new Error("--confirm-state-table must equal the campaign state table");
|
|
559
|
+
}
|
|
485
560
|
const result = executeWindowsJitLaunch(plan, {
|
|
486
561
|
profile: arg("aws-profile"),
|
|
487
562
|
});
|
|
@@ -75,6 +75,14 @@ export function windowsJitRunnerLabels(value) {
|
|
|
75
75
|
return ["self-hosted", "Windows", "X64", windowsJitRunnerLabel(value)];
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
export function windowsJitCampaignId(value) {
|
|
79
|
+
const campaign = String(value || "").trim();
|
|
80
|
+
if (!/^win-[a-z0-9][a-z0-9-]{2,15}$/.test(campaign)) {
|
|
81
|
+
throw new Error("campaignId must be a bounded Windows campaign id");
|
|
82
|
+
}
|
|
83
|
+
return campaign;
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
export function renderWindowsJitBootstrap(template, values = {}) {
|
|
79
87
|
const runnerLabel = windowsJitRunnerLabel(values.runnerLabel);
|
|
80
88
|
const jitParameterName = String(values.jitParameterName || "").trim();
|
|
@@ -86,6 +94,7 @@ export function renderWindowsJitBootstrap(template, values = {}) {
|
|
|
86
94
|
}
|
|
87
95
|
const bounded = {
|
|
88
96
|
REGION: String(values.region || WINDOWS_EC2_JIT.region),
|
|
97
|
+
CAMPAIGN_ID: String(values.campaignId || ""),
|
|
89
98
|
JIT_PARAMETER_NAME: jitParameterName,
|
|
90
99
|
EVIDENCE_BUCKET: String(values.evidenceBucket || ""),
|
|
91
100
|
RUNNER_LABEL: runnerLabel,
|
|
@@ -99,6 +108,7 @@ export function renderWindowsJitBootstrap(template, values = {}) {
|
|
|
99
108
|
};
|
|
100
109
|
const patterns = {
|
|
101
110
|
REGION: /^us-[a-z]+-\d$/,
|
|
111
|
+
CAMPAIGN_ID: /^win-[a-z0-9][a-z0-9-]{2,43}$/,
|
|
102
112
|
EVIDENCE_BUCKET: /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/,
|
|
103
113
|
GITHUB_RUN_ID: /^\d+$/,
|
|
104
114
|
GITHUB_RUN_ATTEMPT: /^\d+$/,
|
|
@@ -184,6 +194,7 @@ export function windowsEc2JitPlan(overrides = {}) {
|
|
|
184
194
|
|
|
185
195
|
export function createWindowsJitEvidence({
|
|
186
196
|
repository,
|
|
197
|
+
campaignId,
|
|
187
198
|
sourceSha,
|
|
188
199
|
sourceRef,
|
|
189
200
|
githubRunId,
|
|
@@ -235,6 +246,7 @@ export function createWindowsJitEvidence({
|
|
|
235
246
|
phase: WINDOWS_EC2_JIT.phase,
|
|
236
247
|
provider: "aws-ec2",
|
|
237
248
|
repository,
|
|
249
|
+
campaign: { id: windowsJitCampaignId(campaignId) },
|
|
238
250
|
source: {
|
|
239
251
|
sha: exactSha(sourceSha, "sourceSha"),
|
|
240
252
|
ref: String(sourceRef || "").trim(),
|
|
@@ -286,6 +298,7 @@ export function createWindowsJitEvidence({
|
|
|
286
298
|
}
|
|
287
299
|
|
|
288
300
|
export function verifyWindowsEc2JitQualification({
|
|
301
|
+
campaignId,
|
|
289
302
|
jobs = [],
|
|
290
303
|
cancellationCleanup = {},
|
|
291
304
|
timeoutCleanup = {},
|
|
@@ -299,6 +312,7 @@ export function verifyWindowsEc2JitQualification({
|
|
|
299
312
|
} = {}) {
|
|
300
313
|
const plan = windowsEc2JitPlan();
|
|
301
314
|
const issues = [];
|
|
315
|
+
const campaign = windowsJitCampaignId(campaignId);
|
|
302
316
|
const accepted = jobs.filter(
|
|
303
317
|
(job) =>
|
|
304
318
|
job?.trusted === true &&
|
|
@@ -308,6 +322,9 @@ export function verifyWindowsEc2JitQualification({
|
|
|
308
322
|
);
|
|
309
323
|
const smokeJobs = accepted.filter((job) => job.kind === "smoke");
|
|
310
324
|
const fullJobs = accepted.filter((job) => job.kind === "full");
|
|
325
|
+
if (accepted.some((job) => job.campaignId !== campaign)) {
|
|
326
|
+
issues.push("accepted-jobs-not-bound-to-campaign");
|
|
327
|
+
}
|
|
311
328
|
if (smokeJobs.length < plan.config.minimumSmokeJobs) {
|
|
312
329
|
issues.push("runner-profile-smoke-missing");
|
|
313
330
|
}
|
|
@@ -349,6 +366,7 @@ export function verifyWindowsEc2JitQualification({
|
|
|
349
366
|
contract: AWS_WINDOWS_JIT_CONTRACT,
|
|
350
367
|
kind: "phase-verification",
|
|
351
368
|
phase: plan.config.phase,
|
|
369
|
+
campaign: { id: campaign },
|
|
352
370
|
status: issues.length ? "failed" : "passed",
|
|
353
371
|
qualifying: issues.length === 0,
|
|
354
372
|
metrics: {
|
|
@@ -35,6 +35,7 @@ export function main() {
|
|
|
35
35
|
if (mode === "evidence") {
|
|
36
36
|
const result = createWindowsJitEvidence({
|
|
37
37
|
repository: process.env.GITHUB_REPOSITORY,
|
|
38
|
+
campaignId: process.env.AWS_EC2_CAMPAIGN_ID,
|
|
38
39
|
sourceSha:
|
|
39
40
|
process.env.BUILDCHAIN_EXPECTED_SOURCE_SHA || process.env.GITHUB_SHA,
|
|
40
41
|
sourceRef:
|
|
@@ -77,6 +78,7 @@ export function main() {
|
|
|
77
78
|
fs.readFileSync(path.resolve(template), "utf8"),
|
|
78
79
|
{
|
|
79
80
|
region: arg("region", "us-east-1"),
|
|
81
|
+
campaignId: arg("campaign-id"),
|
|
80
82
|
jitParameterName: arg("jit-parameter"),
|
|
81
83
|
evidenceBucket: arg("evidence-bucket"),
|
|
82
84
|
runnerLabel: arg("runner-label"),
|
|
@@ -46,6 +46,14 @@ function normalizeFallback(value = "github") {
|
|
|
46
46
|
return fallback;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function normalizeHistoryMode(value = "shallow") {
|
|
50
|
+
const mode = String(value || "shallow").trim().toLowerCase() || "shallow";
|
|
51
|
+
if (!["shallow", "full"].includes(mode)) {
|
|
52
|
+
throw new Error(`checkout-history-mode must be shallow or full; got ${value}`);
|
|
53
|
+
}
|
|
54
|
+
return mode;
|
|
55
|
+
}
|
|
56
|
+
|
|
49
57
|
function splitRepository(repository) {
|
|
50
58
|
const match = String(repository || "").trim().match(/^([^/\s]+)\/([^/\s]+)$/);
|
|
51
59
|
if (!match) {
|
|
@@ -237,19 +245,25 @@ export function fetchSourceCommit({
|
|
|
237
245
|
sourceTreeSha = "",
|
|
238
246
|
timeoutMs,
|
|
239
247
|
env = {},
|
|
248
|
+
historyMode = readEnv("BUILDCHAIN_CHECKOUT_HISTORY_MODE", "shallow"),
|
|
240
249
|
allowFullFetchRetry = false,
|
|
241
250
|
runGit = git,
|
|
242
251
|
containsCommit = hasCommit,
|
|
243
252
|
}) {
|
|
253
|
+
const normalizedHistoryMode = normalizeHistoryMode(historyMode);
|
|
244
254
|
const fetchEnv = isolatedGitFetchEnv(env, targetPath);
|
|
245
255
|
const fetch = (refspec) => {
|
|
246
256
|
const options = { cwd: targetPath, timeoutMs, env: fetchEnv };
|
|
257
|
+
const fetchArgs = ["fetch", "--no-tags"];
|
|
258
|
+
if (normalizedHistoryMode === "shallow") fetchArgs.push("--depth=1");
|
|
259
|
+
fetchArgs.push(remoteName, refspec);
|
|
247
260
|
try {
|
|
248
|
-
runGit(
|
|
249
|
-
return
|
|
261
|
+
runGit(fetchArgs, options);
|
|
262
|
+
return normalizedHistoryMode;
|
|
250
263
|
} catch (error) {
|
|
251
264
|
if (
|
|
252
|
-
|
|
265
|
+
normalizedHistoryMode !== "shallow"
|
|
266
|
+
|| !allowFullFetchRetry
|
|
253
267
|
|| !/dumb http transport does not support shallow capabilities/i.test(
|
|
254
268
|
String(error?.message || error || ""),
|
|
255
269
|
)
|