name: CD on: push: branches: [main] tags: ["v*"] pull_request: branches: [main] env: IMAGE: cad-atlas jobs: guard: name: Detect unsubstituted template runs-on: self-hosted outputs: is_template: ${{ steps.detect.outputs.is_template }} steps: - uses: actions/checkout@v4 - id: detect # Detect an UNSUBSTITUTED template by the leftover __PLACEHOLDER__ tokens. # Must not grep for the substituted module path — that pattern is itself # substituted at generate time, so it would match every real child repo's # own go.mod and skip all CI forever (template-go-web bug, see repo #). run: | if grep -qE '__[A-Z_]+__' go.mod; then echo "is_template=true" >> "$GITHUB_OUTPUT" else echo "is_template=false" >> "$GITHUB_OUTPUT" fi check: name: Lint / Test / Vet needs: guard if: needs.guard.outputs.is_template != 'true' runs-on: self-hosted steps: - uses: actions/checkout@v4 - uses: actions/setup-go@v5 with: go-version-file: go.mod cache: false - name: Install toolchain run: | go version go install github.com/a-h/templ/cmd/templ@latest curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \ | sh -s -- -b "$(go env GOPATH)/bin" v2.11.4 - name: Run checks run: task check oath: name: var-go/oath needs: guard # Only a real pull_request event carries a linked-issue oath to gate (mirrors # swedsl's own oath job, .gitea/workflows/ci.yml). v1 simplification (swedsl#30): # the oath issue number is the PR's OWN number. # # DISCLOSED LIMITATION (honest-stub discipline, see docs/INCEPTION-OATH.md S3 and # knowledge/swedsl-vargo-sprint1-enforcement-teeth-verdict.md): cmd/vargo-gate's # candidate is a hardcoded toy self-test registry (swedsl's own #9 fixture # vocabulary), not a real PR-diff checker. It will fail closed against any oath # that isn't that toy vocabulary — which is every real oath, including this repo's # own #1. A red or green "var-go/oath" status here currently proves the WIRING # (fetch issue -> gate -> post commit status) runs end-to-end on a real PR, not # that the PR satisfies its linked issue's oath. Deliberately NOT required by # branch protection until cad-atlas has its own candidate matching its real oath # vocabulary (#8, blocked on swedsl/oath's import path, swedsl#35) — making it # required now would permanently block every cad-atlas PR. if: needs.guard.outputs.is_template != 'true' && github.event_name == 'pull_request' runs-on: self-hosted steps: - name: Checkout swedsl (var-go source — not go-installable, module path isn't a real import path) uses: actions/checkout@v4 with: repository: mathias/swedsl path: swedsl token: ${{ secrets.DMABE_GITEA_API_TOKEN }} - uses: actions/setup-go@v5 with: go-version-file: swedsl/oath/go.mod cache: false - name: Run vargo-gate (fetch -> gate -> post status against this PR) working-directory: swedsl/oath env: VARGO_GITEA_BASEURL: ${{ github.server_url }} VARGO_GITEA_OWNER: ${{ github.repository_owner }} VARGO_GITEA_REPO: cad-atlas VARGO_GITEA_ISSUE: ${{ github.event.pull_request.number }} VARGO_GITEA_SHA: ${{ github.event.pull_request.head.sha }} run: | export DMABE_GITEA_API_TOKEN='${{ secrets.DMABE_GITEA_API_TOKEN }}' go run ./cmd/vargo-gate build: name: Build & Import needs: [guard, check] if: needs.guard.outputs.is_template != 'true' && github.event_name != 'pull_request' runs-on: self-hosted outputs: image-tag: ${{ steps.meta.outputs.sha-tag }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # full history + tags so `git describe` sees the SemVer tag - name: Derive image tags id: meta run: | SHA=$(git rev-parse --short HEAD) VERSION=$(git describe --tags --always --dirty) echo "sha-tag=${SHA}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - name: Build and push to local registry run: | REGISTRY="localhost:5000" REF="${REGISTRY}/${{ env.IMAGE }}:${{ steps.meta.outputs.sha-tag }}" buildah build \ --build-arg VERSION="${{ steps.meta.outputs.version }}" \ --label "org.opencontainers.image.revision=${{ github.sha }}" \ -t ${REF} \ -t ${REGISTRY}/${{ env.IMAGE }}:latest \ . buildah push --tls-verify=false ${REF} buildah push --tls-verify=false ${REGISTRY}/${{ env.IMAGE }}:latest echo "✓ Image pushed to ${REF}" deploy: name: Deploy via GitOps needs: [guard, build] if: needs.guard.outputs.is_template != 'true' && github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: self-hosted steps: - name: Update image tag in infra repo env: IMAGE_TAG: ${{ needs.build.outputs.image-tag }} DEPLOY_KEY: ${{ secrets.INFRA_DEPLOY_KEY }} run: | set -euo pipefail mkdir -p ~/.ssh echo "$DEPLOY_KEY" > ~/.ssh/id_infra chmod 600 ~/.ssh/id_infra ssh-keyscan -p 30022 10.0.1.20 >> ~/.ssh/known_hosts 2>/dev/null export GIT_SSH_COMMAND="ssh -i ~/.ssh/id_infra -o IdentitiesOnly=yes" rm -rf /tmp/infra git clone -b main ssh://git@10.0.1.20:30022/mathias/infra.git /tmp/infra cd /tmp/infra DEPLOYMENT="k3s/apps/cad-atlas/deployment.yaml" sed -i "s|image: localhost:5000/cad-atlas:.*|image: localhost:5000/cad-atlas:${IMAGE_TAG}|" "$DEPLOYMENT" grep -q "localhost:5000/cad-atlas:${IMAGE_TAG}" "$DEPLOYMENT" \ || { echo "✗ image tag patch failed"; exit 1; } if git diff --quiet "$DEPLOYMENT"; then echo "ℹ image tag unchanged — skipping push" else git -c user.name="cad-atlas CI" \ -c user.email="ci@cad-atlas.local" \ commit -m "chore(deploy): cad-atlas → ${IMAGE_TAG}" "$DEPLOYMENT" git push origin main echo "✓ pushed to infra repo" fi shred -u ~/.ssh/id_infra - name: Trigger Flux reconcile run: | kubectl -n flux-system annotate gitrepository flux-system \ reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite kubectl -n flux-system annotate kustomization apps \ reconcile.fluxcd.io/requestedAt="$(date +%s)" --overwrite - name: Verify rollout run: | kubectl rollout status deployment/cad-atlas \ --namespace cad-atlas \ --timeout=120s \ || { kubectl get pods -n cad-atlas -o wide kubectl get events -n cad-atlas --sort-by='.lastTimestamp' | tail -20 exit 1 }