Skip to content

Releases

A release is a merge request. Like every other change, it is reviewed before it happens: a project’s release is a reviewed change to its version number, and the tag that deploys it follows from the merge. Nobody tags a release by hand.

A tag can’t go through a merge request: it isn’t a change to any file, only a name pointing at a commit. So the decision to release is written into a file the project already keeps, and CI makes the tag from it:

  • "version" in package.json, for a Node project;
  • otherwise the first line of a VERSION file.

Reviewers then see the release as an ordinary diff:

"version": "1.1.31",
"version": "1.1.32",

Approving and merging that is the release. A CHANGELOG.md entry in the same merge request, saying what’s shipping, is good practice but not required.

  1. Open the release merge request: the version bump, from the project’s fork like any change (Source code). Its pipeline runs the full check.
  2. Its owners approve it, and the merge bot merges it, fast-forward.
  3. The merge bot tags it. Right after merging, it sees a version with no v<version> tag yet, creates the tag on that commit through the API (as svrbc_bot; GitLab refuses its deploy key a protected tag), and starts the tag’s pipeline itself, as svrbc_bot. A project’s CI skips the pipeline GitLab starts for the new tag (a push pipeline for a tag), so the bot’s is the only one; in one that doesn’t, the bot sees it and starts none. On a merge that didn’t change the version, the tag already exists and it does nothing. A push to master starts no pipeline: the merged commit is the one the merge request built and tested.
  4. The tag’s pipeline deploys. Its deploy job runs only for a v* tag pipeline that svrbc_bot started. The bot sets SVRBC_PREVIOUS_RELEASE to the newest earlier release whose pipeline passed, so a job can run only when its files changed since then (rules: changes: compare_to: refs/tags/$SVRBC_PREVIOUS_RELEASE; ci/release.yml shows how). It’s unset when there’s no such release, and then every job runs.

GitLab’s free tier can protect tags only by role: v* can be limited to maintainers, but not to svrbc_bot alone. A maintainer could still push a tag by hand. So every deploy job has the rule from .svrbc-release, which runs it only when the tag’s pipeline was started by svrbc_bot. A tag pushed by a person deploys nothing; the only way to production is a merged version bump. The bot creates its tags through the API as svrbc_bot, a maintainer, with its token’s Repository Tag: Create.

.gitlab-ci.yml
include:
- project: svrbc/developers.svrbc.org
file: ci/release.yml
deploy-production:
# …the project's own deploy steps…
rules:
- !reference [.svrbc-release, rules]

Then:

  • put the current version in package.json (or VERSION), matching the latest tag, so merging it releases nothing;
  • run svrbc-merge-bot-enroll <project>, which protects v* tags for maintainers and the merge bot’s key;
  • add the workflow rule that keeps a push to master from starting a pipeline (Verified commits).

A staging branch deploys on every merge, not from tags: its pipeline on the project (never the fork) deploys to the staging site, whose own deploy role trusts only that protected branch, never the release tags (production’s role trusts only the tags). Only the merge bot can push to it, so a merge there is as reviewed as a version bump; nothing it deploys is production. svrbc-audit’s release-gate may warn about it, which the project’s AGENTS.md explains.

Nothing in CI needs a token of svrbc_bot any more: the merge bot tags releases and syncs forks with its deploy key, and starts release pipelines with its own token, which renews itself (merge-bot). SVRBC_BOT_TOKEN, the group CI variable that ci/release.yml and ci/fork-sync.yml used, is retired: delete it from the group’s CI/CD variables, and revoke the token, once every project is on this.

svrbc-audit warns about a project whose CI deploys from tags without the release gate, and about tags not protected for maintainers.