@bitfab/sdk 0.64.16 → 0.64.17
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/cloudReplay.py +282 -24
- package/dist/{chunk-AXFKZYIY.js → chunk-G5BYSZLD.js} +11 -6
- package/dist/chunk-G5BYSZLD.js.map +1 -0
- package/dist/{chunk-PAWBCEI7.js → chunk-I3LSDFMN.js} +2 -2
- package/dist/{chunk-PAWBCEI7.js.map → chunk-I3LSDFMN.js.map} +1 -1
- package/dist/chunk-PQ6ABBRQ.js +1905 -0
- package/dist/chunk-PQ6ABBRQ.js.map +1 -0
- package/dist/{chunk-NNZAQSTW.js → chunk-QZFVHNQE.js} +2 -2
- package/dist/{chunk-LOIVELPI.js → chunk-WLGH45CM.js} +204 -89
- package/dist/chunk-WLGH45CM.js.map +1 -0
- package/dist/{chunk-I4TFQZCW.js → chunk-YHKFC7XZ.js} +16 -2
- package/dist/{chunk-I4TFQZCW.js.map → chunk-YHKFC7XZ.js.map} +1 -1
- package/dist/{http-QQ6VVHUZ.js → http-AYZZNM2L.js} +2 -2
- package/dist/{http-LV7OUBKA.js → http-DAQLSTUN.js} +2 -2
- package/dist/index.cjs +6 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/node.cjs +6 -1
- package/dist/node.cjs.map +1 -1
- package/dist/node.js +3 -3
- package/dist/replay-ARKSPOVK.js +28 -0
- package/dist/{replay-JNKXZPY7.js → replay-FFNEMPGO.js} +3 -3
- package/dist/replay-FFNEMPGO.js.map +1 -0
- package/dist/replayCli.js +3 -2
- package/dist/replayCli.js.map +1 -1
- package/dist/seedCli.js +3 -2
- package/dist/seedCli.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-AXFKZYIY.js.map +0 -1
- package/dist/chunk-LOIVELPI.js.map +0 -1
- /package/dist/{chunk-NNZAQSTW.js.map → chunk-QZFVHNQE.js.map} +0 -0
- /package/dist/{http-LV7OUBKA.js.map → http-AYZZNM2L.js.map} +0 -0
- /package/dist/{http-QQ6VVHUZ.js.map → http-DAQLSTUN.js.map} +0 -0
- /package/dist/{replay-JNKXZPY7.js.map → replay-ARKSPOVK.js.map} +0 -0
package/cloudReplay.py
CHANGED
|
@@ -36,6 +36,16 @@ SDK_LANGUAGE_ENV = "BITFAB_SDK_LANGUAGE"
|
|
|
36
36
|
CHECK_COMMAND_ENV = "BITFAB_REPLAY_CHECK"
|
|
37
37
|
EXECUTION_TARGET_ENV = "BITFAB_REPLAY_EXECUTION_TARGET"
|
|
38
38
|
HOSTED_EXECUTION_TARGET = "hosted"
|
|
39
|
+
API_KEY_ENV = "BITFAB_REPLAY_BITFAB_API_KEY"
|
|
40
|
+
PRINT_API_KEY_FLAG = "--print-api-key"
|
|
41
|
+
API_KEY_LINE = "@@bitfab:api-key "
|
|
42
|
+
KEY_INPUT = "key_id"
|
|
43
|
+
KEY_SECRET_PREFIX = "BITFAB_REPLAY_KEY_"
|
|
44
|
+
KEY_SECRET_REFERENCE = (
|
|
45
|
+
"${{ secrets[format('" + KEY_SECRET_PREFIX + "{0}', inputs." + KEY_INPUT + ")] }}"
|
|
46
|
+
)
|
|
47
|
+
API_KEY_PROBE_SECONDS = 120
|
|
48
|
+
RUN_NAME = "Bitfab replay ${{ inputs.execution_id || github.ref_name }}"
|
|
39
49
|
REQUEST_VERSION = 4
|
|
40
50
|
PLAIN_REQUEST_VERSION = 3
|
|
41
51
|
OLDEST_DISPATCHED_VERSION = 2
|
|
@@ -95,7 +105,7 @@ ENVIRONMENT_SETTING = re.compile(
|
|
|
95
105
|
PUSH_TRIGGER = re.compile(
|
|
96
106
|
r"^[ \t]*\"?(?:on\"?[ \t]*:.*\bpush\b|push\"?[ \t]*:)", re.MULTILINE
|
|
97
107
|
)
|
|
98
|
-
DISPATCH_INPUTS = ("execution_id", "request")
|
|
108
|
+
DISPATCH_INPUTS = ("execution_id", "request", KEY_INPUT)
|
|
99
109
|
BRANCH_NAME = re.compile(r"[A-Za-z0-9][A-Za-z0-9._/-]*")
|
|
100
110
|
ENV_NAME = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
|
|
101
111
|
RESERVED_ENV_PREFIXES = ("GITHUB_", "RUNNER_", "ACTIONS_", "BITFAB_REPLAY_")
|
|
@@ -204,6 +214,14 @@ values --cloud-env sets. version and id are optional: version names the request
|
|
|
204
214
|
format (4 when env is set, otherwise 3; 2 is still accepted), and id is the
|
|
205
215
|
execution UUID, which must match an execution_id input when the workflow has one.
|
|
206
216
|
|
|
217
|
+
The replay runs with the Bitfab API key it would use on your machine, so the
|
|
218
|
+
experiment is recorded as yours. Before dispatch, the replay's registry is loaded
|
|
219
|
+
locally to read its client's key, which the GitHub CLI stores as a GitHub secret
|
|
220
|
+
named for this execution and deletes once the replay starts. Without the GitHub
|
|
221
|
+
CLI, without permission to manage the repository's Actions secrets, with no key
|
|
222
|
+
resolved locally, or with a workflow that declares no key_id input, the run uses
|
|
223
|
+
the workflow's BITFAB_API_KEY secret instead.
|
|
224
|
+
|
|
207
225
|
The runner's job exits with the replay's exit code, so the Actions run fails when
|
|
208
226
|
the replay does. Traces that errored fail it only under --fail-on-error.
|
|
209
227
|
|
|
@@ -216,7 +234,10 @@ Set up once:
|
|
|
216
234
|
--cloud-init [--secret NAME ...] [--environment NAME] [--run COMMAND]
|
|
217
235
|
[--runs-on LABEL] [--secret-prefix PREFIX] [--check COMMAND]
|
|
218
236
|
Writes .github/workflows/bitfab-replay.yml, the only file cloud replay keeps
|
|
219
|
-
in the repository, and lists what is left to do.
|
|
237
|
+
in the repository, and lists what is left to do. On an existing workflow it
|
|
238
|
+
adds only the lines a newer SDK needs and lists what it changed.
|
|
239
|
+
--cloud-update [same options]
|
|
240
|
+
The same command as --cloud-init, for bringing an existing setup up to date.
|
|
220
241
|
--cloud-secrets --env-file FILE [NAME ...] [--environment NAME] [--dry-run]
|
|
221
242
|
Copies local values into the GitHub secrets the workflow reads.
|
|
222
243
|
|
|
@@ -1189,6 +1210,7 @@ def print_outcome(found):
|
|
|
1189
1210
|
|
|
1190
1211
|
|
|
1191
1212
|
def cleanup(root, record):
|
|
1213
|
+
remove_key(record)
|
|
1192
1214
|
if record.get("cleaned"):
|
|
1193
1215
|
return
|
|
1194
1216
|
if record.get("snapshot") is False:
|
|
@@ -1335,10 +1357,110 @@ def submit(root, repo, workflow, parsed, path):
|
|
|
1335
1357
|
) from error
|
|
1336
1358
|
record["state"] = "dispatch_unknown"
|
|
1337
1359
|
save(path, record)
|
|
1338
|
-
|
|
1339
|
-
|
|
1360
|
+
text = within(root, f"{WORKFLOW_DIRECTORY}/{workflow}").read_text()
|
|
1361
|
+
declared = declared_inputs(text)
|
|
1362
|
+
key_inputs = store_key(root, record, declared, text)
|
|
1363
|
+
save(path, record)
|
|
1364
|
+
return dispatch(
|
|
1365
|
+
record,
|
|
1366
|
+
{"execution_id": execution_id, "request": "-", **key_inputs},
|
|
1367
|
+
declared,
|
|
1340
1368
|
)
|
|
1341
|
-
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
def local_api_key(root, request):
|
|
1372
|
+
try:
|
|
1373
|
+
output = command(
|
|
1374
|
+
[*replay_command(), *request["args"], PRINT_API_KEY_FLAG],
|
|
1375
|
+
cwd=within(root, request["cwd"]),
|
|
1376
|
+
timeout=API_KEY_PROBE_SECONDS,
|
|
1377
|
+
input="",
|
|
1378
|
+
)
|
|
1379
|
+
except (ValueError, RuntimeError, OSError, subprocess.SubprocessError):
|
|
1380
|
+
return None
|
|
1381
|
+
for line in reversed(output.splitlines()):
|
|
1382
|
+
if not line.startswith(API_KEY_LINE):
|
|
1383
|
+
continue
|
|
1384
|
+
try:
|
|
1385
|
+
key = json.loads(line.removeprefix(API_KEY_LINE)).get("apiKey")
|
|
1386
|
+
except (ValueError, AttributeError):
|
|
1387
|
+
return None
|
|
1388
|
+
return key if isinstance(key, str) and key.strip() else None
|
|
1389
|
+
return None
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
def store_key(root, record, declared, workflow_text):
|
|
1393
|
+
shared = "so the run uses the workflow's BITFAB_API_KEY secret"
|
|
1394
|
+
if KEY_INPUT not in declared:
|
|
1395
|
+
progress(
|
|
1396
|
+
f"The workflow takes no {KEY_INPUT} input, {shared}. To replay with your own Bitfab API key, run bitfab-replay --cloud-init, which adds the {KEY_INPUT} input and {API_KEY_ENV} to the workflow"
|
|
1397
|
+
)
|
|
1398
|
+
return {}
|
|
1399
|
+
if shutil.which("gh") is None:
|
|
1400
|
+
progress(
|
|
1401
|
+
f"Replaying with your own Bitfab API key needs the GitHub CLI (https://cli.github.com) to store it as a GitHub secret, {shared}"
|
|
1402
|
+
)
|
|
1403
|
+
return {}
|
|
1404
|
+
key = local_api_key(root, record["request"])
|
|
1405
|
+
if key is None:
|
|
1406
|
+
progress(
|
|
1407
|
+
f"The replay's Bitfab client resolved no API key on this machine, {shared}"
|
|
1408
|
+
)
|
|
1409
|
+
return {}
|
|
1410
|
+
key_id = record["id"].replace("-", "").upper()
|
|
1411
|
+
name = KEY_SECRET_PREFIX + key_id
|
|
1412
|
+
configured = ENVIRONMENT_SETTING.search(workflow_text)
|
|
1413
|
+
environment = configured[1] if configured else None
|
|
1414
|
+
try:
|
|
1415
|
+
command(
|
|
1416
|
+
[
|
|
1417
|
+
"gh",
|
|
1418
|
+
"secret",
|
|
1419
|
+
"set",
|
|
1420
|
+
name,
|
|
1421
|
+
"--repo",
|
|
1422
|
+
record["repository"],
|
|
1423
|
+
*(["--env", environment] if environment else []),
|
|
1424
|
+
],
|
|
1425
|
+
input=key,
|
|
1426
|
+
env={**os.environ, "GH_TOKEN": github_access()["token"]},
|
|
1427
|
+
)
|
|
1428
|
+
except (ValueError, RuntimeError, OSError, subprocess.SubprocessError):
|
|
1429
|
+
progress(
|
|
1430
|
+
f"Could not store your Bitfab API key as the GitHub secret {name}, which needs permission to manage the repository's Actions secrets, {shared}"
|
|
1431
|
+
)
|
|
1432
|
+
return {}
|
|
1433
|
+
record["keySecret"] = {"name": name, "environment": environment}
|
|
1434
|
+
progress(
|
|
1435
|
+
f"Replaying with the Bitfab API key from this machine, stored as the GitHub secret {name} until the replay starts"
|
|
1436
|
+
)
|
|
1437
|
+
return {KEY_INPUT: key_id}
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
def remove_key(record):
|
|
1441
|
+
secret = record.get("keySecret")
|
|
1442
|
+
if not secret or secret.get("removed"):
|
|
1443
|
+
return
|
|
1444
|
+
try:
|
|
1445
|
+
command(
|
|
1446
|
+
[
|
|
1447
|
+
"gh",
|
|
1448
|
+
"secret",
|
|
1449
|
+
"delete",
|
|
1450
|
+
secret["name"],
|
|
1451
|
+
"--repo",
|
|
1452
|
+
record["repository"],
|
|
1453
|
+
*(["--env", secret["environment"]] if secret["environment"] else []),
|
|
1454
|
+
],
|
|
1455
|
+
env={**os.environ, "GH_TOKEN": github_access()["token"]},
|
|
1456
|
+
)
|
|
1457
|
+
except (ValueError, RuntimeError, OSError, subprocess.SubprocessError) as error:
|
|
1458
|
+
if getattr(error, "http_status", None) != 404:
|
|
1459
|
+
progress(
|
|
1460
|
+
f"Could not delete the GitHub secret {secret['name']}; delete it at {secrets_page(record['repository'], secret['environment'])}"
|
|
1461
|
+
)
|
|
1462
|
+
return
|
|
1463
|
+
secret["removed"] = True
|
|
1342
1464
|
|
|
1343
1465
|
|
|
1344
1466
|
def declared_inputs(text):
|
|
@@ -1446,7 +1568,8 @@ def submit_as_is(root, repo, workflow, parsed, path):
|
|
|
1446
1568
|
preflight(
|
|
1447
1569
|
repo, workflow, pushes=False, full_output=request.get("fullOutput") is True
|
|
1448
1570
|
)
|
|
1449
|
-
|
|
1571
|
+
text = branch_workflow(repo, workflow, sha)
|
|
1572
|
+
declared = declared_inputs(text)
|
|
1450
1573
|
if "request" not in declared:
|
|
1451
1574
|
raise ValueError(
|
|
1452
1575
|
f"{WORKFLOW_DIRECTORY}/{workflow} on {branch} declares no request input, so it cannot take a replay; add request (type: string, required: false) under on.workflow_dispatch.inputs and land it on {branch}"
|
|
@@ -1466,9 +1589,11 @@ def submit_as_is(root, repo, workflow, parsed, path):
|
|
|
1466
1589
|
progress(
|
|
1467
1590
|
f"Replaying {branch} at {sha[:12]} as it is on GitHub, without local changes. Execution {execution_id}; resume with --cloud-watch {execution_id}"
|
|
1468
1591
|
)
|
|
1592
|
+
key_inputs = store_key(root, record, declared, text)
|
|
1593
|
+
save(path, record)
|
|
1469
1594
|
return dispatch(
|
|
1470
1595
|
record,
|
|
1471
|
-
{"execution_id": execution_id, "request": json.dumps(request)},
|
|
1596
|
+
{"execution_id": execution_id, "request": json.dumps(request), **key_inputs},
|
|
1472
1597
|
declared,
|
|
1473
1598
|
run_details=True,
|
|
1474
1599
|
)
|
|
@@ -1477,10 +1602,11 @@ def submit_as_is(root, repo, workflow, parsed, path):
|
|
|
1477
1602
|
def report_steps(record, shown):
|
|
1478
1603
|
job = replay_job(record)
|
|
1479
1604
|
for step in (job or {}).get("steps", []):
|
|
1480
|
-
if (
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1605
|
+
if step.get("status") not in ("in_progress", "completed"):
|
|
1606
|
+
continue
|
|
1607
|
+
if "--cloud-execute" in (step.get("name") or ""):
|
|
1608
|
+
remove_key(record)
|
|
1609
|
+
if step.get("name") not in shown:
|
|
1484
1610
|
shown.add(step.get("name"))
|
|
1485
1611
|
progress(step.get("name"))
|
|
1486
1612
|
|
|
@@ -1701,6 +1827,11 @@ def execute():
|
|
|
1701
1827
|
request = runner_request(root, commit)
|
|
1702
1828
|
directory = within(root, request["cwd"])
|
|
1703
1829
|
check_secrets(root)
|
|
1830
|
+
if os.environ.get(API_KEY_ENV, "").strip():
|
|
1831
|
+
print(
|
|
1832
|
+
"Replaying with the Bitfab API key of the person who started this run",
|
|
1833
|
+
flush=True,
|
|
1834
|
+
)
|
|
1704
1835
|
environment = request.get("env") or {}
|
|
1705
1836
|
if environment:
|
|
1706
1837
|
workflow_environment_conflicts(root, environment)
|
|
@@ -2376,12 +2507,13 @@ def workflow_document(steps, run, directory, env, *, runs_on, environment):
|
|
|
2376
2507
|
]
|
|
2377
2508
|
return {
|
|
2378
2509
|
"name": "Bitfab replay",
|
|
2379
|
-
"run-name":
|
|
2510
|
+
"run-name": RUN_NAME,
|
|
2380
2511
|
"on": {
|
|
2381
2512
|
"workflow_dispatch": {
|
|
2382
2513
|
"inputs": {
|
|
2383
2514
|
"execution_id": {"type": "string", "required": False},
|
|
2384
2515
|
"request": {"type": "string", "required": False},
|
|
2516
|
+
KEY_INPUT: {"type": "string", "required": False},
|
|
2385
2517
|
}
|
|
2386
2518
|
}
|
|
2387
2519
|
},
|
|
@@ -2390,10 +2522,141 @@ def workflow_document(steps, run, directory, env, *, runs_on, environment):
|
|
|
2390
2522
|
}
|
|
2391
2523
|
|
|
2392
2524
|
|
|
2393
|
-
def
|
|
2525
|
+
def indentation(line):
|
|
2526
|
+
return len(line) - len(line.lstrip(" \t"))
|
|
2527
|
+
|
|
2528
|
+
|
|
2529
|
+
def block_end(lines, start, indent):
|
|
2530
|
+
end = start + 1
|
|
2531
|
+
while end < len(lines) and (
|
|
2532
|
+
not lines[end].strip() or indentation(lines[end]) > indent
|
|
2533
|
+
):
|
|
2534
|
+
end += 1
|
|
2535
|
+
while end > start + 1 and not lines[end - 1].strip():
|
|
2536
|
+
end -= 1
|
|
2537
|
+
return end
|
|
2538
|
+
|
|
2539
|
+
|
|
2540
|
+
def add_input(lines, name):
|
|
2541
|
+
for index, line in enumerate(lines):
|
|
2542
|
+
if not re.fullmatch(r'[ \t]*"?request"?[ \t]*:[ \t]*', line):
|
|
2543
|
+
continue
|
|
2544
|
+
indent = indentation(line)
|
|
2545
|
+
child = " " * (
|
|
2546
|
+
indentation(lines[index + 1])
|
|
2547
|
+
if block_end(lines, index, indent) > index + 1
|
|
2548
|
+
else indent + 2
|
|
2549
|
+
)
|
|
2550
|
+
end = block_end(lines, index, indent - 1)
|
|
2551
|
+
lines[end:end] = [
|
|
2552
|
+
f"{line[:indent]}{name}:",
|
|
2553
|
+
f"{child}type: string",
|
|
2554
|
+
f"{child}required: false",
|
|
2555
|
+
]
|
|
2556
|
+
return True
|
|
2557
|
+
return False
|
|
2558
|
+
|
|
2559
|
+
|
|
2560
|
+
def add_run_name(lines):
|
|
2561
|
+
named = next(
|
|
2562
|
+
(
|
|
2563
|
+
index
|
|
2564
|
+
for index, line in enumerate(lines)
|
|
2565
|
+
if re.match(r'"?name"?[ \t]*:', line)
|
|
2566
|
+
),
|
|
2567
|
+
-1,
|
|
2568
|
+
)
|
|
2569
|
+
lines.insert(named + 1, f"run-name: {json.dumps(RUN_NAME)}")
|
|
2570
|
+
|
|
2571
|
+
|
|
2572
|
+
def replay_step_key(lines):
|
|
2573
|
+
for index, line in enumerate(lines):
|
|
2574
|
+
if "--cloud-execute" not in line:
|
|
2575
|
+
continue
|
|
2576
|
+
for above in range(index, -1, -1):
|
|
2577
|
+
match = re.match(r"([ \t]*)(- +)?run[ \t]*:", lines[above])
|
|
2578
|
+
if match and indentation(lines[above]) <= indentation(line):
|
|
2579
|
+
return above, len(match[1]) + len(match[2] or "")
|
|
2580
|
+
return None
|
|
2581
|
+
|
|
2582
|
+
|
|
2583
|
+
def add_key_env(lines):
|
|
2584
|
+
found = replay_step_key(lines)
|
|
2585
|
+
if found is None:
|
|
2586
|
+
return False
|
|
2587
|
+
run, key = found
|
|
2588
|
+
start = run
|
|
2589
|
+
while start > 0 and not re.match(r"[ \t]*- ", lines[start]):
|
|
2590
|
+
start -= 1
|
|
2591
|
+
end = block_end(lines, run, key - 1)
|
|
2592
|
+
entry = f'{API_KEY_ENV}: "{KEY_SECRET_REFERENCE}"'
|
|
2593
|
+
for index in range(start, end):
|
|
2594
|
+
line = lines[index]
|
|
2595
|
+
if not re.fullmatch(r"[ \t]*(- +)?env[ \t]*:[ \t]*", line):
|
|
2596
|
+
continue
|
|
2597
|
+
if len(line) - len(line.lstrip(" \t-")) != key:
|
|
2598
|
+
continue
|
|
2599
|
+
entries = block_end(lines, index, key)
|
|
2600
|
+
child = indentation(lines[index + 1]) if entries > index + 1 else key + 2
|
|
2601
|
+
lines.insert(entries, " " * child + entry)
|
|
2602
|
+
return True
|
|
2603
|
+
lines[end:end] = [" " * key + "env:", " " * (key + 2) + entry]
|
|
2604
|
+
return True
|
|
2605
|
+
|
|
2606
|
+
|
|
2607
|
+
def update_workflow(root, path):
|
|
2608
|
+
target = within(root, path)
|
|
2609
|
+
text = target.read_text()
|
|
2610
|
+
lines = text.split("\n")
|
|
2611
|
+
updated, by_hand = [], []
|
|
2612
|
+
declared = declared_inputs(text)
|
|
2613
|
+
for name in ("execution_id", KEY_INPUT):
|
|
2614
|
+
if name in declared:
|
|
2615
|
+
continue
|
|
2616
|
+
if add_input(lines, name):
|
|
2617
|
+
updated.append(f"Added the {name} input")
|
|
2618
|
+
else:
|
|
2619
|
+
by_hand.append(
|
|
2620
|
+
f"{name} (type: string, required: false) under on.workflow_dispatch.inputs"
|
|
2621
|
+
)
|
|
2622
|
+
if not re.search(r'^"?run-name"?[ \t]*:', text, re.MULTILINE):
|
|
2623
|
+
add_run_name(lines)
|
|
2624
|
+
updated.append("Named each run after its execution")
|
|
2625
|
+
if API_KEY_ENV not in text:
|
|
2626
|
+
if add_key_env(lines):
|
|
2627
|
+
updated.append(f"Added {API_KEY_ENV} to the replay step's env")
|
|
2628
|
+
else:
|
|
2629
|
+
by_hand.append(
|
|
2630
|
+
f'{API_KEY_ENV}: "{KEY_SECRET_REFERENCE}" in the replay step\'s env'
|
|
2631
|
+
)
|
|
2632
|
+
if updated:
|
|
2633
|
+
target.write_text("\n".join(lines))
|
|
2634
|
+
steps = []
|
|
2635
|
+
if updated:
|
|
2636
|
+
steps.append(
|
|
2637
|
+
f"Commit {path}. A --cloud replay runs the workflow in its own snapshot, so the next one uses it; --cloud-ref replays need it on the branch they replay"
|
|
2638
|
+
)
|
|
2639
|
+
if by_hand:
|
|
2640
|
+
steps.append(
|
|
2641
|
+
"Cloud replay could not place these lines, so add them by hand: "
|
|
2642
|
+
+ "; ".join(by_hand)
|
|
2643
|
+
)
|
|
2644
|
+
if not steps:
|
|
2645
|
+
steps.append(
|
|
2646
|
+
"Already set up. Edit the workflow directly to change its install steps, secrets, runner, or Environment"
|
|
2647
|
+
)
|
|
2648
|
+
return {
|
|
2649
|
+
"files": [path] if updated else [],
|
|
2650
|
+
"workflow": path,
|
|
2651
|
+
"updated": updated,
|
|
2652
|
+
"next": steps,
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
|
|
2656
|
+
def initialize(argv, name="--cloud-init"):
|
|
2394
2657
|
parser = argparse.ArgumentParser(
|
|
2395
|
-
prog="bitfab-replay
|
|
2396
|
-
description="Write .github/workflows/bitfab-replay.yml, the only file cloud replay keeps in the repository, and list what is left to do. It detects the runtime, package manager, install command, and replay command from the directory you run it in.
|
|
2658
|
+
prog=f"bitfab-replay {name}",
|
|
2659
|
+
description="Write .github/workflows/bitfab-replay.yml, the only file cloud replay keeps in the repository, and list what is left to do. It detects the runtime, package manager, install command, and replay command from the directory you run it in. On an existing workflow it only adds what a newer SDK needs (the execution_id and key_id inputs, the run name, and BITFAB_REPLAY_BITFAB_API_KEY) and lists what it changed; edit it directly for anything else.",
|
|
2397
2660
|
)
|
|
2398
2661
|
parser.add_argument(
|
|
2399
2662
|
"--secret",
|
|
@@ -2425,13 +2688,7 @@ def initialize(argv):
|
|
|
2425
2688
|
refuse_old_setup(root)
|
|
2426
2689
|
existing = find_workflow(root)
|
|
2427
2690
|
if existing is not None:
|
|
2428
|
-
return {
|
|
2429
|
-
"files": [],
|
|
2430
|
-
"workflow": f"{WORKFLOW_DIRECTORY}/{existing}",
|
|
2431
|
-
"next": [
|
|
2432
|
-
"Already set up. Edit the workflow directly to change its install steps, secrets, runner, or Environment"
|
|
2433
|
-
],
|
|
2434
|
-
}
|
|
2691
|
+
return update_workflow(root, f"{WORKFLOW_DIRECTORY}/{existing}")
|
|
2435
2692
|
prefix = args.secret_prefix
|
|
2436
2693
|
if prefix and not re.fullmatch(r"[A-Z][A-Z0-9_]*_", prefix):
|
|
2437
2694
|
raise ValueError(
|
|
@@ -2450,6 +2707,7 @@ def initialize(argv):
|
|
|
2450
2707
|
'Pass --run with the command that starts your registry program on the runner, such as --run "go run ./cmd/registry"'
|
|
2451
2708
|
)
|
|
2452
2709
|
env = {name: "${{ secrets." + prefix + name + " }}" for name in names}
|
|
2710
|
+
env[API_KEY_ENV] = KEY_SECRET_REFERENCE
|
|
2453
2711
|
if args.check:
|
|
2454
2712
|
env[CHECK_COMMAND_ENV] = args.check
|
|
2455
2713
|
path = f"{WORKFLOW_DIRECTORY}/{WORKFLOW}"
|
|
@@ -2506,8 +2764,8 @@ def print_json(value):
|
|
|
2506
2764
|
def main():
|
|
2507
2765
|
argv = sys.argv[1:]
|
|
2508
2766
|
try:
|
|
2509
|
-
if argv[:1]
|
|
2510
|
-
print_json(initialize(argv[1:]))
|
|
2767
|
+
if argv[:1] in (["--cloud-init"], ["--cloud-update"]):
|
|
2768
|
+
print_json(initialize(argv[1:], argv[0]))
|
|
2511
2769
|
return 0
|
|
2512
2770
|
if argv[:1] == ["--cloud-secrets"]:
|
|
2513
2771
|
print_json(configure_secrets(argv[1:]))
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
serializeValue,
|
|
18
18
|
toJsonSafe,
|
|
19
19
|
toJsonSafeReport
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-QZFVHNQE.js";
|
|
21
21
|
import {
|
|
22
22
|
BitfabError,
|
|
23
23
|
CONTENT_OFF_KEY,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
readEnv,
|
|
37
37
|
recordCallerTraceMetadata,
|
|
38
38
|
retireTraceMetadata
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-I3LSDFMN.js";
|
|
40
40
|
import {
|
|
41
41
|
__privateAdd,
|
|
42
42
|
__privateGet,
|
|
@@ -4344,6 +4344,7 @@ function readEnv2(name) {
|
|
|
4344
4344
|
return void 0;
|
|
4345
4345
|
}
|
|
4346
4346
|
var CAPTURE_ENABLED_ENV = "BITFAB_CAPTURE_ENABLED";
|
|
4347
|
+
var REPLAY_API_KEY_ENV = "BITFAB_REPLAY_BITFAB_API_KEY";
|
|
4347
4348
|
var TRUE_ENV_VALUES = /* @__PURE__ */ new Set(["1", "true", "yes"]);
|
|
4348
4349
|
var FALSE_ENV_VALUES = /* @__PURE__ */ new Set(["0", "false", "no"]);
|
|
4349
4350
|
function readBooleanEnv(name) {
|
|
@@ -4858,6 +4859,10 @@ var Bitfab = class {
|
|
|
4858
4859
|
* early resolve that found nothing never poisons a later one.
|
|
4859
4860
|
*/
|
|
4860
4861
|
findApiKey(callsKeyFunction) {
|
|
4862
|
+
const replayKey = readEnv2(REPLAY_API_KEY_ENV);
|
|
4863
|
+
if (replayKey && replayKey.trim() !== "") {
|
|
4864
|
+
return replayKey;
|
|
4865
|
+
}
|
|
4861
4866
|
if (this.resolvedApiKey !== void 0) {
|
|
4862
4867
|
return this.resolvedApiKey;
|
|
4863
4868
|
}
|
|
@@ -6302,7 +6307,7 @@ var Bitfab = class {
|
|
|
6302
6307
|
await runWithSeedContext({ traceId }, async () => target(...args));
|
|
6303
6308
|
} finally {
|
|
6304
6309
|
try {
|
|
6305
|
-
const { flushTraces: flushTraces2 } = await import("./http-
|
|
6310
|
+
const { flushTraces: flushTraces2 } = await import("./http-DAQLSTUN.js");
|
|
6306
6311
|
await flushTraces2(3e4);
|
|
6307
6312
|
} finally {
|
|
6308
6313
|
unrecorded = activeTraceStates.delete(traceId);
|
|
@@ -6361,7 +6366,7 @@ var Bitfab = class {
|
|
|
6361
6366
|
`Function is wrapped with trace function key '${wrappedKey}' but replay was called with '${traceFunctionKey}'. Pass matching keys, or pass the unwrapped function to replay it under the explicit key.`
|
|
6362
6367
|
);
|
|
6363
6368
|
}
|
|
6364
|
-
const { replay: doReplay } = await import("./replay-
|
|
6369
|
+
const { replay: doReplay } = await import("./replay-FFNEMPGO.js");
|
|
6365
6370
|
return doReplay(
|
|
6366
6371
|
this.httpClient,
|
|
6367
6372
|
this.serviceUrl,
|
|
@@ -6707,7 +6712,7 @@ async function seedFromRegistry(registry, pipeline, cases, options = {}) {
|
|
|
6707
6712
|
sessionId: seedCase.sessionId
|
|
6708
6713
|
})
|
|
6709
6714
|
);
|
|
6710
|
-
const { flushTraces: flushTraces2 } = await import("./http-
|
|
6715
|
+
const { flushTraces: flushTraces2 } = await import("./http-DAQLSTUN.js");
|
|
6711
6716
|
await flushTraces2(3e4);
|
|
6712
6717
|
return { pipeline, traceFunctionKey, traceIds };
|
|
6713
6718
|
}
|
|
@@ -6752,4 +6757,4 @@ export {
|
|
|
6752
6757
|
reseedFromRegistry,
|
|
6753
6758
|
seedFromRegistry
|
|
6754
6759
|
};
|
|
6755
|
-
//# sourceMappingURL=chunk-
|
|
6760
|
+
//# sourceMappingURL=chunk-G5BYSZLD.js.map
|