diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0f63f58..2504763 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,7 +17,16 @@ concurrency: jobs: build: - runs-on: ubuntu-latest + name: build${{ matrix.arch == 'arm64' && ' (arm64)' || '' }} + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v7 @@ -40,7 +49,16 @@ jobs: # shards inside one launcher process, so the fork boundary and the summary/report # plumbing are invisible to it; this job is where they break loudly. coordinated-profile: - runs-on: ubuntu-latest + name: coordinated-profile${{ matrix.arch == 'arm64' && ' (arm64)' || '' }} + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v7 @@ -104,10 +122,24 @@ jobs: # registry needs -- no PAT. The smoke test boots the actual image: /healthz must answer # with a secret set, and an unset COORDINATOR_SECRETS must be a refused start. container-image: - runs-on: ubuntu-latest + name: container-image${{ matrix.arch == 'arm64' && ' (arm64)' || '' }} + strategy: + fail-fast: false + matrix: + include: + - arch: amd64 + runner: ubuntu-latest + - arch: arm64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} permissions: contents: read packages: write + services: + registry: + image: registry:2 + ports: + - 5000:5000 steps: - uses: actions/checkout@v7 @@ -120,11 +152,20 @@ jobs: 25 cache: maven - - name: Build the coordinator app jar - run: mvn -B -ntp -DskipTests package - - name: Build the image - run: docker build -t shard4j-coordinator-ci shard4j-coordinator + run: mvn -B -ntp -DskipTests package jib:dockerBuild -Djib.to.image=shard4j-coordinator-ci + + - name: Check every platform is in the image index + if: matrix.arch == 'amd64' + run: | + mvn -B -ntp -DskipTests package jib:build -Djib.to.image=localhost:5000/shard4j-coordinator:ci \ + -Djib.allowInsecureRegistries=true + platforms=$(docker buildx imagetools inspect localhost:5000/shard4j-coordinator:ci \ + --format '{{range .Manifest.Manifests}}{{.Platform.OS}}/{{.Platform.Architecture}} {{end}}') + echo "platforms: $platforms" + for p in linux/amd64 linux/arm64 linux/ppc64le linux/s390x; do + case " $platforms " in *" $p "*) ;; *) echo "missing $p" >&2; exit 1 ;; esac + done - name: Smoke test the image run: | @@ -162,18 +203,16 @@ jobs: fi - name: Push to GHCR - if: github.event_name == 'push' + if: github.event_name == 'push' && matrix.arch == 'amd64' run: | echo '${{ secrets.GITHUB_TOKEN }}' | docker login ghcr.io -u '${{ github.actor }}' --password-stdin IMAGE=ghcr.io/${{ github.repository }} VERSION=$(mvn -B -q help:evaluate -Dexpression=project.version -DforceStdout) - docker tag shard4j-coordinator-ci "$IMAGE:$VERSION" - docker push "$IMAGE:$VERSION" + tags=() if [ '${{ github.ref }}' = 'refs/heads/main' ]; then - docker tag shard4j-coordinator-ci "$IMAGE:sha-${GITHUB_SHA::12}" - docker push "$IMAGE:sha-${GITHUB_SHA::12}" + tags=(-Djib.to.tags="sha-${GITHUB_SHA::12}") fi if [ '${{ github.ref_type }}' = 'tag' ]; then - docker tag shard4j-coordinator-ci "$IMAGE:latest" - docker push "$IMAGE:latest" + tags=(-Djib.to.tags=latest) fi + mvn -B -ntp -DskipTests package jib:build -Djib.to.image="$IMAGE:$VERSION" "${tags[@]}" diff --git a/pom.xml b/pom.xml index 491078d..f00a6a4 100644 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,9 @@ 3.12.0 3.2.8 0.11.0 + 3.5.2 + 0.1.0 + true D82FF933653EB8EC483709C77E738F7E2A355AAE 6.46.1 @@ -213,6 +216,11 @@ spring-boot-maven-plugin ${spring-boot.version} + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + diff --git a/shard4j-coordinator/Dockerfile b/shard4j-coordinator/Dockerfile deleted file mode 100644 index 8d9bdf3..0000000 --- a/shard4j-coordinator/Dockerfile +++ /dev/null @@ -1,23 +0,0 @@ -# The coordinator container: the Spring Boot repackaged app jar on the same JRE the -# integration tests run it under. Build the jar first (`mvn -DskipTests package`), then -# build with this module directory as the context. -FROM eclipse-temurin:25-jre - -RUN groupadd --system --gid 10001 shard4j \ - && useradd --system --uid 10001 --gid shard4j --home-dir /opt/shard4j \ - --shell /usr/sbin/nologin shard4j \ - && mkdir -p /opt/shard4j /data \ - && chown shard4j:shard4j /opt/shard4j /data - -COPY --chown=shard4j:shard4j target/shard4j-coordinator-*-app.jar /opt/shard4j/app.jar - -USER shard4j - -# The coordinator takes an exclusive lock on the data directory and appends JSONL there; -# it must live on a mount, never in the container layer. -ENV COORDINATOR_DATA_DIR=/data -VOLUME /data - -EXPOSE 8080 - -ENTRYPOINT ["java", "-jar", "/opt/shard4j/app.jar"] diff --git a/shard4j-coordinator/pom.xml b/shard4j-coordinator/pom.xml index d2ecc54..81c2572 100644 --- a/shard4j-coordinator/pom.xml +++ b/shard4j-coordinator/pom.xml @@ -20,6 +20,7 @@ classpath, so it is free to sit on the current LTS. --> ${coordinator.compiler.release} ${coordinator.rewrite.java.recipe} + false @@ -134,6 +135,81 @@ + + + com.google.cloud.tools + jib-maven-plugin + + + com.google.cloud.tools + jib-ownership-extension-maven + ${jib-ownership-extension-maven.version} + + + + + eclipse-temurin:25-jre + + + amd64 + linux + + + arm64 + linux + + + ppc64le + linux + + + s390x + linux + + + + + shard4j-coordinator + + + + + src/main/jib + data/.keep + + + + + + com.google.cloud.tools.jib.maven.extension.ownership.JibOwnershipExtension + + + + /data + 10001:10001 + + + + + + + com.marvinformatics.shard4j.coordinator.CoordinatorApplication + 10001:10001 + + 8080 + + + /data + + + /data + + + + + org.apache.maven.plugins maven-failsafe-plugin diff --git a/shard4j-coordinator/src/main/jib/data/.keep b/shard4j-coordinator/src/main/jib/data/.keep new file mode 100644 index 0000000..e69de29