diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..01f71d3bb --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: CI - Docker Build + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + prepare: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.tag.outputs.tag }} + ref: ${{ steps.tag.outputs.ref }} + steps: + - id: tag + run: | + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "tag=pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT" + echo "ref=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT" + else + echo "tag=sha-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT" + echo "ref=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" + fi + + docker: + needs: prepare + uses: ./.github/workflows/docker-build.yml + with: + tag: ${{ needs.prepare.outputs.tag }} + ref: ${{ needs.prepare.outputs.ref }} + push_dockerhub: false + permissions: + contents: read + packages: write diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index dadbed212..155f2ad60 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -4,9 +4,19 @@ on: workflow_call: inputs: tag: - description: "Release tag" + description: "Image tag (e.g. v1.0.0 or sha-abc1234)" required: true type: string + ref: + description: "Git ref to checkout (defaults to inputs.tag)" + required: false + type: string + default: "" + push_dockerhub: + description: "Also push to Docker Hub" + required: false + type: boolean + default: true env: GHCR_REGISTRY: ghcr.io @@ -16,24 +26,32 @@ env: jobs: build: - name: 🏗️ Build Docker Image - runs-on: ubuntu-latest + name: 🏗️ Build ${{ matrix.platform }} + runs-on: ${{ matrix.runner }} permissions: contents: read packages: write + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + suffix: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + suffix: arm64 + steps: - # ── Checkout ────────────────────────────── - name: 📥 Checkout repository uses: actions/checkout@v6 with: - ref: ${{ inputs.tag }} + ref: ${{ inputs.ref || inputs.tag }} - # ── Docker Buildx ───────────────────────── - name: 🔧 Set up Docker Buildx uses: docker/setup-buildx-action@v3 - # ── Login to GHCR ───────────────────────── - name: 🔑 Login to GitHub Container Registry uses: docker/login-action@v3 with: @@ -41,36 +59,91 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # ── Login to Docker Hub ──────────────────── - name: 🔑 Login to Docker Hub + if: inputs.push_dockerhub uses: docker/login-action@v3 with: registry: ${{ env.DOCKERHUB_REGISTRY }} username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - # ── Metadata (tags & labels) ────────────── - - name: 🏷️ Prepare image tags - id: tags - shell: bash - run: | - tag="${{ inputs.tag }}" - echo "ghcr_tag=${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:${tag}" >> "$GITHUB_OUTPUT" - echo "ghcr_latest=${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}:latest" >> "$GITHUB_OUTPUT" - echo "dockerhub_tag=${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}:${tag}" >> "$GITHUB_OUTPUT" - echo "dockerhub_latest=${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}:latest" >> "$GITHUB_OUTPUT" - - # ── Build & Push ────────────────────────── - - name: 🚀 Build and push Docker image + - name: 🚀 Build and push by digest + id: build uses: docker/build-push-action@v6 with: context: . - push: true - tags: | - ${{ steps.tags.outputs.ghcr_tag }} - ${{ steps.tags.outputs.ghcr_latest }} - ${{ steps.tags.outputs.dockerhub_tag }} - ${{ steps.tags.outputs.dockerhub_latest }} - cache-from: type=gha - cache-to: type=gha,mode=max - platforms: linux/amd64,linux/arm64,linux/riscv64 + file: docker/Dockerfile + platforms: ${{ matrix.platform }} + outputs: type=image,"name=${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}",push-by-digest=true,name-canonical=true,push=true + cache-from: type=gha,scope=${{ matrix.suffix }} + cache-to: type=gha,mode=max,scope=${{ matrix.suffix }} + + - name: 📤 Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: 📦 Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ matrix.suffix }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: 🔗 Create multi-arch manifest + runs-on: ubuntu-latest + needs: build + permissions: + contents: read + packages: write + + steps: + - name: 📥 Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: 🔧 Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: 🔑 Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.GHCR_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: 🔑 Login to Docker Hub + if: inputs.push_dockerhub + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKERHUB_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: 🏷️ Create and push multi-arch manifest (GHCR) + shell: bash + working-directory: /tmp/digests + run: | + GHCR="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}" + docker buildx imagetools create \ + --tag "${GHCR}:${{ inputs.tag }}" \ + --tag "${GHCR}:latest" \ + $(printf "${GHCR}@sha256:%s " *) + + - name: 🏷️ Create and push multi-arch manifest (Docker Hub) + if: inputs.push_dockerhub + shell: bash + working-directory: /tmp/digests + run: | + GHCR="${{ env.GHCR_REGISTRY }}/${{ env.GHCR_IMAGE_NAME }}" + DH="${{ env.DOCKERHUB_REGISTRY }}/${{ env.DOCKERHUB_IMAGE_NAME }}" + docker buildx imagetools create \ + --tag "${DH}:${{ inputs.tag }}" \ + --tag "${DH}:latest" \ + $(printf "${GHCR}@sha256:%s " *) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0edd29f22..f3ace1db3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,11 +17,6 @@ on: required: false type: boolean default: false - upload_tos: - description: "Upload to Volcengine TOS" - required: false - type: boolean - default: true jobs: create-tag: @@ -45,13 +40,21 @@ jobs: git tag -a "$RELEASE_TAG" -m "Release $RELEASE_TAG" git push origin "$RELEASE_TAG" - release: - name: GoReleaser Release + build-release: + name: GoReleaser Build (${{ matrix.arch }}) needs: create-tag - runs-on: ubuntu-latest + runs-on: ${{ matrix.runner }} permissions: contents: write - packages: write + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + steps: - name: Checkout tag uses: actions/checkout@v6 @@ -65,36 +68,46 @@ jobs: with: go-version-file: go.mod - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - registry: docker.io - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Run GoReleaser + - name: Run GoReleaser (split) uses: goreleaser/goreleaser-action@v6 with: distribution: goreleaser version: ~> v2 - args: release --clean + args: release --clean --split + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} + GOVERSION: ${{ steps.setup-go.outputs.go-version }} + GOARCH: ${{ matrix.arch }} + + merge-release: + name: GoReleaser Merge & Publish + needs: build-release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout tag + uses: actions/checkout@v6 + with: + fetch-depth: 0 + ref: ${{ inputs.tag }} + + - name: Setup Go from go.mod + id: setup-go + uses: actions/setup-go@v6 + with: + go-version-file: go.mod + + - name: Run GoReleaser (merge) + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: ~> v2 + args: continue --merge env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }} - DOCKERHUB_IMAGE_NAME: ${{ vars.DOCKERHUB_REPOSITORY }} GOVERSION: ${{ steps.setup-go.outputs.go-version }} - name: Apply release flags @@ -106,11 +119,15 @@ jobs: --draft=${{ inputs.draft }} \ --prerelease=${{ inputs.prerelease }} - upload-tos: - name: Upload to TOS - needs: release - if: ${{ inputs.upload_tos }} - uses: ./.github/workflows/upload-tos.yml + docker: + name: Build & Push Docker Image + needs: create-tag + uses: ./.github/workflows/docker-build.yml with: tag: ${{ inputs.tag }} + ref: ${{ inputs.tag }} + push_dockerhub: true + permissions: + contents: read + packages: write secrets: inherit diff --git a/.goreleaser.yaml b/.goreleaser.yaml index d531d106b..cc6f6edba 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -94,24 +94,6 @@ builds: - goos: windows goarch: arm -dockers_v2: - - id: picoclaw - dockerfile: docker/Dockerfile.goreleaser - extra_files: - - docker/entrypoint.sh - ids: - - picoclaw - images: - - "ghcr.io/{{ .Env.GITHUB_REPOSITORY_OWNER }}/picoclaw" - - "docker.io/{{ .Env.DOCKERHUB_IMAGE_NAME }}" - tags: - - "{{ .Tag }}" - - "latest" - platforms: - - linux/amd64 - - linux/arm64 - - linux/riscv64 - archives: - formats: [tar.gz] # this name template makes the OS and Arch compatible with the results of `uname`.