rickub/clipublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/rickub/cli.git
git clone ssh://git@rickub.com/rickub/cli.git
RELEASING.md · 228 lines · 9.2 KBmarkdown Blame HistoryRaw
Initial import of the rickub CLI as a standalone public project 1a1d430 Olivier Girardot 8h ago1# Releasing `rickub`
2
3This repository ships three CI workflows, all running on rickub's own
4GitHub-Actions-compatible CI:
5
6| Workflow | Trigger | What it does |
7| --- | --- | --- |
8| [`.github/workflows/ci.yml`](.github/workflows/ci.yml) | every pull request, and pushes to `main` | `gofmt` check, `go vet`, `go test`, `go build` |
9| [`.github/workflows/rc.yml`](.github/workflows/rc.yml) | every push to `main` | tests, cross-compiles all four targets, publishes a **prerelease** `v<BASE>-rc.<run_number>` |
10| [`.github/workflows/release.yml`](.github/workflows/release.yml) | **manual only** (`workflow_dispatch`) | tests, cross-compiles, publishes a real release `v<version>` |
11
12Both release-producing workflows call the same script,
13[`scripts/build-dist.sh`](scripts/build-dist.sh), so an RC and a real release
14are byte-for-byte the same pipeline with a different version stamp.
15
16---
17
18## ⚠️ Prerequisite: the CI proxy must allow `/gh/`
19
20**Release publishing from CI fails until an operator changes the rickub server.**
21
22The CI guest reaches the outside world through `web/ciproxy`, whose allowlist
23today only covers the artifact/cache results service. The release REST shim
24lives under the `/gh` prefix (`$GITHUB_API_URL`), and the proxy currently
25answers **403** for those paths, so `softprops/action-gh-release@v2` cannot
26create the release or upload assets.
27
28To unblock it, in the **rickhub server repo**:
29
301. `web/ciproxy/proxy.go` — add the release shim prefix to `allowedPathPrefixes`:
31
32 ```go
33 var allowedPathPrefixes = []string{
34 "/twirp/github.actions.results.api.v1.ArtifactService/",
35 "/twirp/github.actions.results.api.v1.CacheService/",
36 "/gh/", // GitHub-compatible REST shim: releases + asset upload
37 }
38 ```
39
402. `web/ciproxy/proxy.go` — `allowedMethods` is currently
41 `GET / HEAD / POST / PUT` only. Release *creation* and asset upload are
42 `POST`, but editing a release is `PATCH` and removing an asset is `DELETE`;
43 add whichever verbs you intend to support.
44
453. `web/ciproxy/proxy_test.go` — add coverage for the new prefix: a `/gh/repos/…`
46 path is allowed, a non-`/gh` path is still denied, and path traversal such as
47 `/gh/../login` is still refused (`Allowed` rejects anything non-canonical).
48 Also re-run `web/ci_proxy_allowlist_conformance_test.go`, which cross-checks
49 the allowlist against the registered mux routes.
50
51Until that ships, `rc.yml` and `release.yml` will build and upload their
52`actions/upload-artifact` bundle successfully and then fail on the
53"Publish …" step. The archives are still downloadable from the run's artifacts,
54so you can release by hand in the meantime.
55
56You can rehearse the whole pipeline without touching the shim by dispatching
57`release.yml` with **`dry_run: true`** — it builds, tests, verifies the tag is
58free and uploads the artifacts, but never calls the release API.
59
60---
61
62## The `VERSION` file
63
64[`VERSION`](VERSION) at the repository root holds the **next** version to be
65released, as a bare `MAJOR.MINOR.PATCH` string with no leading `v`:
66
67```
680.1.0
69```
70
71* `rc.yml` reads it to name release candidates: with `VERSION` = `0.1.0`, the
72 17th push to `main` publishes the prerelease `v0.1.0-rc.17`.
73* `release.yml` compares it against the dispatched version and emits a
74 **warning** (not a failure) on a mismatch, so an out-of-band hotfix is still
75 possible.
76
77**Bumping `VERSION` is a normal pull request.** After releasing `v0.1.0`, open a
78PR setting `VERSION` to `0.2.0` (or `0.1.1`); once it merges, RCs on `main`
79start counting toward the next release. Nothing in CI ever writes to this file.
80
81---
82
83## Release candidates (automatic)
84
85Every push to `main` runs `rc.yml`:
86
871. `go test ./...`
882. `scripts/build-dist.sh <BASE>-rc.<run_number>`
893. publishes a **prerelease** at tag `v<BASE>-rc.<run_number>`, with the four
90 `.tar.gz` archives and `SHA256SUMS` attached.
91
92The tag does not exist beforehand — the release shim creates it at
93`target_commitish`, which the workflow sets to `${{ github.sha }}`, so CI cuts
94its own RC tags.
95
96`concurrency: { group: rc, cancel-in-progress: true }` means only the newest
97push to `main` is building at any moment; superseded RC runs are cancelled, so
98RC numbers are not contiguous. That is expected — `run_number` is the source of
99uniqueness, not a count of published RCs.
100
101Releases created by CI do **not** re-trigger workflows (the server has a
102recursion guard), so an RC never kicks off another build.
103
104---
105
106## Cutting a real release
107
1081. Make sure `main` is green and the latest RC is the build you want to ship.
1092. If needed, land a PR bumping [`VERSION`](VERSION) to the version you are
110 about to release.
1113. In the rickub web UI, open **Actions → Release → Run workflow**.
1124. Pick the branch/ref (normally `main`), enter the version as bare
113 `MAJOR.MINOR.PATCH` — e.g. `1.2.3`, **no leading `v`** — leave `dry_run`
114 unchecked, and run it.
115
116The workflow then:
117
118* validates the format and refuses a leading `v` or anything that is not
119 `X.Y.Z`;
120* refuses to continue if the tag `v<version>` already exists locally or on the
121 remote, and additionally probes the releases API for that tag (a non-200,
122 non-404 answer is only a warning — the git tag check is the real gate);
123* runs `go vet` and `go test`;
124* builds the four archives + `SHA256SUMS` via `scripts/build-dist.sh`;
125* publishes a non-prerelease release at `v<version>`, targeting
126 `${{ github.sha }}` — the exact commit of the ref you dispatched — so the tag
127 is created at that commit.
128
129There is no approval gate: rickub CI silently drops `environment:`, so the
130permission to run this workflow *is* the permission to release.
131
132### ⚠️ Use the web UI, not the dispatch API
133
134`release.yml` is the **only** workflow in this repository that declares
135`workflow_dispatch`, and it must stay that way.
136
137The rickub dispatch API — `POST /api/v1/repos/{owner}/{repo}/actions/dispatch`
138and the `rickub run dispatch` CLI — does not take a workflow name: it fires
139**every** `workflow_dispatch` workflow on the ref. If a second dispatchable
140workflow were added here, one `rickub run dispatch` would start both. Keeping
141the trigger unique makes that failure mode impossible.
142
143The web UI's **Run workflow** button *can* target a single workflow, so it is
144the supported way to cut a release. If you must use the API, remember it will
145run `release.yml` (and only `release.yml`, as long as this rule holds), and it
146still needs the `version` input.
147
148If you ever need a second manually-triggered pipeline, give it a
149`workflow_call` trigger and invoke it as a reusable workflow from `release.yml`
150rather than adding another `workflow_dispatch`.
151
152---
153
154## Artifacts
155
156`scripts/build-dist.sh` produces, in `dist/`:
157
158```
159rickub_<version>_linux_amd64.tar.gz
160rickub_<version>_linux_arm64.tar.gz
161rickub_<version>_darwin_amd64.tar.gz
162rickub_<version>_darwin_arm64.tar.gz
163SHA256SUMS
164```
165
166`<version>` is the bare version (no `v`): `1.2.3` for a release,
167`0.1.0-rc.17` for a candidate. Each archive contains the `rickub` binary at the
168top level plus `README.md` (and `LICENSE`, automatically, once this repo has
169one).
170
171All four are cross-compiled on a single `ubuntu-latest` runner with
172`CGO_ENABLED=0` and `-trimpath`. The rickub runner fleet is Linux/amd64 only —
173there are no macOS or arm64 hosts — so the darwin and arm64 binaries are never
174executed by CI. Keeping the CLI pure Go is what makes this work; introducing
175cgo would break three of the four targets.
176
177The version is stamped with
178`-ldflags "-X rickub.com/rickub/cli/cmd.Version=<version>"` (plus `-s -w`), so
179`rickub version` reports the release version instead of the `dev` default.
180
181To verify a download:
182
183```sh
184sha256sum -c SHA256SUMS --ignore-missing
185```
186
187Both workflows also upload `dist/` via `actions/upload-artifact@v4`, so the
188binaries are retrievable from the run page even if the release API call fails.
189
190---
191
192## Building locally
193
194```sh
195# same script CI runs
196scripts/build-dist.sh 1.2.3
197
198# single local binary
199go build -ldflags "-X rickub.com/rickub/cli/cmd.Version=$(cat VERSION)-dev" -o rickub .
200```
201
202The script needs Go and `tar`; it uses `sha256sum` where available and falls
203back to `shasum -a 256` on macOS. It stages into a temporary directory and
204leaves nothing untracked behind — `dist/` is already in `.gitignore`.
205
206---
207
208## Troubleshooting
209
210**`403` publishing the release.** The `/gh` ciproxy allowlist prerequisite at
211the top of this document has not landed yet.
212
213**`Resource not accessible by integration` / `404` on release creation.** The
214`GITHUB_TOKEN` is read-only by default; the workflow must declare
215`permissions: { contents: write }`. Both `rc.yml` and `release.yml` do.
216
217**`422` uploading an asset.** An asset with that name already exists on the
218release — the shim rejects duplicates. This normally means a partially
219completed run is being retried; delete the release (or the asset) and re-run.
220
221**Editing a release via `PATCH`.** The shim reads `draft` and `prerelease`
222unconditionally, so **always send both fields** in a PATCH body or you will
223silently flip a release to draft.
224
225**Nothing to release / `make_latest` ignored.** The shim does not implement
226`make_latest` or `generate_release_notes`, and there is no
227`GET /releases/latest`. The workflows therefore write their own release notes
228and never ask the API to pick a "latest" release.