Skip to content

Commit 1ead6fa

Browse files
committed
Redact runtime switch logs
1 parent bd1083a commit 1ead6fa

3 files changed

Lines changed: 87 additions & 27 deletions

File tree

.github/workflows/manual-strategy-switch.yml

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -297,8 +297,7 @@ jobs:
297297
- name: Preview assignments
298298
run: |
299299
set -euo pipefail
300-
python3 scripts/runtime_settings.py render "${TARGET_FILE}" --format env
301-
python3 scripts/runtime_settings.py render "${TARGET_FILE}" --format json > "${RUNNER_TEMP}/assignments.json"
300+
python3 scripts/runtime_settings.py render "${TARGET_FILE}" --format json --redact-values > "${RUNNER_TEMP}/assignments.json"
302301
python - <<'PY' "${TARGET_FILE}" "${RUNNER_TEMP}/assignments.json" >> "$GITHUB_STEP_SUMMARY"
303302
import json
304303
import sys
@@ -307,20 +306,20 @@ jobs:
307306
assignments = json.load(open(sys.argv[2], encoding="utf-8"))
308307
print("## Runtime switch preview")
309308
print()
310-
print(f"- target_id: `{target['target_id']}`")
311-
print(f"- repository: `{target['github']['repository']}`")
309+
print(f"- platform: `{target['runtime_target']['platform_id']}`")
310+
print("- target_id: `<redacted>`")
311+
print("- repository: `<redacted>`")
312312
print(f"- variable_scope: `{target['github']['variable_scope']}`")
313313
if target["github"].get("environment"):
314-
print(f"- environment: `{target['github']['environment']}`")
315-
print(f"- strategy_profile: `{target['runtime_target']['strategy_profile']}`")
316-
print(f"- service_name: `{target['runtime_target']['service_name']}`")
314+
print("- environment: `<redacted>`")
315+
print("- strategy_profile: `<redacted>`")
316+
print("- service_name: `<redacted>`")
317317
print(f"- execution_mode: `{target['runtime_target']['execution_mode']}`")
318+
print("- values: `<redacted>`")
318319
print()
319320
print("### Variables")
320321
for assignment in assignments:
321-
value = str(assignment["value"])
322-
preview = value if len(value) <= 220 else value[:220] + "..."
323-
print(f"- `{assignment['name']}` = `{preview}`")
322+
print(f"- `{assignment['name']}` = `{assignment['value']}`")
324323
PY
325324
326325
- name: Apply GitHub variable updates

scripts/runtime_settings.py

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,38 @@ class Assignment:
6363
name: str
6464
value: str
6565

66-
def gh_command(self) -> list[str]:
67-
command = ["gh", "variable", "set", self.name, "--repo", self.repository, "--body", self.value]
66+
def gh_command(self, *, redact_body: bool = False, redact_metadata: bool = False) -> list[str]:
67+
body = redacted_value() if redact_body else self.value
68+
repository = redacted_value() if redact_metadata else self.repository
69+
command = ["gh", "variable", "set", self.name, "--repo", repository, "--body", body]
6870
if self.variable_scope == "environment":
69-
command.extend(["--env", self.environment or ""])
71+
environment = redacted_value() if redact_metadata else (self.environment or "")
72+
command.extend(["--env", environment])
7073
return command
7174

72-
def shell_command(self) -> str:
73-
return " ".join(shlex.quote(part) for part in self.gh_command())
75+
def shell_command(self, *, redact_body: bool = False, redact_metadata: bool = False) -> str:
76+
return " ".join(
77+
shlex.quote(part)
78+
for part in self.gh_command(redact_body=redact_body, redact_metadata=redact_metadata)
79+
)
80+
81+
82+
def redacted_value() -> str:
83+
return "<redacted>"
84+
85+
86+
def assignment_payload(assignment: Assignment, *, redact_values: bool = False) -> dict[str, Any]:
87+
payload = {
88+
"target_id": assignment.target_id,
89+
"repository": assignment.repository,
90+
"variable_scope": assignment.variable_scope,
91+
"environment": assignment.environment,
92+
"name": assignment.name,
93+
"value": redacted_value() if redact_values else assignment.value,
94+
}
95+
if redact_values:
96+
payload["value_redacted"] = True
97+
return payload
7498

7599

76100
def compact_json(value: Any) -> str:
@@ -482,14 +506,7 @@ def command_render(args: argparse.Namespace) -> int:
482506
print(
483507
json.dumps(
484508
[
485-
{
486-
"target_id": assignment.target_id,
487-
"repository": assignment.repository,
488-
"variable_scope": assignment.variable_scope,
489-
"environment": assignment.environment,
490-
"name": assignment.name,
491-
"value": assignment.value,
492-
}
509+
assignment_payload(assignment, redact_values=args.redact_values)
493510
for assignment in all_assignments
494511
],
495512
ensure_ascii=False,
@@ -500,7 +517,7 @@ def command_render(args: argparse.Namespace) -> int:
500517

501518
if args.format == "gh":
502519
for assignment in all_assignments:
503-
print(assignment.shell_command())
520+
print(assignment.shell_command(redact_body=args.redact_values, redact_metadata=args.redact_values))
504521
return 0
505522

506523
current_target = None
@@ -511,7 +528,8 @@ def command_render(args: argparse.Namespace) -> int:
511528
if assignment.environment:
512529
suffix += f":{assignment.environment}"
513530
print(f"# {assignment.target_id} -> {assignment.repository} ({suffix})")
514-
print(f"{assignment.name}={shlex.quote(assignment.value)}")
531+
value = redacted_value() if args.redact_values else assignment.value
532+
print(f"{assignment.name}={shlex.quote(value)}")
515533
return 0
516534

517535

@@ -521,10 +539,15 @@ def command_apply(args: argparse.Namespace) -> int:
521539
all_assignments.extend(build_assignments(target))
522540

523541
for assignment in all_assignments:
524-
print(assignment.shell_command())
542+
redact_preview = not args.show_values
543+
print(assignment.shell_command(redact_body=redact_preview, redact_metadata=redact_preview))
525544

526545
if not args.yes:
527-
print("\nDry run only. Re-run with --yes to apply these GitHub variables.")
546+
if args.show_values:
547+
print("\nDry run only. Re-run with --yes to apply these GitHub variables.")
548+
else:
549+
print("\nDry run only. Re-run with --yes to apply these GitHub variables.")
550+
print("Values are redacted by default; add --show-values only in a private local terminal.")
528551
return 0
529552

530553
for assignment in all_assignments:
@@ -548,11 +571,17 @@ def build_parser() -> argparse.ArgumentParser:
548571
render = subparsers.add_parser("render", help="render generated variables")
549572
render.add_argument("targets", nargs="*", help="target JSON files; defaults to all targets")
550573
render.add_argument("--format", choices=("env", "gh", "json"), default="env")
574+
render.add_argument("--redact-values", action="store_true", help="hide assignment values in rendered output")
551575
render.set_defaults(func=command_render)
552576

553577
apply = subparsers.add_parser("apply", help="preview or apply GitHub variable updates")
554578
apply.add_argument("targets", nargs="*", help="target JSON files; defaults to all targets")
555579
apply.add_argument("--yes", action="store_true", help="apply updates with gh variable set")
580+
apply.add_argument(
581+
"--show-values",
582+
action="store_true",
583+
help="print exact values in the preview; avoid this in public CI logs",
584+
)
556585
apply.set_defaults(func=command_apply)
557586

558587
repository = subparsers.add_parser("repository", help="print the configured platform repository")

tests/test_runtime_settings.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,38 @@ def test_plugin_mount_schema_version_is_rendered_for_platform_parser(self):
6969
assignments["SCHWAB_STRATEGY_PLUGIN_MOUNTS_JSON"],
7070
)
7171

72+
def test_assignment_payload_can_redact_values(self):
73+
_, target = self.load_target("examples/targets/longbridge/sg.example.json")
74+
assignment = next(
75+
item
76+
for item in runtime_settings.build_assignments(target)
77+
if item.name == "RUNTIME_TARGET_JSON"
78+
)
79+
80+
payload = runtime_settings.assignment_payload(assignment, redact_values=True)
81+
82+
self.assertEqual(payload["value"], "<redacted>")
83+
self.assertTrue(payload["value_redacted"])
84+
self.assertNotIn(target["runtime_target"]["strategy_profile"], json.dumps(payload))
85+
self.assertNotIn(target["runtime_target"]["service_name"], json.dumps(payload))
86+
87+
def test_assignment_shell_command_can_redact_body_and_metadata(self):
88+
_, target = self.load_target("examples/targets/longbridge/sg.example.json")
89+
assignment = next(
90+
item
91+
for item in runtime_settings.build_assignments(target)
92+
if item.name == "RUNTIME_TARGET_JSON"
93+
)
94+
95+
command = assignment.shell_command(redact_body=True, redact_metadata=True)
96+
97+
self.assertIn("--repo '<redacted>'", command)
98+
self.assertIn("--body '<redacted>'", command)
99+
self.assertIn("--env '<redacted>'", command)
100+
self.assertNotIn(assignment.value, command)
101+
self.assertNotIn(assignment.repository, command)
102+
self.assertNotIn(assignment.environment, command)
103+
72104
def test_plugin_mount_schema_version_must_be_non_empty_string(self):
73105
_, target = self.load_target("examples/targets/schwab/live.example.json")
74106
target["plugin_mounts"][0]["expected_schema_version"] = ""

0 commit comments

Comments
 (0)