bitfab 0.73.0 → 0.73.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.
- checksums.yaml +4 -4
- data/lib/bitfab/cloudReplay.py +162 -11
- data/lib/bitfab/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23076d3ce8682540fa7b9104949899c4292d5ac8aab6cd3b8a8adb77ff379f5e
|
|
4
|
+
data.tar.gz: 1464557fde7f948ac8aeb34ab4c66d9c947dd30b352d8a2862d74a835ca3d061
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 652fd2e9a0285090fff5055b615f04ba48d48b49de4ba27fd4256f59f660d58ea2d4afb6d128eaf9b4f50cbe0a61d0ca022cc90ab59db76d1e0206f5ac11c3c7
|
|
7
|
+
data.tar.gz: b40ea515566dcae4643ad52b9732244b6a8663a43db9b092cebc7af5360de7df95a77e15a91231ce06b5dc1acb801f3933f69a0cb2072ec0c03d74017c79bc05
|
data/lib/bitfab/cloudReplay.py
CHANGED
|
@@ -152,6 +152,11 @@ CODE_CHANGE_FLAG = "--code-change"
|
|
|
152
152
|
CODE_CHANGE_BASE_FLAG = "--code-change-base"
|
|
153
153
|
BRANCH_CODE_CHANGES = ("last-branch", "default-branch", "ref")
|
|
154
154
|
GIT_CODE_CHANGES = ("working-tree", *BRANCH_CODE_CHANGES)
|
|
155
|
+
FETCHED_CODE_CHANGES = ("default-branch", "ref")
|
|
156
|
+
CHECKOUT_ONLY_CODE_CHANGES = ("working-tree", "last-branch")
|
|
157
|
+
GIT_TOKEN_ENV = "BITFAB_REPLAY_GIT_TOKEN"
|
|
158
|
+
GIT_TOKEN_VALUE = "${{ github.token }}"
|
|
159
|
+
FETCH_SECONDS = 600
|
|
155
160
|
DESCRIBED_REF = re.compile(r"[^\x00-\x1f\x7f]{1,200}")
|
|
156
161
|
CHECKOUT_DEPTH = 2
|
|
157
162
|
CODE_CHANGE_PATH_ENV = "BITFAB_CODE_CHANGE_PATH"
|
|
@@ -223,9 +228,12 @@ Options added by --cloud:
|
|
|
223
228
|
checkout of the repository, with GitHub access that can
|
|
224
229
|
read it and run Actions (a job token needs contents: read
|
|
225
230
|
and actions: write, passed as GH_TOKEN), and a workflow on
|
|
226
|
-
BRANCH that declares a request input. With no snapshot
|
|
227
|
-
|
|
228
|
-
|
|
231
|
+
BRANCH that declares a request input. With no snapshot,
|
|
232
|
+
--code-change default-branch and ref fetch their base on
|
|
233
|
+
the runner with the run's GitHub token, which the
|
|
234
|
+
workflow passes as BITFAB_REPLAY_GIT_TOKEN and the
|
|
235
|
+
replay never sees; working-tree and last-branch are
|
|
236
|
+
refused, as they are on a dispatched run.
|
|
229
237
|
|
|
230
238
|
A workflow can also dispatch bitfab-replay.yml itself on any branch other than a
|
|
231
239
|
bitfab-replay/ snapshot branch, passing the replay as JSON in the request input:
|
|
@@ -1048,9 +1056,15 @@ def chosen_base_point(root, chosen):
|
|
|
1048
1056
|
|
|
1049
1057
|
def refuse_git_code_change(replay, where):
|
|
1050
1058
|
kind = flag_value(replay, CODE_CHANGE_FLAG)
|
|
1051
|
-
if kind in
|
|
1059
|
+
if kind in CHECKOUT_ONLY_CODE_CHANGES:
|
|
1052
1060
|
raise ValueError(
|
|
1053
|
-
f"{where} as it is on GitHub, with no snapshot of a checkout, so --code-change {kind} has nothing to compare against there; pass --code-change skip, or --code-change manual with --code-change-file, or start it with bitfab-replay --cloud and no --cloud-ref, which resolves {kind} from your checkout before upload"
|
|
1061
|
+
f"{where} as it is on GitHub, with no snapshot of a checkout, so --code-change {kind} has nothing to compare against there; pass --code-change default-branch, --code-change ref with {CODE_CHANGE_BASE_FLAG}, --code-change skip, or --code-change manual with --code-change-file, or start it with bitfab-replay --cloud and no --cloud-ref, which resolves {kind} from your checkout before upload"
|
|
1062
|
+
)
|
|
1063
|
+
if kind == "ref" and not BRANCH_NAME.fullmatch(
|
|
1064
|
+
flag_value(replay, CODE_CHANGE_BASE_FLAG) or ""
|
|
1065
|
+
):
|
|
1066
|
+
raise ValueError(
|
|
1067
|
+
f"{where} as it is on GitHub, so --code-change ref needs {CODE_CHANGE_BASE_FLAG} naming a branch or commit SHA on GitHub to fetch there"
|
|
1054
1068
|
)
|
|
1055
1069
|
|
|
1056
1070
|
|
|
@@ -2069,14 +2083,29 @@ def snapshot_request(root, commit):
|
|
|
2069
2083
|
return request
|
|
2070
2084
|
|
|
2071
2085
|
|
|
2072
|
-
def
|
|
2086
|
+
def dispatch_event():
|
|
2073
2087
|
path = os.environ.get("GITHUB_EVENT_PATH")
|
|
2074
2088
|
if not path or not Path(path).is_file():
|
|
2075
2089
|
return {}
|
|
2076
|
-
|
|
2090
|
+
event = json.loads(Path(path).read_text())
|
|
2091
|
+
return event if isinstance(event, dict) else {}
|
|
2092
|
+
|
|
2093
|
+
|
|
2094
|
+
def dispatched_inputs():
|
|
2095
|
+
inputs = dispatch_event().get("inputs")
|
|
2077
2096
|
return inputs if isinstance(inputs, dict) else {}
|
|
2078
2097
|
|
|
2079
2098
|
|
|
2099
|
+
def event_default_branch():
|
|
2100
|
+
repository = dispatch_event().get("repository")
|
|
2101
|
+
branch = repository.get("default_branch") if isinstance(repository, dict) else None
|
|
2102
|
+
if not isinstance(branch, str) or not BRANCH_NAME.fullmatch(branch):
|
|
2103
|
+
raise ValueError(
|
|
2104
|
+
"The run's event names no default branch, so --code-change default-branch has nothing to compare against; pass --code-change ref with --code-change-base"
|
|
2105
|
+
)
|
|
2106
|
+
return branch
|
|
2107
|
+
|
|
2108
|
+
|
|
2080
2109
|
def decode_request(text):
|
|
2081
2110
|
try:
|
|
2082
2111
|
return json.loads(text)
|
|
@@ -2279,6 +2308,72 @@ def runner_code_change(root, base, commit, request):
|
|
|
2279
2308
|
return change or {"files": []}
|
|
2280
2309
|
|
|
2281
2310
|
|
|
2311
|
+
def git_token_environment(token):
|
|
2312
|
+
environment = {**os.environ, "GIT_TERMINAL_PROMPT": "0"}
|
|
2313
|
+
if not token:
|
|
2314
|
+
return environment
|
|
2315
|
+
basic = base64.b64encode(f"x-access-token:{token}".encode()).decode()
|
|
2316
|
+
return {
|
|
2317
|
+
**environment,
|
|
2318
|
+
"GIT_CONFIG_COUNT": "1",
|
|
2319
|
+
"GIT_CONFIG_KEY_0": "http.https://github.com/.extraheader",
|
|
2320
|
+
"GIT_CONFIG_VALUE_0": f"AUTHORIZATION: basic {basic}",
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
|
|
2324
|
+
def fetched_base(root, ref, token):
|
|
2325
|
+
shallow = quiet_git(root, "rev-parse", "--is-shallow-repository") == "true"
|
|
2326
|
+
try:
|
|
2327
|
+
command(
|
|
2328
|
+
[
|
|
2329
|
+
"git",
|
|
2330
|
+
"-C",
|
|
2331
|
+
str(root),
|
|
2332
|
+
"fetch",
|
|
2333
|
+
"-q",
|
|
2334
|
+
"--no-tags",
|
|
2335
|
+
*(["--unshallow"] if shallow else []),
|
|
2336
|
+
"origin",
|
|
2337
|
+
ref,
|
|
2338
|
+
],
|
|
2339
|
+
env=git_token_environment(token),
|
|
2340
|
+
timeout=FETCH_SECONDS,
|
|
2341
|
+
)
|
|
2342
|
+
except (CommandError, subprocess.TimeoutExpired) as error:
|
|
2343
|
+
hint = (
|
|
2344
|
+
"check that it exists on GitHub"
|
|
2345
|
+
if token
|
|
2346
|
+
else f"the workflow passes no {GIT_TOKEN_ENV} to fetch it with, so run bitfab-replay --cloud-update and commit the workflow it updates"
|
|
2347
|
+
)
|
|
2348
|
+
raise ValueError(
|
|
2349
|
+
f"Could not fetch {ref} from origin to compare against ({error}); {hint}, or pass --code-change skip"
|
|
2350
|
+
) from None
|
|
2351
|
+
base = quiet_git(root, "merge-base", "HEAD", "FETCH_HEAD")
|
|
2352
|
+
if not base:
|
|
2353
|
+
raise ValueError(
|
|
2354
|
+
f"This branch shares no history with {ref}, so there is nothing to compare against"
|
|
2355
|
+
)
|
|
2356
|
+
return base
|
|
2357
|
+
|
|
2358
|
+
|
|
2359
|
+
def dispatched_code_change(root, commit, request, token):
|
|
2360
|
+
args = request["args"]
|
|
2361
|
+
kind = flag_value(args, CODE_CHANGE_FLAG)
|
|
2362
|
+
if kind not in FETCHED_CODE_CHANGES:
|
|
2363
|
+
return None
|
|
2364
|
+
ref = (
|
|
2365
|
+
flag_value(args, CODE_CHANGE_BASE_FLAG)
|
|
2366
|
+
if kind == "ref"
|
|
2367
|
+
else event_default_branch()
|
|
2368
|
+
)
|
|
2369
|
+
print(f"Fetching {ref} to record the code change against it", flush=True)
|
|
2370
|
+
base = fetched_base(root, ref, token)
|
|
2371
|
+
change = snapshot_code_change(
|
|
2372
|
+
root, base, commit, replay_name(args), f"vs {ref}", commit
|
|
2373
|
+
)
|
|
2374
|
+
return change or {"files": []}
|
|
2375
|
+
|
|
2376
|
+
|
|
2282
2377
|
def write_code_change(change):
|
|
2283
2378
|
handle, path = tempfile.mkstemp(
|
|
2284
2379
|
prefix="bitfab-code-change-",
|
|
@@ -2293,14 +2388,16 @@ def write_code_change(change):
|
|
|
2293
2388
|
def execute():
|
|
2294
2389
|
if os.environ.get("GITHUB_RUN_ATTEMPT") != "1":
|
|
2295
2390
|
raise ValueError("Submit a new replay instead of rerunning an Actions job")
|
|
2391
|
+
git_token = os.environ.pop(GIT_TOKEN_ENV, None)
|
|
2296
2392
|
root = root_directory()
|
|
2297
2393
|
commit = os.environ["GITHUB_SHA"]
|
|
2298
2394
|
if git(root, "rev-parse", "HEAD") != commit:
|
|
2299
2395
|
raise ValueError("Runner checkout does not match the dispatched commit")
|
|
2300
2396
|
request = runner_request(root, commit)
|
|
2397
|
+
snapshot = os.environ.get("GITHUB_REF_NAME", "").startswith(PREFIX)
|
|
2301
2398
|
base = (
|
|
2302
2399
|
check_out_snapshot_base(root, commit, request.get("branch"))
|
|
2303
|
-
if
|
|
2400
|
+
if snapshot
|
|
2304
2401
|
else None
|
|
2305
2402
|
)
|
|
2306
2403
|
directory = within(root, request["cwd"])
|
|
@@ -2309,7 +2406,12 @@ def execute():
|
|
|
2309
2406
|
if environment:
|
|
2310
2407
|
workflow_environment_conflicts(root, environment)
|
|
2311
2408
|
print(f"Setting {', '.join(sorted(environment))} from the request", flush=True)
|
|
2312
|
-
|
|
2409
|
+
if snapshot:
|
|
2410
|
+
change = (
|
|
2411
|
+
None if base is None else runner_code_change(root, base, commit, request)
|
|
2412
|
+
)
|
|
2413
|
+
else:
|
|
2414
|
+
change = dispatched_code_change(root, commit, request, git_token)
|
|
2313
2415
|
run_environment = dict(environment)
|
|
2314
2416
|
if change is not None:
|
|
2315
2417
|
run_environment[CODE_CHANGE_PATH_ENV] = write_code_change(change)
|
|
@@ -3087,7 +3189,7 @@ def workflow_document(steps, run, directory, env, *, runs_on, environment):
|
|
|
3087
3189
|
{
|
|
3088
3190
|
**({} if directory == "." else {"working-directory": directory}),
|
|
3089
3191
|
"run": f"{run} --cloud-execute",
|
|
3090
|
-
"env": env,
|
|
3192
|
+
"env": {**env, GIT_TOKEN_ENV: GIT_TOKEN_VALUE},
|
|
3091
3193
|
},
|
|
3092
3194
|
]
|
|
3093
3195
|
return {
|
|
@@ -3226,6 +3328,46 @@ def add_checkout_depth(lines):
|
|
|
3226
3328
|
return "unchanged"
|
|
3227
3329
|
|
|
3228
3330
|
|
|
3331
|
+
def has_git_token_env(lines):
|
|
3332
|
+
return any(
|
|
3333
|
+
re.match(rf'[ \t]*(- +)?"?{GIT_TOKEN_ENV}"?[ \t]*:', without_yaml_comment(line))
|
|
3334
|
+
for line in lines
|
|
3335
|
+
)
|
|
3336
|
+
|
|
3337
|
+
|
|
3338
|
+
def add_git_token_env(lines):
|
|
3339
|
+
execute = next(
|
|
3340
|
+
(
|
|
3341
|
+
index
|
|
3342
|
+
for index in range(len(lines) - 1, -1, -1)
|
|
3343
|
+
if "--cloud-execute" in without_yaml_comment(lines[index])
|
|
3344
|
+
),
|
|
3345
|
+
None,
|
|
3346
|
+
)
|
|
3347
|
+
if execute is None:
|
|
3348
|
+
return False
|
|
3349
|
+
start = execute
|
|
3350
|
+
while start > 0 and not re.match(r"[ \t]*- ", lines[start]):
|
|
3351
|
+
start -= 1
|
|
3352
|
+
key = len(lines[start]) - len(lines[start].lstrip(" \t-"))
|
|
3353
|
+
end = block_end(lines, start, key - 1)
|
|
3354
|
+
entry = f"{GIT_TOKEN_ENV}: {json.dumps(GIT_TOKEN_VALUE)}"
|
|
3355
|
+
for at in range(start, end):
|
|
3356
|
+
env_key = re.fullmatch(
|
|
3357
|
+
r"[ \t]*(- +)?\"?env\"?[ \t]*:(.*)", without_yaml_comment(lines[at])
|
|
3358
|
+
)
|
|
3359
|
+
if not env_key or len(lines[at]) - len(lines[at].lstrip(" \t-")) != key:
|
|
3360
|
+
continue
|
|
3361
|
+
if env_key[2].strip():
|
|
3362
|
+
return False
|
|
3363
|
+
entries = block_end(lines, at, key)
|
|
3364
|
+
child = indentation(lines[at + 1]) if entries > at + 1 else key + 2
|
|
3365
|
+
lines.insert(entries, " " * child + entry)
|
|
3366
|
+
return True
|
|
3367
|
+
lines[end:end] = [" " * key + "env:", " " * (key + 2) + entry]
|
|
3368
|
+
return True
|
|
3369
|
+
|
|
3370
|
+
|
|
3229
3371
|
def add_id_token_permission(lines):
|
|
3230
3372
|
for index, line in enumerate(lines):
|
|
3231
3373
|
if not re.fullmatch(r'"?permissions"?[ \t]*:[ \t]*(#.*)?', line):
|
|
@@ -3412,6 +3554,15 @@ def update_workflow(root, path, *, remove_old_key_secrets=False):
|
|
|
3412
3554
|
updated.append(
|
|
3413
3555
|
f"Removed the {API_KEY_ENV} line, which read a secret by a name built from the run's inputs and so gave the runner every secret in the repository; the runner now gets the key from Bitfab"
|
|
3414
3556
|
)
|
|
3557
|
+
if not has_git_token_env(lines):
|
|
3558
|
+
if add_git_token_env(lines):
|
|
3559
|
+
updated.append(
|
|
3560
|
+
f"Passed the run's GitHub token to the replay step as {GIT_TOKEN_ENV}, so a --cloud-ref or dispatched replay can fetch the branch it compares against for --code-change default-branch or ref; the replay itself never sees it"
|
|
3561
|
+
)
|
|
3562
|
+
else:
|
|
3563
|
+
by_hand.append(
|
|
3564
|
+
f"{GIT_TOKEN_ENV}: {json.dumps(GIT_TOKEN_VALUE)} in the env of the step that runs --cloud-execute"
|
|
3565
|
+
)
|
|
3415
3566
|
depth = add_checkout_depth(lines)
|
|
3416
3567
|
if depth == "updated":
|
|
3417
3568
|
updated.append(
|
|
@@ -3450,7 +3601,7 @@ def update_workflow(root, path, *, remove_old_key_secrets=False):
|
|
|
3450
3601
|
def initialize(argv, name="--cloud-init"):
|
|
3451
3602
|
parser = argparse.ArgumentParser(
|
|
3452
3603
|
prog=f"bitfab-replay {name}",
|
|
3453
|
-
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 push trigger and the condition that skips a duplicate push run, the run name,
|
|
3604
|
+
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 push trigger and the condition that skips a duplicate push run, the run name, the id-token: write permission, and the BITFAB_REPLAY_GIT_TOKEN line that lets a --cloud-ref or dispatched replay fetch the branch it compares against), removes the BITFAB_REPLAY_BITFAB_API_KEY line older SDKs wrote, lists what it changed, and lists the BITFAB_REPLAY_KEY_* secrets older SDKs left in the repository; edit it directly for anything else.",
|
|
3454
3605
|
)
|
|
3455
3606
|
parser.add_argument(
|
|
3456
3607
|
"--secret",
|
data/lib/bitfab/version.rb
CHANGED