ci: install litestream so the replication suite runs in the gate #77
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: image | |
| # The image a cluster is made of. | |
| # | |
| # Opening a cluster from the console provisions a machine that pulls this and | |
| # runs it. Before this workflow existed there was nothing to pull: `just ship` | |
| # rsyncs the source to a host and builds there, which works for the one node | |
| # somebody has a shell on and cannot work for a machine created by a request. | |
| # | |
| # Published on every push to main rather than on a tag, because a cluster opened | |
| # today should be the blazie of today. `latest` is what cloud-init asks for; the | |
| # sha tag is so a machine can be pinned to what it was opened with when something | |
| # has to be explained afterwards. | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| jobs: | |
| publish: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Built and LOADED, not pushed. The probe below decides whether it is fit | |
| # to be `latest`, and a push before that has already happened is not a | |
| # gate — the first two runs of this workflow published a `latest` that | |
| # could not boot, and only the run that finally passed replaced it. Same | |
| # ordering rule the control plane follows when it makes a tunnel before a | |
| # machine: do the irreversible thing last. | |
| - name: Build | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| push: false | |
| load: true | |
| tags: | | |
| ghcr.io/${{ github.repository }}:latest | |
| ghcr.io/${{ github.repository }}:${{ github.sha }} | |
| # A cluster is a Linux server and nothing else builds from this, so one | |
| # architecture. arm64 doubles the build for a platform nothing runs. | |
| platforms: linux/amd64 | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Refuse an image that will not answer | |
| run: | | |
| set -euo pipefail | |
| # A throwaway secret, because the release refuses to boot without one | |
| # and a probe that skipped it would be probing a different program | |
| # than the one a cluster runs. | |
| docker run -d --name probe -p 4000:4000 \ | |
| -e SECRET_KEY_BASE="$(head -c 48 /dev/urandom | base64)" \ | |
| ghcr.io/${{ github.repository }}:latest | |
| # The same question the healthcheck asks, for the same reason: a | |
| # container that starts and does not serve is the failure this catches, | |
| # and it is the one that shipped unnoticed when the healthcheck was | |
| # left pointing at a retired endpoint. | |
| for i in $(seq 1 60); do | |
| code=$(curl -s -o /dev/null -w '%{http_code}' -X POST \ | |
| http://127.0.0.1:4000/run -H 'content-type: application/json' \ | |
| -d '{}' --max-time 5 || true) | |
| [ "$code" = "401" ] && break | |
| sleep 2 | |
| done | |
| docker logs probe | tail -30 | |
| if [ "$code" != "401" ]; then | |
| echo "the image answered ${code} where 401 was expected" | |
| exit 1 | |
| fi | |
| echo "the image serves" | |
| # Only now, and only because it answered. | |
| - name: Publish | |
| run: | | |
| docker push ghcr.io/${{ github.repository }}:latest | |
| docker push ghcr.io/${{ github.repository }}:${{ github.sha }} |