From 243de9fba1ebc5ef271e69f683dc72ffe5e5d71f Mon Sep 17 00:00:00 2001 From: Kpa-clawbot Date: Sun, 5 Apr 2026 18:09:20 -0700 Subject: [PATCH] =?UTF-8?q?fix:=20consolidate=20CI=20pipeline=20=E2=80=94?= =?UTF-8?q?=20build,=20publish=20to=20GHCR,=20then=20deploy=20staging=20(#?= =?UTF-8?q?636)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Consolidate CI Pipeline — Build + Publish to GHCR + Deploy Staging ### What Merges the separate `publish.yml` workflow into `deploy.yml`, creating a single CI/CD pipeline: **`go-test → e2e-test → build-and-publish → deploy → publish-badges`** ### Why - Two workflows doing overlapping builds was wasteful and error-prone - `publish.yml` had a bug: `BUILD_TIME=$(date ...)` in a `with:` block never executed (literal string) - The old build job had duplicate/conflicting `APP_VERSION` assignments ### Changes - **`build-and-publish` job** replaces old `build` job — builds locally for staging, then does multi-arch GHCR push (gated to push events only, PRs skip) - **Build metadata** computed in a dedicated step, passed via `GITHUB_OUTPUT` — no more shell expansion bugs - **`APP_VERSION`** is `v1.2.3` on tag push, `edge` on master push - **Deploy** now pulls the `edge` image from GHCR and tags for compose compatibility, with fallback to local build - **`publish.yml` deleted** — no duplicate workflow - **Top-level `permissions`** block with `packages:write` for GHCR auth - **Triggers** now include `tags: ['v*']` for release publishing ### Status - ✅ Rebased onto master - ✅ Self-reviewed (all checklist items pass) - ✅ Ready for merge Co-authored-by: you --- .github/workflows/deploy.yml | 102 ++++++++++++++++++++++++++++------ .github/workflows/publish.yml | 54 ------------------ 2 files changed, 84 insertions(+), 72 deletions(-) delete mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 40da6b8..9441000 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,10 +3,15 @@ name: CI/CD Pipeline on: push: branches: [master] + tags: ['v*'] pull_request: branches: [master] workflow_dispatch: +permissions: + contents: read + packages: write + concurrency: group: ci-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true @@ -18,8 +23,8 @@ env: STAGING_CONTAINER: corescope-staging-go # Pipeline (sequential, fail-fast): -# go-test → e2e-test → build → deploy → publish -# PRs stop after build. Master continues to deploy + publish. +# go-test → e2e-test → build-and-publish → deploy → publish-badges +# PRs stop after build-and-publish (no GHCR push). Master continues to deploy + badges. jobs: # ─────────────────────────────────────────────────────────────── @@ -231,51 +236,112 @@ jobs: include-hidden-files: true # ─────────────────────────────────────────────────────────────── - # 3. Build Docker Image + # 3. Build & Publish Docker Image # ─────────────────────────────────────────────────────────────── - build: - name: "🏗️ Build Docker Image" + build-and-publish: + name: "🏗️ Build & Publish Docker Image" needs: [e2e-test] runs-on: [self-hosted, meshcore-runner-2] steps: - name: Checkout code uses: actions/checkout@v5 - - name: Set up Node.js 22 - uses: actions/setup-node@v5 - with: - node-version: '22' - - name: Free disk space run: | docker system prune -af 2>/dev/null || true docker builder prune -af 2>/dev/null || true df -h / - - name: Build Go Docker image + - name: Compute build metadata + id: meta run: | - echo "${GITHUB_SHA::7}" > .git-commit - APP_VERSION=$(node -p "require('./package.json').version") \ - GIT_COMMIT="${GITHUB_SHA::7}" \ - APP_VERSION=$(grep -oP 'APP_VERSION:-\K[^}]+' docker-compose.yml | head -1 || echo "3.0.0") - GIT_COMMIT=$(git rev-parse --short HEAD) BUILD_TIME=$(date -u '+%Y-%m-%dT%H:%M:%SZ') - export APP_VERSION GIT_COMMIT BUILD_TIME + GIT_COMMIT="${GITHUB_SHA::7}" + if [[ "$GITHUB_REF" == refs/tags/v* ]]; then + APP_VERSION="${GITHUB_REF#refs/tags/}" + else + APP_VERSION="edge" + fi + echo "build_time=$BUILD_TIME" >> "$GITHUB_OUTPUT" + echo "git_commit=$GIT_COMMIT" >> "$GITHUB_OUTPUT" + echo "app_version=$APP_VERSION" >> "$GITHUB_OUTPUT" + echo "Build: version=$APP_VERSION commit=$GIT_COMMIT time=$BUILD_TIME" + + - name: Build Go Docker image (local staging) + run: | + GIT_COMMIT="${{ steps.meta.outputs.git_commit }}" \ + APP_VERSION="${{ steps.meta.outputs.app_version }}" \ + BUILD_TIME="${{ steps.meta.outputs.build_time }}" \ docker compose -f "$STAGING_COMPOSE_FILE" -p corescope-staging build "$STAGING_SERVICE" echo "Built Go staging image ✅" + - name: Set up QEMU + if: github.event_name == 'push' + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + if: github.event_name == 'push' + uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + if: github.event_name == 'push' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + if: github.event_name == 'push' + id: docker-meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/kpa-clawbot/corescope + tags: | + type=semver,pattern=v{{version}} + type=semver,pattern=v{{major}}.{{minor}} + type=semver,pattern=v{{major}} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=edge,branch=master + + - name: Build and push to GHCR + if: github.event_name == 'push' + uses: docker/build-push-action@v6 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} + build-args: | + APP_VERSION=${{ steps.meta.outputs.app_version }} + GIT_COMMIT=${{ steps.meta.outputs.git_commit }} + BUILD_TIME=${{ steps.meta.outputs.build_time }} + cache-from: type=gha + cache-to: type=gha,mode=max + # ─────────────────────────────────────────────────────────────── # 4. Deploy Staging (master only) # ─────────────────────────────────────────────────────────────── deploy: name: "🚀 Deploy Staging" if: github.event_name == 'push' - needs: [build] + needs: [build-and-publish] runs-on: [self-hosted, meshcore-runner-2] steps: - name: Checkout code uses: actions/checkout@v5 + - name: Pull latest image from GHCR + run: | + # Try to pull the edge image from GHCR and tag for docker-compose compatibility + if docker pull ghcr.io/kpa-clawbot/corescope:edge; then + docker tag ghcr.io/kpa-clawbot/corescope:edge corescope-go:latest + echo "Pulled and tagged GHCR edge image ✅" + else + echo "⚠️ GHCR pull failed — falling back to locally built image" + fi + - name: Deploy staging run: | # Stop old container and release memory diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index dc80e0b..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Publish Docker Image - -on: - push: - tags: ['v*'] - branches: [master] - workflow_dispatch: - -jobs: - publish: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - - uses: docker/setup-qemu-action@v3 - - - uses: docker/setup-buildx-action@v3 - - - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/kpa-clawbot/corescope - tags: | - # On tag push: v1.2.3, v1.2, v1, latest - type=semver,pattern=v{{version}} - type=semver,pattern=v{{major}}.{{minor}} - type=semver,pattern=v{{major}} - type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} - # On master push: edge - type=edge,branch=master - - - uses: docker/build-push-action@v6 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_VERSION=${{ github.ref_name }} - GIT_COMMIT=${{ github.sha }} - BUILD_TIME=$(date -u +'%Y-%m-%dT%H:%M:%SZ') - cache-from: type=gha - cache-to: type=gha,mode=max