ci: port the rust, chart, docker, web and edge-publish gates from the rhema repository
android / debug (push) Successful in 2m20s
ci / checks (push) Successful in 2m1s
ci / test (push) Successful in 57s
ci / chart (push) Successful in 6s
ci / web (push) Successful in 13s
ci / docker (push) Successful in 1m44s
android / release (push) Successful in 2m7s
ci / publish-edge-images (deploy/docker/claw.Dockerfile, rhema-claw) (push) Successful in 1m25s
ci / publish-edge-images (deploy/docker/ui.Dockerfile, rhema-claw-ui) (push) Successful in 10s
ci / publish-edge-chart (push) Successful in 3s
android / debug (push) Successful in 2m20s
ci / checks (push) Successful in 2m1s
ci / test (push) Successful in 57s
ci / chart (push) Successful in 6s
ci / web (push) Successful in 13s
ci / docker (push) Successful in 1m44s
android / release (push) Successful in 2m7s
ci / publish-edge-images (deploy/docker/claw.Dockerfile, rhema-claw) (push) Successful in 1m25s
ci / publish-edge-images (deploy/docker/ui.Dockerfile, rhema-claw-ui) (push) Successful in 10s
ci / publish-edge-chart (push) Successful in 3s
The android workflow came over with the tree; the rest of the old repo's CI was framework-shaped. What the claw needs is the rust gate (fmt, clippy, release build with the binary artifact, offline tests), the chart lint/render suite, the daemon image smoke test, the web bundle, and the edge + release publishing pair (latest/sha images on main, exact-version images and chart on a v* tag). Dropped on the way: the crates.io publish-order and claw-boundary checks (nothing here publishes to crates.io; the boundary is enforced by the tree layout now), the cloudflare wasm gate, the binary release legs, and the manifest/crates.io release jobs — all framework concerns that stayed in the rhema repository. Release tags are checked against the workspace version by a verify-tag job that the image legs wait on.
This commit is contained in:
1 parent
12ba97c8f1
commit
95e2fcfd57
2 files changed
+381
No files matched your search
@@ -0,0 +1,271 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
OWNER: vhaudiquet
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Fast checks + release build of the Rust workspace.
|
||||||
|
checks:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
# Installs the toolchain pinned in rust-toolchain.toml.
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
# reqwest's default TLS links openssl; rusqlite's bundled SQLite
|
||||||
|
# needs a C compiler (present on the runner image, kept explicit
|
||||||
|
# next to the rest).
|
||||||
|
- name: Install native build dependencies
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev
|
||||||
|
|
||||||
|
- name: Format
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
|
- name: Clippy (warnings are errors)
|
||||||
|
run: cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
|
||||||
|
- name: Build (release)
|
||||||
|
run: cargo build --release --workspace
|
||||||
|
|
||||||
|
- name: Upload the claw binary
|
||||||
|
uses: https://gitea.com/actions/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: rhema-claw-linux-amd64
|
||||||
|
path: target/release/rhema-claw
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- name: Install native build dependencies
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev
|
||||||
|
|
||||||
|
# The offline-suite rule is load-bearing here: no test talks to the
|
||||||
|
# network, so the runner needs nothing beyond the toolchain.
|
||||||
|
- name: Test
|
||||||
|
run: cargo test --workspace --all-features
|
||||||
|
|
||||||
|
# The deploy chart: lint, render every supported shape, validate the
|
||||||
|
# result against the Kubernetes schemas — and prove the single-node
|
||||||
|
# invariant is enforced rather than hoped for.
|
||||||
|
chart:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install helm and kubeconform
|
||||||
|
run: |
|
||||||
|
curl -fsSL https://get.helm.sh/helm-v3.16.4-linux-amd64.tar.gz \
|
||||||
|
| tar -xz -C /tmp
|
||||||
|
sudo install /tmp/linux-amd64/helm /usr/local/bin/helm
|
||||||
|
curl -fsSL https://github.com/yannh/kubeconform/releases/download/v0.6.7/kubeconform-linux-amd64.tar.gz \
|
||||||
|
| tar -xz kubeconform
|
||||||
|
sudo install kubeconform /usr/local/bin/kubeconform
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: helm lint deploy/helm/rhema-claw --strict --set token=ci-render-token
|
||||||
|
|
||||||
|
- name: Render and validate
|
||||||
|
run: |
|
||||||
|
check() {
|
||||||
|
helm template test "$@" --set token=ci-render-token | kubeconform -strict -summary
|
||||||
|
}
|
||||||
|
# The shapes: defaults (persistent), the disposable one
|
||||||
|
# (emptyDir), the recommended secret path, and a seeded watch.
|
||||||
|
# The token rides every render: the daemon binds all interfaces
|
||||||
|
# in-cluster and refuses a non-loopback surface without one.
|
||||||
|
check deploy/helm/rhema-claw
|
||||||
|
check deploy/helm/rhema-claw --set persistence.enabled=false
|
||||||
|
check deploy/helm/rhema-claw --set secret.existingSecret=rhema-claw-secrets
|
||||||
|
check deploy/helm/rhema-claw --set config.watches[0].forge=github --set config.watches[0].repo=acme/widgets
|
||||||
|
|
||||||
|
# The state layout is pinned to the mounted volume regardless of
|
||||||
|
# values, and the listener must not be loopback inside a pod.
|
||||||
|
render=$(helm template test deploy/helm/rhema-claw --set token=ci-render-token)
|
||||||
|
grep -q "db: /data/claw.db" <<<"$render"
|
||||||
|
grep -q "workspace_root: /data/workspaces" <<<"$render"
|
||||||
|
grep -q "listen: 0.0.0.0:7799" <<<"$render"
|
||||||
|
|
||||||
|
# The token is a render-time failure: an unguarded daemon with a
|
||||||
|
# wide bind refuses to start, and the chart says so earlier.
|
||||||
|
if helm template test deploy/helm/rhema-claw \
|
||||||
|
> /dev/null 2>&1; then
|
||||||
|
echo "an empty token must not render"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The ingress shapes: host+class, host+TLS, and the two
|
||||||
|
# refusals — an enabled ingress without a host, and TLS without
|
||||||
|
# a certificate secret. Bearer tokens cross this ingress; neither
|
||||||
|
# may render silently.
|
||||||
|
check deploy/helm/rhema-claw --set ingress.enabled=true \
|
||||||
|
--set ingress.host=claw.example.com --set ingress.className=nginx
|
||||||
|
check deploy/helm/rhema-claw --set ingress.enabled=true \
|
||||||
|
--set ingress.host=claw.example.com --set ingress.tls.enabled=true \
|
||||||
|
--set ingress.tls.secretName=claw-tls
|
||||||
|
if helm template test deploy/helm/rhema-claw \
|
||||||
|
--set token=ci-render-token --set ingress.enabled=true > /dev/null 2>&1; then
|
||||||
|
echo "ingress without a host must not render"; exit 1
|
||||||
|
fi
|
||||||
|
if helm template test deploy/helm/rhema-claw \
|
||||||
|
--set token=ci-render-token --set ingress.enabled=true --set ingress.host=claw.example.com \
|
||||||
|
--set ingress.tls.enabled=true > /dev/null 2>&1; then
|
||||||
|
echo "tls without a secret must not render"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The invariant is a render-time failure: the claw is single-node
|
||||||
|
# by design (one SQLite store, one dispatcher), and the chart is
|
||||||
|
# where that is enforced.
|
||||||
|
if helm template test deploy/helm/rhema-claw \
|
||||||
|
--set replicaCount=2 --set token=ci-render-token > /dev/null 2>&1; then
|
||||||
|
echo "replicaCount 2 must not render"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The daemon's container image: build it, then prove the binary boots
|
||||||
|
# and serves /healthz inside it — config parsing, the state dirs, the
|
||||||
|
# keyless backend path, the dynamic linking. The image carries bash but
|
||||||
|
# no curl/wget, so the probe rides bash's /dev/tcp.
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build image
|
||||||
|
run: docker build -f deploy/docker/claw.Dockerfile -t rhema-claw:ci .
|
||||||
|
|
||||||
|
- name: Smoke test
|
||||||
|
run: |
|
||||||
|
docker rm -f rhema-claw || true
|
||||||
|
trap 'docker rm -f rhema-claw || true' EXIT
|
||||||
|
# The llama-cpp backend boots keyless: no credentials are needed
|
||||||
|
# to prove the daemon serves. All state goes to /tmp so the
|
||||||
|
# smoke never depends on volume ownership.
|
||||||
|
docker run -d --name rhema-claw \
|
||||||
|
-e XDG_STATE_HOME=/tmp/state \
|
||||||
|
--entrypoint /bin/bash rhema-claw:ci -c '
|
||||||
|
mkdir -p /tmp/ws /tmp/state
|
||||||
|
printf "provider: llama-cpp\ndb: /tmp/claw.db\nworkspace_root: /tmp/ws\nlisten: 127.0.0.1:7799\n" > /tmp/claw.yaml
|
||||||
|
exec /usr/local/bin/rhema-claw --config /tmp/claw.yaml'
|
||||||
|
ok=0
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
# The request needs the full header terminator, and the read
|
||||||
|
# needs its own timeout: an accepted-but-unanswered probe
|
||||||
|
# would hang the exec, not fail it.
|
||||||
|
if docker exec rhema-claw bash -c \
|
||||||
|
'exec 3<>/dev/tcp/127.0.0.1/7799 && printf "GET /healthz HTTP/1.0\r\n\r\n" >&3 && timeout 5 grep -q ok <&3'; then
|
||||||
|
ok=1; break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if [ "$ok" != 1 ]; then
|
||||||
|
docker logs rhema-claw
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
docker rm -f rhema-claw
|
||||||
|
|
||||||
|
# The web UI: install and produce the static bundle — the artifact an
|
||||||
|
# ingress serves next to /v1.
|
||||||
|
web:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node
|
||||||
|
run: |
|
||||||
|
curl -fsSL https://nodejs.org/dist/v22.14.0/node-v22.14.0-linux-x64.tar.xz \
|
||||||
|
| sudo tar -xJ -C /usr/local --strip-components=1
|
||||||
|
node --version
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
working-directory: web/claw
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
working-directory: web/claw
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
# The edge channel: a main push that passed everything above ships
|
||||||
|
# the two deployable images as a rolling latest + sha pair. Release
|
||||||
|
# publishing (exact versions, the stable pin) lives in release.yaml.
|
||||||
|
# The claw image is rebuilt here for the registry even though the
|
||||||
|
# docker job above just built and smoked it: no artifact hand-off is
|
||||||
|
# worth the plumbing for a build this cheap.
|
||||||
|
publish-edge-images:
|
||||||
|
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
|
||||||
|
needs: [checks, test, chart, docker, web]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- dockerfile: deploy/docker/claw.Dockerfile
|
||||||
|
image: rhema-claw
|
||||||
|
- dockerfile: deploy/docker/ui.Dockerfile
|
||||||
|
image: rhema-claw-ui
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: https://git.vhaudiquet.fr
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
push: true
|
||||||
|
# The edge pair: rolling latest, and the sha pin the chart's
|
||||||
|
# appVersion resolves to.
|
||||||
|
tags: |
|
||||||
|
git.vhaudiquet.fr/${{ env.OWNER }}/${{ matrix.image }}:latest
|
||||||
|
git.vhaudiquet.fr/${{ env.OWNER }}/${{ matrix.image }}:${{ gitea.sha }}
|
||||||
|
|
||||||
|
# The edge chart lands only once its images are in the registry: the
|
||||||
|
# chart's default image tag is appVersion, and appVersion here is
|
||||||
|
# this exact commit. The version rides the base version + a timestamp
|
||||||
|
# (ordered; SemVer pre-release rules allow no dots).
|
||||||
|
publish-edge-chart:
|
||||||
|
if: gitea.event_name == 'push' && gitea.ref == 'refs/heads/main'
|
||||||
|
needs: publish-edge-images
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v5
|
||||||
|
|
||||||
|
- name: Update Chart version and appVersion
|
||||||
|
run: |
|
||||||
|
BASE_VERSION=$(grep '^version:' deploy/helm/rhema-claw/Chart.yaml | awk '{print $2}')
|
||||||
|
TIMESTAMP=$(date -u +"%Y%m%d-%H%M%S")
|
||||||
|
sed -i "s/^version:.*/version: \"${BASE_VERSION}-${TIMESTAMP}\"/" deploy/helm/rhema-claw/Chart.yaml
|
||||||
|
sed -i "s/^appVersion:.*/appVersion: \"${{ gitea.sha }}\"/" deploy/helm/rhema-claw/Chart.yaml
|
||||||
|
|
||||||
|
- name: Lint Helm chart
|
||||||
|
run: helm lint deploy/helm/rhema-claw
|
||||||
|
|
||||||
|
- name: Package Helm chart
|
||||||
|
run: helm package deploy/helm/rhema-claw
|
||||||
|
|
||||||
|
- name: Push Helm chart to Gitea registry
|
||||||
|
run: |
|
||||||
|
TOKEN=$(echo -n "${{ secrets.PACKAGES_TOKEN }}" | tr -d '\n')
|
||||||
|
curl --fail --user "${{ gitea.actor }}:${TOKEN}" \
|
||||||
|
-X POST \
|
||||||
|
--upload-file rhema-claw-*.tgz \
|
||||||
|
"https://git.vhaudiquet.fr/api/packages/${{ env.OWNER }}/helm/api/charts"
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# The release channel: a v* tag ships the exact-version images (plus
|
||||||
|
# the sha pin the chart's appVersion resolves to) and the exact-version
|
||||||
|
# chart. Edge publishing on every main push lives in ci.yml. Release
|
||||||
|
# candidates (a v* tag with a prerelease suffix, v0.2.0-rc1) publish
|
||||||
|
# nothing. The daemon crate itself is `publish = false`, so no
|
||||||
|
# crates.io leg runs here: the crates the claw pulls live in the rhema
|
||||||
|
# repository's release channel.
|
||||||
|
name: release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
REGISTRY: git.vhaudiquet.fr
|
||||||
|
OWNER: vhaudiquet
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
verify-tag:
|
||||||
|
# cargo build ships whatever the manifest declares, regardless of
|
||||||
|
# the tag: a mismatch must fail loudly instead of shipping the
|
||||||
|
# wrong version under the release tag.
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check the tag matches the workspace version
|
||||||
|
run: |
|
||||||
|
crate_version="$(grep -m1 '^version =' Cargo.toml | awk -F'"' '{print $2}')"
|
||||||
|
tag_version="${GITHUB_REF_NAME#v}"
|
||||||
|
if [ "$crate_version" != "$tag_version" ]; then
|
||||||
|
echo "tag $GITHUB_REF_NAME does not match workspace version $crate_version" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
build-and-push-release-images:
|
||||||
|
# The same two deployable images as the edge channel, tagged for the
|
||||||
|
# release: the exact version, the rolling stable pin, and the sha
|
||||||
|
# the chart's appVersion resolves to.
|
||||||
|
if: ${{ !contains(gitea.ref_name, '-') }}
|
||||||
|
needs: verify-tag
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- dockerfile: deploy/docker/claw.Dockerfile
|
||||||
|
image: rhema-claw
|
||||||
|
- dockerfile: deploy/docker/ui.Dockerfile
|
||||||
|
image: rhema-claw-ui
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: https://git.vhaudiquet.fr
|
||||||
|
username: ${{ gitea.actor }}
|
||||||
|
password: ${{ secrets.PACKAGES_TOKEN }}
|
||||||
|
|
||||||
|
- name: Compute the release version (the tag strips the v)
|
||||||
|
id: ver
|
||||||
|
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Build and push image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ${{ matrix.dockerfile }}
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
git.vhaudiquet.fr/${{ env.OWNER }}/${{ matrix.image }}:${{ steps.ver.outputs.version }}
|
||||||
|
git.vhaudiquet.fr/${{ env.OWNER }}/${{ matrix.image }}:stable
|
||||||
|
git.vhaudiquet.fr/${{ env.OWNER }}/${{ matrix.image }}:${{ gitea.sha }}
|
||||||
|
|
||||||
|
release-chart:
|
||||||
|
# The exact tag version ships as-is so deployments can pin
|
||||||
|
# `--version 0.1.0`; appVersion pins the exact commit the image was
|
||||||
|
# built from (the chart's default image tag is appVersion, so the
|
||||||
|
# sha image above must exist before this runs).
|
||||||
|
if: ${{ !contains(gitea.ref_name, '-') }}
|
||||||
|
needs: build-and-push-release-images
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: https://gitea.com/actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Helm
|
||||||
|
uses: azure/setup-helm@v5
|
||||||
|
|
||||||
|
- name: Update Chart version and appVersion
|
||||||
|
run: |
|
||||||
|
sed -i "s/^version:.*/version: \"${GITHUB_REF_NAME#v}\"/" deploy/helm/rhema-claw/Chart.yaml
|
||||||
|
sed -i "s/^appVersion:.*/appVersion: \"${{ gitea.sha }}\"/" deploy/helm/rhema-claw/Chart.yaml
|
||||||
|
|
||||||
|
- name: Lint Helm chart
|
||||||
|
run: helm lint deploy/helm/rhema-claw
|
||||||
|
|
||||||
|
- name: Package Helm chart
|
||||||
|
run: helm package deploy/helm/rhema-claw
|
||||||
|
|
||||||
|
- name: Push Helm chart to Gitea registry
|
||||||
|
run: |
|
||||||
|
TOKEN=$(echo -n "${{ secrets.PACKAGES_TOKEN }}" | tr -d '\n')
|
||||||
|
curl --fail --user "${{ gitea.actor }}:${TOKEN}" \
|
||||||
|
-X POST \
|
||||||
|
--upload-file rhema-claw-*.tgz \
|
||||||
|
"https://git.vhaudiquet.fr/api/packages/${{ env.OWNER }}/helm/api/charts"
|
||||||
Reference in new issue
Block a user