fix(db): populate book.Author in List and Get responses (closes #882)… #1819
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: CI | |
| on: | |
| push: | |
| branches: [main, development] | |
| tags: ['v*'] | |
| pull_request: | |
| permissions: | |
| contents: read | |
| jobs: | |
| # ── Parallel checks — all events ─────────────────────────────────────────── | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0 | |
| with: | |
| version: v2.11.4 | |
| args: --timeout=5m | |
| - name: govulncheck | |
| run: | | |
| go install golang.org/x/vuln/cmd/govulncheck@d1f380186385b4f64e00313f31743df8e4b89a77 | |
| govulncheck ./... | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - run: npm ci --prefix web | |
| - run: npm run lint --prefix web | |
| - run: npm run typecheck --prefix web | |
| smoke: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - run: make smoke | |
| # PR-only: Go tests with coverage. Race-detector runs as a separate | |
| # parallel job (validate-race-go) so neither stacks the other's overhead. | |
| # Combined on one job they were tripping the 10-minute default timeout | |
| # in CI when both -race and -covermode=atomic compounded. | |
| validate-go: | |
| name: validate (Go) | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - run: go test -coverprofile=coverage.out -covermode=atomic ./cmd/... ./internal/... | |
| - uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 | |
| with: | |
| files: coverage.out | |
| flags: backend | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| # PR-only race detector. Runs in parallel with validate-go so the two | |
| # checks don't serialise. Matches the post-merge race job at line 125 | |
| # (no coverage flag, just -race). | |
| validate-race-go: | |
| name: validate (Go race) | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - run: go test -race -timeout=15m ./cmd/... ./internal/... | |
| validate-frontend: | |
| name: validate (frontend) | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'pull_request' | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - run: npm ci --prefix web | |
| - run: npm run build --prefix web | |
| # ── Gate: fast tests that must pass before image + release ───────────────── | |
| test: | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/development' || startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - run: go test -coverprofile=coverage.out -covermode=atomic ./cmd/... ./internal/... | |
| - uses: codecov/codecov-action@75cd11691c0faa626561e295848008c8a7dddffe # v5 | |
| with: | |
| files: coverage.out | |
| flags: backend | |
| fail_ci_if_error: false | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - run: npm ci --prefix web | |
| - run: npm run build --prefix web | |
| # Race-detector run — parallel with image/goreleaser, not a deploy gate. | |
| # A failure shows as a non-blocking check; fix before the next release. | |
| race: | |
| runs-on: ubuntu-latest | |
| if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - run: go test -race -timeout=15m ./cmd/... ./internal/... | |
| # ── Docker image — runs after test passes ────────────────────────────────── | |
| image: | |
| needs: test | |
| runs-on: ubuntu-latest | |
| permissions: | |
| packages: write | |
| id-token: write | |
| attestations: write | |
| env: | |
| DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | |
| if: env.DOCKERHUB_TOKEN != '' | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| id: meta | |
| with: | |
| images: | | |
| ghcr.io/${{ github.repository }} | |
| vavallee/bindery | |
| tags: | | |
| type=sha,format=short | |
| type=raw,value=latest,enable={{is_default_branch}} | |
| type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} | |
| type=raw,value=development,enable=${{ github.ref == 'refs/heads/development' }} | |
| type=semver,pattern={{version}} | |
| type=semver,pattern=v{{version}} | |
| - name: Derive version | |
| id: ver | |
| run: | | |
| short="${GITHUB_SHA::7}" | |
| if described=$(git describe --tags --match 'v*' --always 2>/dev/null) && [[ "$described" == v* ]]; then | |
| echo "version=${described}" >> "$GITHUB_OUTPUT" | |
| elif [[ "$GITHUB_REF" == refs/heads/development ]]; then | |
| echo "version=dev-${short}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "version=sha-${short}" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 | |
| id: push | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64,linux/arm64 | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| build-args: | | |
| VERSION=${{ steps.ver.outputs.version }} | |
| COMMIT=${{ github.sha }} | |
| BUILD_DATE=${{ github.event.head_commit.timestamp }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Attest build provenance | |
| uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1 | |
| with: | |
| subject-name: ghcr.io/${{ github.repository }} | |
| subject-digest: ${{ steps.push.outputs.digest }} | |
| push-to-registry: true | |
| # ── Deploy to bindery-dev (development branch only) ──────────────────────── | |
| # Runs after image build on every push to development. Updates | |
| # values-dev.yaml with the SHA image tag so ArgoCD picks up the new build. | |
| deploy-dev: | |
| name: Deploy to dev | |
| needs: image | |
| if: github.ref == 'refs/heads/development' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Update values-dev.yaml on development branch | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| IMAGE_TAG="sha-${GITHUB_SHA::7}" | |
| FILE_PATH="charts/bindery/values-dev.yaml" | |
| DEPLOY_BRANCH="auto/dev-deploy-${GITHUB_SHA::7}" | |
| DEV_SHA=$(gh api "repos/${GITHUB_REPOSITORY}/git/refs/heads/development" --jq .object.sha) | |
| gh api "repos/${GITHUB_REPOSITORY}/git/refs" \ | |
| -f ref="refs/heads/${DEPLOY_BRANCH}" \ | |
| -f sha="${DEV_SHA}" | |
| FILE_SHA=$(gh api "repos/${GITHUB_REPOSITORY}/contents/${FILE_PATH}?ref=${DEPLOY_BRANCH}" --jq .sha 2>/dev/null || echo "") | |
| CONTENT=$(printf 'image:\n tag: "%s"\n' "${IMAGE_TAG}" | base64 -w0) | |
| if [ -n "$FILE_SHA" ]; then | |
| gh api "repos/${GITHUB_REPOSITORY}/contents/${FILE_PATH}" \ | |
| -X PUT \ | |
| -f message="chore(deploy-dev): update to ${IMAGE_TAG} [skip ci]" \ | |
| -f content="${CONTENT}" \ | |
| -f sha="${FILE_SHA}" \ | |
| -f branch="${DEPLOY_BRANCH}" | |
| else | |
| gh api "repos/${GITHUB_REPOSITORY}/contents/${FILE_PATH}" \ | |
| -X PUT \ | |
| -f message="chore(deploy-dev): create values-dev.yaml for ${IMAGE_TAG} [skip ci]" \ | |
| -f content="${CONTENT}" \ | |
| -f branch="${DEPLOY_BRANCH}" | |
| fi | |
| # --repo is required because this job does not check out the | |
| # repository, so gh has no local git context to infer the target | |
| # from. Without it, `gh pr create` exits 1 with "not a git | |
| # repository: .git" and the 2>/dev/null mask hides that error; | |
| # the subsequent `gh pr merge` then fails for the same reason | |
| # (and orphans the auto/dev-deploy-<sha> branch with the right | |
| # content but no PR). The deploy-prod job below already has the | |
| # --repo flag; this brings deploy-dev in line. | |
| gh pr create \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --head "${DEPLOY_BRANCH}" \ | |
| --base development \ | |
| --title "chore(deploy-dev): update to ${IMAGE_TAG}" \ | |
| --body "Automated dev deploy: image tag \`${IMAGE_TAG}\`." 2>/dev/null || true | |
| gh pr merge "${DEPLOY_BRANCH}" --repo "${GITHUB_REPOSITORY}" --squash --admin --delete-branch | |
| echo "bindery-dev image tag updated to ${IMAGE_TAG}" | |
| - name: Trigger ArgoCD dev refresh | |
| env: | |
| ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }} | |
| ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }} | |
| run: | | |
| if [ -z "${ARGOCD_SERVER:-}" ] || [ -z "${ARGOCD_TOKEN:-}" ]; then | |
| echo "::warning::ARGOCD_SERVER or ARGOCD_TOKEN not configured — Argo will pick up changes on next poll" | |
| exit 0 | |
| fi | |
| curl -sf -X POST \ | |
| -H "Authorization: Bearer ${ARGOCD_TOKEN}" \ | |
| "${ARGOCD_SERVER}/api/v1/applications/bindery-dev?refresh=hard" \ | |
| && echo "ArgoCD bindery-dev refresh triggered" \ | |
| || echo "::warning::ArgoCD dev refresh failed — Argo will pick up changes on next poll" | |
| # ── GoReleaser — runs after test passes, tag pushes only ─────────────────── | |
| goreleaser: | |
| needs: test | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: "22" | |
| cache: npm | |
| cache-dependency-path: web/package-lock.json | |
| - run: npm ci --prefix web | |
| - run: npm run build --prefix web | |
| - name: Populate embedded frontend | |
| run: rm -rf internal/webui/dist && cp -r web/dist internal/webui/dist && touch internal/webui/dist/.gitkeep | |
| - name: Create GitHub Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VERSION="${GITHUB_REF_NAME}" | |
| NOTES=$(awk -v ver="## [${VERSION}]" ' | |
| index($0, ver) == 1 { found=1; next } | |
| found && index($0, "## ") == 1 { exit } | |
| found { print } | |
| ' CHANGELOG.md) | |
| if [ -z "$NOTES" ]; then | |
| echo "::error::No CHANGELOG section found for ${VERSION}. Add '## [${VERSION}]' to CHANGELOG.md before tagging." | |
| exit 1 | |
| fi | |
| TITLE=$(printf '%s' "$NOTES" | awk 'NF { print; exit }' | sed 's/^### //') | |
| RELEASE_TITLE="${VERSION} — ${TITLE}" | |
| if gh release view "$VERSION" &>/dev/null; then | |
| echo "Release $VERSION already exists — updating notes." | |
| printf '%s' "$NOTES" | gh release edit "$VERSION" --title "$RELEASE_TITLE" --notes-file - | |
| else | |
| printf '%s' "$NOTES" | gh release create "$VERSION" --title "$RELEASE_TITLE" --notes-file - | |
| fi | |
| - name: Install Syft | |
| uses: anchore/sbom-action/download-syft@e22c389904149dbc22b58101806040fa8d37a610 # v0 | |
| - name: GoReleaser (release binaries) | |
| uses: goreleaser/goreleaser-action@1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8 # v7.2.1 | |
| with: | |
| version: "~> v2" | |
| args: release --clean | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Bump bindery-ping LATEST_VERSION | |
| env: | |
| KUBECONFIG_B64: ${{ secrets.HETZ1_KUBECONFIG }} | |
| VERSION: ${{ github.ref_name }} | |
| run: | | |
| [ -z "$KUBECONFIG_B64" ] && exit 0 | |
| echo "$KUBECONFIG_B64" | base64 -d > /tmp/kc | |
| KUBECONFIG=/tmp/kc kubectl -n bindery-ping set env deployment/bindery-ping \ | |
| LATEST_VERSION="$VERSION" | |
| rm -f /tmp/kc | |
| # Pinned ABS compatibility suite. Runs self-contained against the | |
| # repository's seeded Phase 6 fixture harness and can optionally target a | |
| # real pinned ABS instance when the env vars below are provided. | |
| abs-contract: | |
| name: ABS contract | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - name: Run ABS contract suite | |
| env: | |
| BINDERY_ABS_CONTRACT_BASE_URL: ${{ secrets.BINDERY_ABS_CONTRACT_BASE_URL }} | |
| BINDERY_ABS_CONTRACT_API_KEY: ${{ secrets.BINDERY_ABS_CONTRACT_API_KEY }} | |
| BINDERY_ABS_CONTRACT_LIMITED_API_KEY: ${{ secrets.BINDERY_ABS_CONTRACT_LIMITED_API_KEY }} | |
| BINDERY_ABS_CONTRACT_LIBRARY_ID: ${{ secrets.BINDERY_ABS_CONTRACT_LIBRARY_ID }} | |
| run: | | |
| make abs-contract | |
| # ── Pre-deploy smoke — sanity check dev before promoting to prod ─────────── | |
| predeploy-smoke: | |
| name: Pre-deploy smoke | |
| needs: image | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 | |
| with: | |
| go-version: "1.25.10" | |
| cache: true | |
| - name: Wait for ArgoCD dev to sync | |
| env: | |
| ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }} | |
| ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }} | |
| run: | | |
| if [[ -z "$ARGOCD_SERVER" || -z "$ARGOCD_TOKEN" ]]; then | |
| echo "ArgoCD secrets not set — skipping wait" | |
| exit 0 | |
| fi | |
| echo "Waiting for ArgoCD app 'bindery-dev' to be Healthy+Synced..." | |
| for i in $(seq 1 24); do | |
| # jq avoids a curl|python3 pipe flagged by OpenSSF Scorecard downloadThenRun | |
| STATUS=$(curl -sf -H "Authorization: Bearer $ARGOCD_TOKEN" \ | |
| "$ARGOCD_SERVER/api/v1/applications/bindery-dev" 2>/dev/null | \ | |
| jq -r '"\(.status.health.status) \(.status.sync.status)"' 2>/dev/null || echo "unknown unknown") | |
| echo " attempt $i: $STATUS" | |
| [[ "$STATUS" == "Healthy Synced" ]] && echo "ArgoCD dev sync complete" && exit 0 | |
| sleep 10 | |
| done | |
| echo "Timed out waiting for ArgoCD dev sync" | |
| exit 1 | |
| - name: Run pre-deploy smoke tests | |
| env: | |
| BINDERY_URL: https://bindery.autonomy.ninja | |
| BINDERY_API_KEY: ${{ secrets.BINDERY_SMOKE_API_KEY }} | |
| run: | | |
| if ! curl -sf --max-time 5 "$BINDERY_URL/api/v1/health" > /dev/null 2>&1; then | |
| echo "::warning::Live instance unreachable from runner — skipping pre-deploy smoke" | |
| exit 0 | |
| fi | |
| make predeploy-smoke | |
| # ── Deploy to prod (bindery) — tag push, after image + goreleaser + smoke ── | |
| # Updates values.yaml on main; ArgoCD prod app (bindery) picks it up. | |
| deploy-prod: | |
| name: Deploy to prod | |
| needs: [image, goreleaser, predeploy-smoke] | |
| if: startsWith(github.ref, 'refs/tags/v') && needs.goreleaser.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Update values.yaml on main | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| IMAGE_TAG="${GITHUB_REF_NAME#v}" | |
| FILE_PATH="charts/bindery/values.yaml" | |
| DEPLOY_BRANCH="auto/prod-deploy-${IMAGE_TAG}" | |
| MAIN_SHA=$(gh api "repos/${GITHUB_REPOSITORY}/git/refs/heads/main" --jq .object.sha) | |
| gh api "repos/${GITHUB_REPOSITORY}/git/refs" \ | |
| -f ref="refs/heads/${DEPLOY_BRANCH}" \ | |
| -f sha="${MAIN_SHA}" | |
| FILE_SHA=$(gh api "repos/${GITHUB_REPOSITORY}/contents/${FILE_PATH}?ref=${DEPLOY_BRANCH}" --jq .sha) | |
| CONTENT=$(gh api "repos/${GITHUB_REPOSITORY}/contents/${FILE_PATH}?ref=${DEPLOY_BRANCH}" \ | |
| --jq '.content' | base64 -d | sed "s|tag:.*|tag: \"${IMAGE_TAG}\"|" | base64 -w0) | |
| gh api "repos/${GITHUB_REPOSITORY}/contents/${FILE_PATH}" \ | |
| -X PUT \ | |
| -f message="chore(deploy): promote bindery to ${IMAGE_TAG} [skip ci]" \ | |
| -f content="${CONTENT}" \ | |
| -f sha="${FILE_SHA}" \ | |
| -f branch="${DEPLOY_BRANCH}" | |
| gh pr create \ | |
| --repo "${GITHUB_REPOSITORY}" \ | |
| --head "${DEPLOY_BRANCH}" \ | |
| --base main \ | |
| --title "chore(deploy): promote bindery to ${GITHUB_REF_NAME}" \ | |
| --body "Automated prod deploy: image tag \`${IMAGE_TAG}\`." 2>/dev/null || true | |
| gh pr merge "${DEPLOY_BRANCH}" --repo "${GITHUB_REPOSITORY}" --squash --admin --delete-branch | |
| echo "bindery (prod) image tag updated to ${IMAGE_TAG}" | |
| - name: Trigger ArgoCD prod refresh | |
| env: | |
| ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }} | |
| ARGOCD_TOKEN: ${{ secrets.ARGOCD_TOKEN }} | |
| run: | | |
| if [ -z "${ARGOCD_SERVER:-}" ] || [ -z "${ARGOCD_TOKEN:-}" ]; then | |
| echo "::warning::ARGOCD_SERVER or ARGOCD_TOKEN not configured — Argo will pick up changes on next poll" | |
| exit 0 | |
| fi | |
| curl -sf -X POST \ | |
| -H "Authorization: Bearer ${ARGOCD_TOKEN}" \ | |
| "${ARGOCD_SERVER}/api/v1/applications/bindery?refresh=hard" \ | |
| && echo "ArgoCD bindery (prod) refresh triggered" \ | |
| || echo "::warning::ArgoCD prod refresh failed — Argo will pick up changes on next poll" | |
| # ── Discord release announcement ─────────────────────────────────────────── | |
| notify-discord: | |
| name: Notify Discord | |
| needs: [goreleaser, image] | |
| if: startsWith(github.ref, 'refs/tags/v') && needs.goreleaser.result == 'success' | |
| runs-on: ubuntu-latest | |
| # A broken/expired webhook should never fail the release workflow. | |
| continue-on-error: true | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Post release announcement | |
| env: | |
| DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }} | |
| VERSION: ${{ github.ref_name }} | |
| run: | | |
| PAYLOAD=$(python3 -c " | |
| import json, re, os, sys | |
| version = os.environ['VERSION'] | |
| with open('CHANGELOG.md') as f: | |
| content = f.read() | |
| pattern = r'## \[' + re.escape(version) + r'\][^\n]*\n(.*?)(?=\n## \[|\Z)' | |
| m = re.search(pattern, content, re.DOTALL) | |
| notes = m.group(1).strip() if m else '' | |
| if len(notes) > 900: | |
| notes = notes[:900].rsplit('\n', 1)[0] + '\n...' | |
| body = '**Bindery ' + version + '** is out!' | |
| if notes: | |
| body += '\n\n' + notes | |
| body += '\n\n<https://github.com/vavallee/bindery/releases/tag/' + version + '>' | |
| print(json.dumps({'content': body})) | |
| ") | |
| HTTP=$(curl -s -o /dev/null -w "%{http_code}" -X POST \ | |
| -H "Content-Type: application/json" \ | |
| --data-binary "$PAYLOAD" \ | |
| "$DISCORD_WEBHOOK") | |
| if [ "$HTTP" = "204" ]; then | |
| echo "Discord notified for ${VERSION}" | |
| else | |
| echo "::warning::Discord webhook returned HTTP ${HTTP} — check DISCORD_RELEASE_WEBHOOK secret" | |
| exit 1 | |
| fi |