From 4be476ea6c3f68132b581c1db9b756da687c9ca1 Mon Sep 17 00:00:00 2001 From: Wouter Born Date: Thu, 9 Apr 2026 11:21:44 +0200 Subject: [PATCH] Improve Docker CI for PRs and add manual release workflow Refactor the Docker image workflow to better support pull requests, forks, and multi-architecture builds. Changes: - add pull_request support for Docker image builds - refactor duplicated amd64/arm64 jobs into a matrix build - keep DockerHub login/push disabled for PR builds - keep manifest creation only for non-PR builds - derive image namespace from DOCKERHUB_NAMESPACE or repository owner - add Grype scanning for the base image - upload SARIF results only for non-PR builds - print scan SARIF in PR builds instead of uploading it - keep the existing per-arch slim image flow - add Buildx cache configuration Also add a separate manual Release workflow that: - takes an explicit VERSION input - creates and pushes a lightweight git tag - creates the GitHub release - dispatches the Docker workflow for that tag via workflow_dispatch This keeps PR behavior fork-friendly, adds image vulnerability scanning, and makes releases explicit without requiring Gradle or computed versioning. --- .github/release.yml | 17 ++ .github/workflows/postgresql.yml | 344 +++++++++++++------------------ .github/workflows/release.yml | 65 ++++++ 3 files changed, 229 insertions(+), 197 deletions(-) create mode 100644 .github/release.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..26adb1d --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,17 @@ +changelog: + categories: + - title: 🎉 New features + labels: + - Feature + - title: ⭐ Enhancements + labels: + - Enhancement + - title: 🐞 Bug fixes + labels: + - Bug + - title: 📝 Documentation + labels: + - Documentation + - title: Other changes + labels: + - "*" diff --git a/.github/workflows/postgresql.yml b/.github/workflows/postgresql.yml index 4e88bf4..c2b60bf 100644 --- a/.github/workflows/postgresql.yml +++ b/.github/workflows/postgresql.yml @@ -1,10 +1,8 @@ -# This is a basic workflow to help you get started with Actions - name: Docker Image # Controls when the action will run. on: - + # When a release is published release: types: [published] @@ -12,252 +10,204 @@ on: # Push excluding tags and workflow changes push: branches: - - main + - main tags-ignore: - '*.*' paths-ignore: - '**/*.md' + # PR + pull_request: + branches: + - main + paths-ignore: + - '**/*.md' + + # Manual trigger + workflow_dispatch: + +permissions: + contents: read + security-events: write + concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha || github.ref }} cancel-in-progress: true -# A workflow run is made up of one or more jobs that can run sequentially or in parallel +env: + IMAGE_NAME: ${{ vars.DOCKERHUB_NAMESPACE || github.repository_owner }}/postgresql + jobs: - image_postgresql_amd64: + build_arch_images: + name: Build ${{ matrix.arch }} runs-on: ubuntu-latest - + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + platform: linux/amd64 + - arch: arm64 + platform: linux/arm64 + steps: - name: Free up disk space + shell: bash run: | echo "Disk space before cleanup:" df -h - + # Remove large directories - sudo rm -rf /usr/share/dotnet - sudo rm -rf /usr/local/lib/android - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/.ghcup - sudo rm -rf /opt/hostedtoolcache/CodeQL - + sudo rm -rf /usr/share/dotnet \ + /usr/local/lib/android \ + /opt/ghc \ + /usr/local/.ghcup \ + /opt/hostedtoolcache/CodeQL || true + # Remove large packages - sudo apt-get remove -y '^aspnetcore-.*' || true - sudo apt-get remove -y '^dotnet-.*' || true - sudo apt-get remove -y '^llvm-.*' || true - sudo apt-get remove -y 'php.*' || true - sudo apt-get remove -y '^mongodb-.*' || true - sudo apt-get remove -y '^mysql-.*' || true - sudo apt-get remove -y azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri || true - sudo apt-get remove -y google-cloud-sdk google-cloud-cli || true + sudo apt-get remove -y '^aspnetcore-.*' '^dotnet-.*' '^llvm-.*' 'php.*' \ + '^mongodb-.*' '^mysql-.*' azure-cli google-chrome-stable firefox \ + powershell mono-devel libgl1-mesa-dri google-cloud-sdk google-cloud-cli || true sudo apt-get autoremove -y sudo apt-get clean - + # Remove Docker images - sudo docker image prune --all --force - + sudo docker image prune --all --force || true + # Remove swap storage sudo swapoff -a || true sudo rm -f /mnt/swapfile || true - + echo "Disk space after cleanup:" df -h - - - name: Set tags - run: | - if [ -z "$TAG" ]; then - echo "TAG=-t openremote/postgresql:develop-amd64" >> $GITHUB_ENV - else - echo "TAG=-t openremote/postgresql:$TAG-amd64" >> $GITHUB_ENV - fi - env: - TAG: ${{ github.event.release.tag_name }} - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - - - name: set up QEMU - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 + + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: platforms: all - - - name: install buildx - id: buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 - with: - version: latest - install: true - - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef + + - name: Set up Buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + + - name: Log in to Docker Hub + if: github.event_name != 'pull_request' + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: username: ${{ secrets._TEMP_DOCKERHUB_USER }} password: ${{ secrets._TEMP_DOCKERHUB_PASSWORD }} - - - name: Build amd64 image locally - run: | - docker buildx build \ - --build-arg GIT_COMMIT=${{ github.sha }} \ - --load \ - --platform linux/amd64 \ - --no-cache-filter trimmed \ - --no-cache-filter trimmed-all \ - $TAG . - - - name: Install slim toolkit - run: | - curl -sL https://raw.githubusercontent.com/slimtoolkit/slim/master/scripts/install-slim.sh | sudo -E bash - - - - name: Slim the image - run: | - # Extract image name from TAG (remove -t prefix) - IMAGE_NAME=$(echo "$TAG" | sed 's/-t //') - chmod +x ./slim-image.sh - ./slim-image.sh "$IMAGE_NAME" "${IMAGE_NAME}-slim" amd64 - - name: Push amd64 image + - name: Compute image tags + id: tags + shell: bash run: | - IMAGE_NAME=$(echo "$TAG" | sed 's/-t //') - docker push $IMAGE_NAME - docker push ${IMAGE_NAME}-slim - - image_postgresql_arm64: - needs: image_postgresql_amd64 - runs-on: ubuntu-latest - - steps: - - name: Free up disk space - run: | - echo "Disk space before cleanup:" - df -h - - # Remove large directories - sudo rm -rf /usr/share/dotnet - sudo rm -rf /usr/local/lib/android - sudo rm -rf /opt/ghc - sudo rm -rf /usr/local/.ghcup - sudo rm -rf /opt/hostedtoolcache/CodeQL - - # Remove large packages - sudo apt-get remove -y '^aspnetcore-.*' || true - sudo apt-get remove -y '^dotnet-.*' || true - sudo apt-get remove -y '^llvm-.*' || true - sudo apt-get remove -y 'php.*' || true - sudo apt-get remove -y '^mongodb-.*' || true - sudo apt-get remove -y '^mysql-.*' || true - sudo apt-get remove -y azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri || true - sudo apt-get remove -y google-cloud-sdk google-cloud-cli || true - sudo apt-get autoremove -y - sudo apt-get clean - - # Remove Docker images - sudo docker image prune --all --force - - # Remove swap storage - sudo swapoff -a || true - sudo rm -f /mnt/swapfile || true - - echo "Disk space after cleanup:" - df -h - - - name: Set tags - run: | - if [ -z "$TAG" ]; then - echo "TAG=-t openremote/postgresql:develop-arm64" >> $GITHUB_ENV + if [[ "${{ github.event_name }}" == "release" ]]; then + BASE_TAG="${IMAGE_NAME}:${{ github.event.release.tag_name }}-${{ matrix.arch }}" + SLIM_TAG="${IMAGE_NAME}:${{ github.event.release.tag_name }}-${{ matrix.arch }}-slim" + elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then + BASE_TAG="${IMAGE_NAME}:develop-${{ matrix.arch }}" + SLIM_TAG="${IMAGE_NAME}:develop-${{ matrix.arch }}-slim" else - echo "TAG=-t openremote/postgresql:$TAG-arm64" >> $GITHUB_ENV + BASE_TAG="${IMAGE_NAME}:pr-${{ github.event.pull_request.number || github.run_number }}-${{ matrix.arch }}" + SLIM_TAG="${IMAGE_NAME}:pr-${{ github.event.pull_request.number || github.run_number }}-${{ matrix.arch }}-slim" fi - env: - TAG: ${{ github.event.release.tag_name }} - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 - - - name: set up QEMU - uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 + + echo "base_tag=$BASE_TAG" >> "$GITHUB_OUTPUT" + echo "slim_tag=$SLIM_TAG" >> "$GITHUB_OUTPUT" + + - name: Build base image + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: - platforms: all - - - name: install buildx - id: buildx - uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 + context: . + platforms: ${{ matrix.platform }} + load: true + push: false + tags: ${{ steps.tags.outputs.base_tag }} + build-args: | + GIT_COMMIT=${{ github.sha }} + cache-from: type=gha,scope=postgresql-${{ matrix.arch }} + cache-to: type=gha,mode=max,scope=postgresql-${{ matrix.arch }} + no-cache-filters: | + trimmed + trimmed-all + + - name: Scan base Docker image + uses: anchore/scan-action@3c9a191a0fbab285ca6b8530b5de5a642cba332f # v7.2.2 + id: anchore-scan with: - version: latest - install: true - - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef + image: ${{ steps.tags.outputs.base_tag }} + fail-build: false + severity-cutoff: critical + + - name: Upload Anchore scan SARIF report + if: ${{ !cancelled() && github.event_name != 'pull_request' }} + uses: github/codeql-action/upload-sarif@c8e3174949dcd2ceb71718aeaa53fee4dc9052f2 # v4.31.7 with: - username: ${{ secrets._TEMP_DOCKERHUB_USER }} - password: ${{ secrets._TEMP_DOCKERHUB_PASSWORD }} - - - name: Build arm64 image locally - run: | - docker buildx build \ - --build-arg GIT_COMMIT=${{ github.sha }} \ - --load \ - --platform linux/arm64 \ - --no-cache-filter trimmed \ - --no-cache-filter trimmed-all \ - $TAG . + sarif_file: ${{ steps.anchore-scan.outputs.sarif }} + category: grype-${{ matrix.arch }} + + - name: Inspect Anchore scan SARIF report + if: ${{ !cancelled() && github.event_name == 'pull_request' }} + run: cat "${{ steps.anchore-scan.outputs.sarif }}" - name: Install slim toolkit + shell: bash run: | curl -sL https://raw.githubusercontent.com/slimtoolkit/slim/master/scripts/install-slim.sh | sudo -E bash - - name: Slim the image + shell: bash run: | - # Extract image name from TAG (remove -t prefix) - IMAGE_NAME=$(echo "$TAG" | sed 's/-t //') chmod +x ./slim-image.sh - ./slim-image.sh "$IMAGE_NAME" "${IMAGE_NAME}-slim" arm64 + ./slim-image.sh "${{ steps.tags.outputs.base_tag }}" "${{ steps.tags.outputs.slim_tag }}" "${{ matrix.arch }}" - - name: Push arm64 image + - name: Push arch images + if: github.event_name != 'pull_request' + shell: bash run: | - IMAGE_NAME=$(echo "$TAG" | sed 's/-t //') - docker push $IMAGE_NAME - docker push ${IMAGE_NAME}-slim + docker push "${{ steps.tags.outputs.base_tag }}" + docker push "${{ steps.tags.outputs.slim_tag }}" - create_manifest: - needs: [image_postgresql_amd64, image_postgresql_arm64] + create_manifests: + name: Create manifests + needs: build_arch_images + if: github.event_name != 'pull_request' runs-on: ubuntu-latest - + steps: - - name: Set tags - run: | - if [ -z "$TAG" ]; then - echo "TAG=openremote/postgresql:develop" >> $GITHUB_ENV - echo "TAG_AMD64=openremote/postgresql:develop-amd64" >> $GITHUB_ENV - echo "TAG_ARM64=openremote/postgresql:develop-arm64" >> $GITHUB_ENV - echo "TAG_SLIM=openremote/postgresql:develop-slim" >> $GITHUB_ENV - echo "TAG_SLIM_AMD64=openremote/postgresql:develop-amd64-slim" >> $GITHUB_ENV - echo "TAG_SLIM_ARM64=openremote/postgresql:develop-arm64-slim" >> $GITHUB_ENV - else - echo "TAG=openremote/postgresql:$TAG" >> $GITHUB_ENV - echo "TAG_LATEST=openremote/postgresql:latest" >> $GITHUB_ENV - echo "TAG_AMD64=openremote/postgresql:$TAG-amd64" >> $GITHUB_ENV - echo "TAG_ARM64=openremote/postgresql:$TAG-arm64" >> $GITHUB_ENV - echo "TAG_SLIM=openremote/postgresql:$TAG-slim" >> $GITHUB_ENV - echo "TAG_SLIM_LATEST=openremote/postgresql:latest-slim" >> $GITHUB_ENV - echo "TAG_SLIM_AMD64=openremote/postgresql:$TAG-amd64-slim" >> $GITHUB_ENV - echo "TAG_SLIM_ARM64=openremote/postgresql:$TAG-arm64-slim" >> $GITHUB_ENV - fi - env: - TAG: ${{ github.event.release.tag_name }} - - - name: Login to DockerHub - uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef + - name: Log in to Docker Hub + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: username: ${{ secrets._TEMP_DOCKERHUB_USER }} password: ${{ secrets._TEMP_DOCKERHUB_PASSWORD }} - - - name: Create and push multi-arch manifest - run: | - docker buildx imagetools create -t $TAG $TAG_AMD64 $TAG_ARM64 - if [ ! -z "$TAG_LATEST" ]; then - docker buildx imagetools create -t $TAG_LATEST $TAG_AMD64 $TAG_ARM64 - fi - - - name: Create and push multi-arch manifest for slim images + + - name: Create manifest tags + shell: bash run: | - docker buildx imagetools create -t $TAG_SLIM $TAG_SLIM_AMD64 $TAG_SLIM_ARM64 - if [ ! -z "$TAG_SLIM_LATEST" ]; then - docker buildx imagetools create -t $TAG_SLIM_LATEST $TAG_SLIM_AMD64 $TAG_SLIM_ARM64 + if [[ "${{ github.event_name }}" == "release" ]]; then + TAG="${IMAGE_NAME}:${{ github.event.release.tag_name }}" + TAG_LATEST="${IMAGE_NAME}:latest" + TAG_AMD64="${IMAGE_NAME}:${{ github.event.release.tag_name }}-amd64" + TAG_ARM64="${IMAGE_NAME}:${{ github.event.release.tag_name }}-arm64" + + TAG_SLIM="${IMAGE_NAME}:${{ github.event.release.tag_name }}-slim" + TAG_SLIM_LATEST="${IMAGE_NAME}:latest-slim" + TAG_SLIM_AMD64="${IMAGE_NAME}:${{ github.event.release.tag_name }}-amd64-slim" + TAG_SLIM_ARM64="${IMAGE_NAME}:${{ github.event.release.tag_name }}-arm64-slim" + + docker buildx imagetools create -t "$TAG" -t "$TAG_LATEST" "$TAG_AMD64" "$TAG_ARM64" + docker buildx imagetools create -t "$TAG_SLIM" -t "$TAG_SLIM_LATEST" "$TAG_SLIM_AMD64" "$TAG_SLIM_ARM64" + else + TAG="${IMAGE_NAME}:develop" + TAG_AMD64="${IMAGE_NAME}:develop-amd64" + TAG_ARM64="${IMAGE_NAME}:develop-arm64" + + TAG_SLIM="${IMAGE_NAME}:develop-slim" + TAG_SLIM_AMD64="${IMAGE_NAME}:develop-amd64-slim" + TAG_SLIM_ARM64="${IMAGE_NAME}:develop-arm64-slim" + + docker buildx imagetools create -t "$TAG" "$TAG_AMD64" "$TAG_ARM64" + docker buildx imagetools create -t "$TAG_SLIM" "$TAG_SLIM_AMD64" "$TAG_SLIM_ARM64" fi diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5f18ab7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,65 @@ +name: Release + +on: + workflow_dispatch: + inputs: + VERSION: + description: 'Release version/tag to create (example: 17.6.0.1)' + type: string + required: true + +permissions: + actions: write + contents: write + +jobs: + release: + name: Release + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate version + shell: bash + run: | + if [[ -z "${VERSION}" ]]; then + echo "VERSION is required" + exit 1 + fi + + if git ls-remote --tags origin "refs/tags/${VERSION}" | grep -q "refs/tags/${VERSION}$"; then + echo "Tag ${VERSION} already exists on origin" + exit 1 + fi + env: + VERSION: ${{ github.event.inputs.VERSION }} + + - name: Create and push tag + shell: bash + run: | + git tag "${VERSION}" + git push origin "${VERSION}" + env: + VERSION: ${{ github.event.inputs.VERSION }} + + - name: Create GitHub release + shell: bash + run: | + gh release create "${VERSION}" \ + --generate-notes \ + --title "${VERSION}" + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ github.event.inputs.VERSION }} + + - name: Trigger Docker workflow + shell: bash + run: | + gh workflow run postgresql.yml --ref "${VERSION}" + env: + GH_TOKEN: ${{ github.token }} + VERSION: ${{ github.event.inputs.VERSION }}