diff --git a/.github/actions/docker-release-metadata/action.yml b/.github/actions/docker-release-metadata/action.yml deleted file mode 100644 index 0585df4..0000000 --- a/.github/actions/docker-release-metadata/action.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: docker-release-metadata -description: Compute Docker build args and release tags from the current git tag. - -inputs: - image_name: - description: Docker image name without tag. - required: true - -outputs: - build_args: - description: Multiline build arguments for docker/build-push-action. - value: ${{ steps.meta.outputs.build_args }} - tags: - description: Multiline list of image tags to push. - value: ${{ steps.meta.outputs.tags }} - version: - description: Normalized semver version without the v prefix. - value: ${{ steps.meta.outputs.version }} - -runs: - using: composite - steps: - - name: Prepare release metadata - id: meta - shell: bash - run: | - set -euo pipefail - - version="$(git describe --tags --exact-match HEAD)" - semver="${version#v}" - remainder="${semver#*.}" - minor="${semver%%.*}.${remainder%%.*}" - commit="$(git rev-parse --short=12 HEAD)" - date="$(git log -1 --format=%cI HEAD)" - - { - echo "build_args<> "${GITHUB_OUTPUT}" diff --git a/.github/actions/release-version/action.yml b/.github/actions/release-version/action.yml new file mode 100644 index 0000000..7efa543 --- /dev/null +++ b/.github/actions/release-version/action.yml @@ -0,0 +1,71 @@ +name: release-version +description: Compute the next release version from conventional commits since the latest tag. + +outputs: + bump: + description: Version bump kind. + value: ${{ steps.version.outputs.bump }} + version: + description: Next semver without the v prefix. + value: ${{ steps.version.outputs.version }} + tag_name: + description: Next git tag with the v prefix. + value: ${{ steps.version.outputs.tag_name }} + minor_tag: + description: Major.minor Docker tag alias. + value: ${{ steps.version.outputs.minor_tag }} + previous_tag: + description: Previous git tag used as the release base. + value: ${{ steps.version.outputs.previous_tag }} + +runs: + using: composite + steps: + - name: Compute next version + id: version + shell: bash + run: | + set -euo pipefail + + previous_tag="$(git tag --list 'v*' --sort=-version:refname | head -n1)" + if [[ -z "${previous_tag}" ]]; then + previous_tag="v0.0.0" + range="HEAD" + else + range="${previous_tag}..HEAD" + fi + + bump="patch" + if git log --format='%s%n%b' "${range}" | grep -Eq 'BREAKING CHANGE|^[^[:space:]:]+(\([^)]+\))?!:'; then + bump="major" + elif git log --format='%s' "${range}" | grep -Eq '^feat(\([^)]+\))?: '; then + bump="minor" + fi + + version_core="${previous_tag#v}" + IFS='.' read -r major minor patch <<< "${version_core}" + + case "${bump}" in + major) + major=$((major + 1)) + minor=0 + patch=0 + ;; + minor) + minor=$((minor + 1)) + patch=0 + ;; + patch) + patch=$((patch + 1)) + ;; + esac + + version="${major}.${minor}.${patch}" + + { + echo "bump=${bump}" + echo "version=${version}" + echo "tag_name=v${version}" + echo "minor_tag=${major}.${minor}" + echo "previous_tag=${previous_tag}" + } >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d5c8b56..64098bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,24 @@ jobs: - name: Build run: go build -trimpath -o dist/galaxio ./cmd/galaxio - docker-image: + integration: + name: integration tests + runs-on: ubuntu-latest + needs: test + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Integration tests + run: go test -tags=integration -race -count=1 ./... + + docker-image-pr: name: docker image runs-on: ubuntu-latest if: github.event_name == 'pull_request' @@ -80,13 +97,87 @@ jobs: version: dev image_tag: galax-io/galaxio:pr-${{ github.event.pull_request.number }}-${{ github.sha }} - integration: - name: integration tests + docker-image-main: + name: docker image runs-on: ubuntu-latest - needs: test + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - test + - integration + outputs: + bump: ${{ steps.version.outputs.bump }} + version: ${{ steps.version.outputs.version }} + tag_name: ${{ steps.version.outputs.tag_name }} + minor_tag: ${{ steps.version.outputs.minor_tag }} + previous_tag: ${{ steps.version.outputs.previous_tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + fetch-tags: true + + - name: Compute release version + id: version + uses: ./.github/actions/release-version + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Prepare image build metadata + id: buildmeta + shell: bash + run: | + set -euo pipefail + + commit="$(git rev-parse --short=12 HEAD)" + date="$(git log -1 --format=%cI HEAD)" + + { + echo "build_args<> "${GITHUB_OUTPUT}" + + - name: Build image locally with BuildKit cache + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + load: true + push: false + tags: galax-io/galaxio:${{ steps.version.outputs.version }} + build-args: ${{ steps.buildmeta.outputs.build_args }} + cache-to: type=local,dest=/tmp/buildx-cache,mode=max + provenance: false + + - name: Validate image with utility command + run: docker run --rm galax-io/galaxio:${{ steps.version.outputs.version }} version + + - name: Upload BuildKit cache artifact + uses: actions/upload-artifact@v4 + with: + name: buildx-cache-${{ github.sha }} + path: /tmp/buildx-cache + if-no-files-found: error + retention-days: 1 + + release: + name: release + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - docker-image-main + permissions: + contents: write steps: - name: Checkout uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + fetch-tags: true - name: Set up Go uses: actions/setup-go@v5 @@ -94,5 +185,108 @@ jobs: go-version-file: go.mod cache: true - - name: Integration tests - run: go test -tags=integration -race -count=1 ./... + - name: Create and push git tag + env: + TAG_NAME: ${{ needs.docker-image-main.outputs.tag_name }} + run: | + set -euo pipefail + + if git ls-remote --exit-code --tags origin "refs/tags/${TAG_NAME}" >/dev/null 2>&1; then + echo "Tag ${TAG_NAME} already exists on origin, skipping push." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}" + git push origin "${TAG_NAME}" + + - name: Check existing GitHub release + id: release_exists + env: + TAG_NAME: ${{ needs.docker-image-main.outputs.tag_name }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + + if gh release view "${TAG_NAME}" >/dev/null 2>&1; then + echo "exists=true" >> "${GITHUB_OUTPUT}" + else + echo "exists=false" >> "${GITHUB_OUTPUT}" + fi + + - name: Run GoReleaser + if: steps.release_exists.outputs.exists != 'true' + uses: goreleaser/goreleaser-action@v6 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + distribution: goreleaser + version: latest + args: release --clean + + publish-image: + name: publish image + runs-on: ubuntu-latest + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: + - docker-image-main + - release + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + fetch-tags: true + + - name: Download BuildKit cache artifact + uses: actions/download-artifact@v4 + with: + name: buildx-cache-${{ github.sha }} + path: /tmp/buildx-cache + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Prepare image build metadata + id: buildmeta + env: + VERSION: ${{ needs.docker-image-main.outputs.version }} + run: | + set -euo pipefail + + commit="$(git rev-parse --short=12 HEAD)" + date="$(git log -1 --format=%cI HEAD)" + + { + echo "build_args<> "${GITHUB_OUTPUT}" + + - name: Build and push image with BuildKit + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.buildmeta.outputs.tags }} + build-args: ${{ steps.buildmeta.outputs.build_args }} + cache-from: type=local,src=/tmp/buildx-cache + provenance: false diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index e8a20bf..0000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,74 +0,0 @@ -name: docker-publish - -on: - workflow_run: - workflows: - - release - types: - - completed - -permissions: - contents: read - -env: - IMAGE_NAME: galax-io/galaxio - -jobs: - build-and-validate: - name: build and validate image - if: ${{ github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_sha }} - fetch-depth: 0 - fetch-tags: true - - - name: Build and validate image - uses: ./.github/actions/docker-build-validate - with: - image_name: galax-io/galaxio - - publish: - name: publish image - needs: build-and-validate - if: ${{ github.event.workflow_run.conclusion == 'success' }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_sha }} - fetch-depth: 0 - fetch-tags: true - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Prepare release metadata - id: meta - uses: ./.github/actions/docker-release-metadata - with: - image_name: ${{ env.IMAGE_NAME }} - - - name: Build and push image - uses: docker/build-push-action@v6 - with: - context: . - file: ./Dockerfile - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.meta.outputs.tags }} - build-args: ${{ steps.meta.outputs.build_args }} - provenance: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 9a96995..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: release - -on: - push: - tags: - - "v*" - -permissions: - contents: write - -jobs: - goreleaser: - name: goreleaser - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: true - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}