From 0fc0d0390529b25050a736222334360c0e091227 Mon Sep 17 00:00:00 2001 From: Matthew Campbell Date: Sun, 8 Feb 2026 20:14:46 -0500 Subject: [PATCH] Refactor GitHub actions into a matrix using the right native runner --- .github/workflows/manual-publish-image.yml | 72 +++++++++++-- .github/workflows/release.yml | 119 +++++++++++++++++---- .talismanrc | 4 +- Dockerfile.native | 25 +++-- 4 files changed, 176 insertions(+), 44 deletions(-) diff --git a/.github/workflows/manual-publish-image.yml b/.github/workflows/manual-publish-image.yml index b01b0c7..64f87ff 100644 --- a/.github/workflows/manual-publish-image.yml +++ b/.github/workflows/manual-publish-image.yml @@ -8,12 +8,21 @@ env: IMAGE_NAME: mtthwcmpbll/turtorial jobs: - publish: - runs-on: ubuntu-latest + build-image: + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + runs-on: ${{ matrix.runner }} permissions: contents: read packages: write - steps: - name: Checkout repository uses: actions/checkout@v4 @@ -22,11 +31,16 @@ jobs: id: slug run: echo "sha8=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_OUTPUT - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Set up GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: '25' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + - name: Build Native Binary + run: ./mvnw clean package -Pnative,prod -DskipTests - name: Log in to the Container registry uses: docker/login-action@v3 @@ -35,13 +49,49 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and push Docker image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile.native + build-args: BINARY=prebuilt + platforms: ${{ matrix.platform }} push: true tags: | - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest - ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }} - platforms: linux/amd64, linux/arm64 + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }}-${{ matrix.arch }} + + create-manifest: + needs: [build-image] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Get short SHA + id: slug + run: echo "sha8=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_OUTPUT + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Create Multi-Arch Manifest (latest) + run: | + docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }}-arm64 + + - name: Create Multi-Arch Manifest (sha8) + run: | + docker buildx imagetools create -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }} \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }}-amd64 \ + ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }}-arm64 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 601e365..857a14d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,20 +54,17 @@ jobs: run: npm run test working-directory: src/main/frontend - release: + prepare-release: runs-on: ubuntu-latest needs: [test-backend, test-frontend] + outputs: + release-version: ${{ steps.calculate_version.outputs.release_version }} + sha8: ${{ steps.slug.outputs.sha8 }} steps: - uses: actions/checkout@v4 with: ssh-key: ${{ secrets.DEPLOY_KEY }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Set up GraalVM uses: graalvm/setup-graalvm@v1 with: @@ -85,7 +82,7 @@ jobs: node-version: '24' cache: 'npm' cache-dependency-path: src/main/frontend/package-lock.json - + - name: Get short SHA id: slug run: echo "sha8=$(echo ${GITHUB_SHA} | cut -c1-8)" >> $GITHUB_OUTPUT @@ -97,16 +94,11 @@ jobs: GITHUB_ACTOR: ${{ github.actor }} run: | VERSION=$(python3 .github/workflows/scripts/get-next-version.py ${{ inputs.release_type }}) - echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV + echo "release_version=$VERSION" >> $GITHUB_OUTPUT echo "Next version: $VERSION" - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - name: Update POM to Release Version - run: mvn -B versions:set -DnewVersion=${{ env.RELEASE_VERSION }} -DgenerateBackupPoms=false + run: mvn -B versions:set -DnewVersion=${{ steps.calculate_version.outputs.release_version }} -DgenerateBackupPoms=false - name: Build and Publish to Maven env: @@ -114,6 +106,36 @@ jobs: MAVEN_PASSWORD: ${{ secrets.GITHUB_TOKEN }} run: mvn -B deploy -Pprod + build-image: + needs: [prepare-release] + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + arch: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + arch: arm64 + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + + - name: Set up GraalVM + uses: graalvm/setup-graalvm@v1 + with: + java-version: '25' + distribution: 'graalvm' + github-token: ${{ secrets.GITHUB_TOKEN }} + cache: 'maven' + + - name: Update POM to Release Version + run: mvn -B versions:set -DnewVersion=${{ needs.prepare-release.outputs.release-version }} -DgenerateBackupPoms=false + + - name: Build Native Binary + run: ./mvnw clean package -Pnative,prod -DskipTests + - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: @@ -121,22 +143,77 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and Push Docker Image uses: docker/build-push-action@v6 with: context: . file: ./Dockerfile.native + build-args: BINARY=prebuilt + platforms: ${{ matrix.platform }} push: true tags: | - ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:latest - ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.slug.outputs.sha8 }} - ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.RELEASE_VERSION }} - platforms: linux/amd64, linux/arm64 + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.sha8 }}-${{ matrix.arch }} + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.release-version }}-${{ matrix.arch }} + + create-manifest: + needs: [prepare-release, build-image] + runs-on: ubuntu-latest + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.IMAGE_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Create Multi-Arch Manifest (latest) + run: | + docker buildx imagetools create -t ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:latest \ + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.sha8 }}-amd64 \ + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.sha8 }}-arm64 + + - name: Create Multi-Arch Manifest (sha8) + run: | + docker buildx imagetools create -t ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.sha8 }} \ + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.sha8 }}-amd64 \ + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.sha8 }}-arm64 + + - name: Create Multi-Arch Manifest (release version) + run: | + docker buildx imagetools create -t ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.release-version }} \ + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.release-version }}-amd64 \ + ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ needs.prepare-release.outputs.release-version }}-arm64 + + finalize-release: + needs: [prepare-release, create-manifest] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.DEPLOY_KEY }} + + - name: Set up JDK 25 + uses: actions/setup-java@v4 + with: + java-version: '25' + distribution: 'temurin' + cache: 'maven' + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" - name: Calculate Next Snapshot Version id: next_snapshot run: | - IFS='.' read -r major minor patch <<< "${{ env.RELEASE_VERSION }}" + IFS='.' read -r major minor patch <<< "${{ needs.prepare-release.outputs.release-version }}" NEXT_PATCH=$((patch + 1)) NEXT_SNAPSHOT="${major}.${minor}.${NEXT_PATCH}-SNAPSHOT" echo "NEXT_SNAPSHOT=$NEXT_SNAPSHOT" >> $GITHUB_ENV @@ -147,5 +224,5 @@ jobs: - name: Commit and Push run: | - git commit -am "Releasing version ${{ env.RELEASE_VERSION }}" + git commit -am "Releasing version ${{ needs.prepare-release.outputs.release-version }}" git push diff --git a/.talismanrc b/.talismanrc index 157db5d..c573db9 100644 --- a/.talismanrc +++ b/.talismanrc @@ -1,6 +1,8 @@ fileignoreconfig: +- filename: .github/workflows/manual-publish-image.yml + checksum: 3eec99c37eae7cbd91743e7b266570b8be0c69b060abbd92951ae2383778cf3a - filename: .github/workflows/release.yml - checksum: 5fc68c655ea8e84be2711ce3aef887505c98b1b0fb62f4cda642c8aaf7a7039a + checksum: cb5291091a8847e40b2f2050c5530c68f41255e9f59631ce03055ca746626070 - filename: backend.log checksum: 0181e4eeae0ab15fe8369040796ba9fd135bbe90e7bd1f3e31fff736e0175c12 - filename: src/main/frontend/package-lock.json diff --git a/Dockerfile.native b/Dockerfile.native index 3bdaed1..f838da4 100644 --- a/Dockerfile.native +++ b/Dockerfile.native @@ -1,20 +1,23 @@ -# Stage 1: Build the native image inside a Linux GraalVM container -FROM ghcr.io/graalvm/native-image-community:25 AS builder +# Build arg to select binary source: "build" (compile from source) or "prebuilt" (use pre-built binary) +ARG BINARY=build +# Builder stage — compiles native image from source (local dev, default) +FROM ghcr.io/graalvm/native-image-community:25 AS build WORKDIR /build - -# Copy Maven wrapper and pom.xml first for better layer caching COPY .mvn .mvn COPY mvnw pom.xml ./ RUN ./mvnw dependency:go-offline -B - -# Copy source code COPY src src +RUN ./mvnw clean package -Pnative,prod -DskipTests && cp target/turtorial /turtorial + +# Prebuilt stage — copies binary from build context (CI) +FROM scratch AS prebuilt +COPY target/turtorial /turtorial -# Build the native image with frontend assets (prod profile builds frontend via frontend-maven-plugin) -RUN ./mvnw clean package -Pnative,prod -DskipTests +# Select binary source based on ARG (BuildKit skips unused stages) +FROM ${BINARY} AS binary -# Stage 2: Runtime base image with tools for the tutorial environment +# Runtime base image with tools for the tutorial environment FROM ubuntu:24.04 AS base # Set install locations @@ -50,8 +53,8 @@ RUN mkdir -p /home/turtorial/.ssh \ # Create app directory WORKDIR /app -# Copy the native binary from the builder stage (now a Linux binary) -COPY --from=builder /build/target/turtorial /app/turtorial +# Copy the native binary from the selected source +COPY --from=binary /turtorial /app/turtorial # Change ownership of the app directory RUN chown -R turtorial:turtorial /app