Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/cmux-tui-artifacts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,12 @@ jobs:
macos_runner: ${{ inputs.macos_runner || vars.MACOS_RUNNER_BACKGROUND || 'macos-15' }}

publish:
# Production secrets: GitHub releases them only to protected refs (the
# environment's deployment branch policy: main, tags v*).
environment: release
# R2 upload credentials only. A commit-addressed cmux-tui build is not a
# production release: cmux-next pins daemon builds from helper branches.
# The artifacts environment's deployment branch policy allows main,
# feat-cmux-next and cmux-tui-pin-*; signing, Sparkle, Homebrew and Apple
# secrets stay in the release environment.
environment: artifacts
name: publish to R2
# PR runs of this workflow only validate the build; publishing is
# main-push or manual dispatch.
Expand Down
21 changes: 19 additions & 2 deletions tests/test_ci_production_secrets_protected_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
ref the dispatcher picks, so any condition written in the workflow can be
edited away on a branch. GitHub enforces an environment's deployment branch
policy outside the workflow: these jobs declare the `release` environment
(policy: branch main, tags v*), or a cloud-vm environment with its own
(policy: branch main, tags v*), the artifacts environment, or a cloud-vm environment with its own
policy, and their production secrets live in that environment.

The iroh release gate checks out a requested ref; its job with production
Stack credentials also requires that ref to be the run's own revision.
"""

import os
import re
import sys

import yaml
Expand All @@ -21,6 +22,15 @@
WORKFLOWS = os.path.join(ROOT, ".github", "workflows")
FAILURES = []

# Publishing a commit-addressed cmux-tui build is not a production release:
# cmux-next pins daemon builds from helper branches (cmux-tui-pin-*). That job
# runs in the `artifacts` environment (policy: main, feat-cmux-next,
# cmux-tui-pin-*), which holds only the R2 upload credentials.
ARTIFACT_JOBS = {
"cmux-tui-artifacts.yml": ["publish"],
}
ARTIFACT_SECRETS = {"CF_R2_ACCESS_KEY_ID", "CF_R2_SECRET_ACCESS_KEY", "CF_R2_ACCOUNT_ID"}

RELEASE_JOBS = {
"release.yml": ["build-sign-notarize"],
"nightly.yml": ["build-sign-notarize-nightly", "publish-nightly"],
Expand All @@ -29,7 +39,6 @@
"ios-appstore-upload.yml": ["set-testflight-notes", "assign-internal"],
"repair-v0-64-25-helper-rpaths.yml": ["repair"],
"iroh-release-gate.yml": ["simulator-e2e"],
"cmux-tui-artifacts.yml": ["publish"],
"repair-nightly-appcast-content-types.yml": ["repair"],
"update-homebrew.yml": ["update-cask"],
}
Expand All @@ -49,6 +58,14 @@ def main():
for job in jobs:
definition = document["jobs"].get(job, {})
_check(definition.get("environment") == "release", f"{name} {job} runs in the release environment")
for name, jobs in ARTIFACT_JOBS.items():
text = open(os.path.join(WORKFLOWS, name), encoding="utf-8").read()
document = yaml.load(text, Loader=yaml.BaseLoader)
for job in jobs:
definition = document["jobs"].get(job, {})
_check(definition.get("environment") == "artifacts", f"{name} {job} runs in the artifacts environment")
used = set(re.findall(r"secrets\.([A-Za-z0-9_]+)", yaml.dump(definition)))
_check(used <= ARTIFACT_SECRETS, f"{name} {job} uses only R2 upload secrets (found {sorted(used)})")
gate = yaml.load(open(os.path.join(WORKFLOWS, "iroh-release-gate.yml"), encoding="utf-8"), Loader=yaml.BaseLoader)
condition = " ".join(str(gate["jobs"]["simulator-e2e"].get("if", "")).split())
_check(
Expand Down
Loading