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
41 changes: 41 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,20 @@ jobs:
env:
DISPATCHED: ${{ inputs.sha }}
FROM_CI: ${{ github.event.workflow_run.head_sha }}
EVENT: ${{ github.event_name }}
# The person who pressed Run or Re-run; github.actor keeps the original actor on a
# re-run, so it cannot be what this checks.
TRIGGERED_BY: ${{ github.triggering_actor }}
ATTEMPT: ${{ github.run_attempt }}
PROD_DEPLOYERS: ${{ vars.PROD_DEPLOYERS }}
run: |
# Anyone with write access can press Run workflow or Re-run; only maintainers may
# start a production deploy by hand. Merges reach production through ci.
if [[ "${EVENT}" == "workflow_dispatch" || "${ATTEMPT}" != "1" ]] &&
[[ ",${PROD_DEPLOYERS}," != *",${TRIGGERED_BY},"* ]]; then
echo "${TRIGGERED_BY} is not in PROD_DEPLOYERS; ask a maintainer to run this deploy" >&2
exit 1
fi
sha="${DISPATCHED:-${FROM_CI}}"
if [[ ! "${sha}" =~ ^[0-9a-f]{40}$ ]]; then
echo "a full commit SHA is required" >&2
Expand All @@ -52,10 +65,17 @@ jobs:
echo "${sha} is not on main" >&2
exit 1
fi
# An automatic deploy only ships main's current tip, so replaying an old run can't
# roll production back. Rolling back is a maintainer's Run workflow with the SHA.
if [[ "${EVENT}" == "workflow_run" && "${sha}" != "$(git rev-parse origin/main)" ]]; then
echo "::notice::${sha} is no longer main's tip; the newer merge deploys instead"
exit 0
fi
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"

staging:
needs: release
if: needs.release.outputs.sha != ''
runs-on: ubuntu-latest
timeout-minutes: 45
environment:
Expand Down Expand Up @@ -93,6 +113,27 @@ jobs:
contents: read
id-token: write
steps:
# "Re-run failed jobs" reuses the release job's result, so this job checks again itself.
- name: Check who started this and that it is still main's tip
env:
SHA: ${{ needs.release.outputs.sha }}
EVENT: ${{ github.event_name }}
TRIGGERED_BY: ${{ github.triggering_actor }}
ATTEMPT: ${{ github.run_attempt }}
PROD_DEPLOYERS: ${{ vars.PROD_DEPLOYERS }}
run: |
if [[ "${EVENT}" == "workflow_dispatch" || "${ATTEMPT}" != "1" ]] &&
[[ ",${PROD_DEPLOYERS}," != *",${TRIGGERED_BY},"* ]]; then
echo "${TRIGGERED_BY} is not in PROD_DEPLOYERS; ask a maintainer to run this deploy" >&2
exit 1
fi
if [[ "${EVENT}" == "workflow_run" ]]; then
tip="$(git ls-remote "https://github.com/${GITHUB_REPOSITORY}.git" refs/heads/main | cut -f1)"
if [[ "${SHA}" != "${tip}" ]]; then
echo "${SHA} is no longer main's tip (${tip}); not deploying it to production" >&2
exit 1
fi
fi
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ needs.release.outputs.sha }}
Expand Down
3 changes: 3 additions & 0 deletions docs/repository-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ See [GitHub's fork approval guidance](https://docs.github.com/en/actions/how-tos

- Require the verify and staging checks, one code-owner review and resolved review threads
on main.
- Let only `tin-lite-maintainers` update `main` ("Main: only maintainers merge"); other
writers open pull requests and deploy branches to staging.
- Limit manual runs of `deploy` to `PROD_DEPLOYERS`.
- Restrict the `production` environment to `main`; Google Cloud trusts only that
environment for the production deployer, and only `staging` for the staging deployer.
- Limit the review/check bypass to the named Tin maintainer team.
Expand Down
6 changes: 6 additions & 0 deletions docs/staging.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ Markdown) pass without a deploy. Staging has one slot: whoever deployed last own
the next deploy, and a merge to `main` also takes it, so say in the team channel when you
are testing there.

Anyone with write access can deploy to staging this way, without approval. Only the
`tin-lite-maintainers` team can update `main` (the "Main: only maintainers merge" ruleset),
and only the people in the `PROD_DEPLOYERS` repository variable can run **deploy** by hand,
so write access never reaches production on its own. Staging's host holds keys shared with
production, so write access is for the team.

## From merge to production

When `ci` passes on `main`, the `deploy` workflow deploys that commit to staging and, if it
Expand Down
Loading