hush/.woodpecker.yml
jx12n 0b2cbf01b8
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci: deepen the clone in the release step; the plugin fetches depth 1 regardless
Pipeline 11 recorded nothing: the clone plugin ran `git fetch --depth=1
--filter=tree:0` with depth: 100 set, so `git log <serving>..<this>`
could not name the commit it replaced. The repo is anonymously readable
on Gitea, so the release step deepens by 100 itself before it renders
the notes. The clone setting goes back to what it was.
2026-09-05 16:57:55 -06:00

115 lines
5.3 KiB
YAML

# hush CI/CD. Origin must be Gitea (git.threesix.ai) — that remote carries the
# webhook. Pushing to the GitHub mirror does not deploy anything.
#
# push to main → test → Kaniko amd64 build → Zot → kubectl set image on `projects`
clone:
git:
image: woodpeckerci/plugin-git
settings:
depth: 1
steps:
test:
# 1.26, not the 1.25 in the template: go-chassis declares `go 1.26.0` and an
# older toolchain refuses to build it.
image: golang:1.26-alpine
environment:
# Hermetic, and deliberately so. go-chassis is a PRIVATE module and this
# container has no git credential, so a build that reached for the network
# would fail here — the vendored tree is what makes CI possible at all.
# GOPROXY=off turns "silently fetched from a proxy" into a hard error.
GOFLAGS: -mod=vendor
GOPROXY: "off"
commands:
- go vet ./...
- go test ./...
# gofmt as a gate, not a suggestion. -l lists offenders; the test fails if
# the list is non-empty. vendor/ is excluded because it is not ours.
- test -z "$(gofmt -l ./cmd ./internal)" || { gofmt -l ./cmd ./internal; echo "gofmt"; exit 1; }
when:
branch: main
event: push
build:
image: woodpeckerci/plugin-kaniko
settings:
registry: registry.threesix.ai
repo: hush/api
tags:
- latest
# SHA-tagged as well as latest: `latest` cannot pin a rollback.
- ${CI_COMMIT_SHA:0:8}
context: .
dockerfile: Dockerfile
cache: true
skip_tls_verify: true # internal Zot registry, self-signed cert
when:
branch: main
event: push
deploy:
image: bitnami/kubectl:latest
commands:
# What is serving NOW, before it is replaced: the left edge of the release
# notes and the `prev_sha` on the record. A first deploy leaves it empty.
- prev="$(kubectl -n projects get deployment hush -o jsonpath='{.spec.template.spec.containers[0].image}' 2>/dev/null || true)"; prev="$${prev##*:}"; echo "$${prev%%@*}" > .prev-sha
- kubectl set image deployment/hush hushd=registry.threesix.ai/hush/api:${CI_COMMIT_SHA:0:8} -n projects
- kubectl rollout status deployment/hush -n projects --timeout=120s
# Prove the rolled pod actually serves, not merely that it became Ready.
# A pod can pass readiness and still be the wrong image if `set image`
# silently matched nothing.
- kubectl -n projects get deployment hush -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'
when:
branch: main
event: push
# The release channel. `courier deployed` appends a version to the document
# releases/hush.prod (the changelog, `git log <serving>..<this>`) and posts one
# message on the `deploys` topic, as the `releases` agent, whose credential can
# send and do nothing else. The same call every other substrate makes — see
# orchard9-k3sf scripts/lib/release.sh; this is the Woodpecker copy.
#
# The CLI comes out of courier's own image, pinned by digest, pulled with the
# bare OCI protocol (zot allows anonymous pulls): the image is distroless and
# the k8s backend runs steps through /bin/sh, so neither the image itself nor
# crane:debug (/busybox/sh only) can be the step — measured on pipeline 10.
release:
image: alpine/git
environment:
COURIER_URL: https://courier.orchard9.ai
COURIER_TOKEN:
from_secret: courier_releases_token
commands:
- |
R=https://registry.threesix.ai/v2/courier/courierd
D=sha256:9d1ce76216a93f3d3808fc208179aaa2eb6c3eda20332095a28c3893b2ff5bdf # courierd:3d490785
wget -qO manifest.json --header='Accept: application/vnd.oci.image.manifest.v1+json,application/vnd.docker.distribution.manifest.v2+json' "$$R/manifests/$$D"
for l in $$(grep -o '"digest":"sha256:[a-f0-9]*"' manifest.json | cut -d'"' -f4); do
wget -qO- "$$R/blobs/$$l" | tar -xzf - usr/local/bin/courier 2>/dev/null && break
done
test -x usr/local/bin/courier || { echo "courier CLI not found in any layer of courierd@$$D"; exit 1; }
mv usr/local/bin/courier ./courier; rm -rf usr manifest.json
# The clone is `--depth=1 --filter=tree:0` whatever `depth:` says (measured on
# pipeline 11: the plugin fetched depth 1 with depth: 100 set), and the notes are
# `git log <serving>..<this>`, which needs the commit it replaced. The repo is
# anonymously readable on Gitea, so deepen it here; 100 covers any gap between
# two deploys, and the fallbacks below say so if it does not.
- git fetch -q --deepen=100 origin 2>/dev/null || git fetch -q --unshallow origin 2>/dev/null || true
- |
set -- hush prod ${CI_COMMIT_SHA} \
-image registry.threesix.ai/hush/api:${CI_COMMIT_SHA:0:8} -substrate k3s \
-by "woodpecker pipeline ${CI_PIPELINE_NUMBER}" -link "${CI_PIPELINE_URL}"
prev="$$(cat .prev-sha 2>/dev/null || true)"
if [ -n "$$prev" ] && [ "$$prev" != "${CI_COMMIT_SHA:0:8}" ]; then
set -- "$$@" -prev "$$prev" -notes-from-git -repo .
elif [ -n "$$prev" ]; then
set -- "$$@" -prev "$$prev" -notes "re-roll of ${CI_COMMIT_SHA:0:8}: the same build, rolled again"
else
set -- "$$@" -notes "first recorded release of hush to prod"
fi
./courier deployed "$$@"
when:
branch: main
event: push