diff --git a/README.md b/README.md index b5c6448..0e4b587 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # 🚀 GitHub Action for GitOps -This GitHub Action can be used for our GitOps workflow. The GitHub Action will build and push the Docker image for your service and deploys +This GitHub Action can be used for our GitOps workflow. The GitHub Action will build and push the Docker image for your +service and deploys the new version at your Kubernetes clusters. ## Requirement -When you want to use this GitHub Action your GitHub repository should have a `dev` and `master` / `main` branch and it should use tags for +When you want to use this GitHub Action your GitHub repository should have a `dev` and `master` / `main` branch and it +should use tags for releases. - For the `dev` branch we will change the files specified under `gitops-dev`. @@ -119,6 +121,7 @@ jobs: | `docker-build-secrets` | List of secrets to expose to the build (e.g., key=string, GIT_AUTH_TOKEN=mytoken) | | | `docker-build-secret-files` | List of secret files to expose to the build (e.g., key=filename, MY_SECRET=./secret.txt) | | | `docker-build-target` | Sets the target stage to build like: "runtime" | | +| `docker-build-platforms` | Sets the target platforms for build | linux/amd64 | | `docker-build-provenance` | Generate [provenance](https://docs.docker.com/build/attestations/slsa-provenance/) attestation for the build | `false` | | `docker-disable-retagging` | Disables retagging of existing images and run a new build instead | `false` | | `gitops-organization` | GitHub Organization for GitOps | `Staffbase` | @@ -140,7 +143,8 @@ jobs: ## Contributing -Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us. +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull +requests to us. ## License @@ -161,4 +165,5 @@ This project is licensed under the Apache-2.0 License - see the [LICENSE.md](LIC ## Releasing new versions -Go to the release overview page and publish the draft release with a new version number. Make sure to update the floating version commit. +Go to the release overview page and publish the draft release with a new version number. Make sure to update the +floating version commit. diff --git a/action.yml b/action.yml index 3c88ce1..91c1d51 100644 --- a/action.yml +++ b/action.yml @@ -39,6 +39,10 @@ inputs: docker-build-target: description: "Sets the target stage to build" required: false + docker-build-platforms: + description: "Sets the target platforms for build" + required: false + default: 'linux/amd64' docker-build-provenance: description: "Generate provenance attestation for the build" required: false @@ -151,6 +155,34 @@ runs: echo "tag=$TAG" >> $GITHUB_OUTPUT echo "tag_list=$TAG_LIST" >> $GITHUB_OUTPUT + - name: Verify Architecture Match + shell: bash + if: steps.preparation.outputs.build == 'true' + run: | + RUNNER_ARCH="${{ runner.arch }}" # X64 (AMD64) or ARM64 + TARGET_PLATFORMS="${{ inputs.docker-build-platforms }}" + + echo "Runner CPU Architecture: $RUNNER_ARCH" + echo "Requested Build Platforms: $TARGET_PLATFORMS" + + # Check for AMD64 mismatch (Runner is X64, but user requests ONLY arm64, OR user requests multi-arch which requires emulation) + if [[ "$RUNNER_ARCH" == "X64" ]]; then + if [[ "$TARGET_PLATFORMS" == *"linux/arm64"* ]]; then + echo "::error::Runner is X64 (Intel/AMD) but build includes 'linux/arm64'. This requires emulation. Aborting strictly." + exit 1 + fi + fi + + # Check for ARM64 mismatch + if [[ "$RUNNER_ARCH" == "ARM64" ]]; then + if [[ "$TARGET_PLATFORMS" == *"linux/amd64"* ]]; then + echo "::error::Runner is ARM64 (Apple Silicon/Graviton) but build includes 'linux/amd64'. This requires emulation. Aborting strictly." + exit 1 + fi + fi + + echo "Architecture match verified for native build ✅" + - name: Set up Docker Buildx if: inputs.docker-username != '' && inputs.docker-password != '' uses: docker/setup-buildx-action@v3 @@ -177,7 +209,7 @@ runs: tags: ${{ steps.preparation.outputs.tag_list }} secrets: ${{ inputs.docker-build-secrets }} secret-files: ${{ inputs.docker-build-secret-files }} - platforms: linux/amd64 + platforms: ${{ inputs.docker-build-platforms }} cache-from: type=gha cache-to: type=gha,mode=max provenance: ${{ inputs.docker-build-provenance }} @@ -189,20 +221,24 @@ runs: shell: bash run: | CHECK_EXISTING_TAGS="master-${GITHUB_SHA::8} main-${GITHUB_SHA::8}" - CONTENT_TYPE="application/vnd.docker.distribution.manifest.v2+json" + # Accept both single-arch manifests and multi-arch manifest lists/indexes + ACCEPT_HEADER="application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json" echo "CHECK_EXISTING_TAGS: ${CHECK_EXISTING_TAGS}" echo "RELEASE_TAG: ${RELEASE_TAG:1}" echo "Check if an image already exists for ${{ inputs.docker-image }}:main|master-${GITHUB_SHA::8} 🐋 ⬇" foundImage=false + DETECTED_CONTENT_TYPE="" + DIGEST="" end=$((SECONDS+300)) while [ $SECONDS -lt $end ]; do MANIFEST="" for tag in $CHECK_EXISTING_TAGS; do - MANIFEST=$(curl -H "Accept: ${CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${tag}") + # Dump headers to file to extract Content-Type and Digest later + MANIFEST=$(curl -s -D headers.txt -H "Accept: ${ACCEPT_HEADER}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${tag}") if [[ $MANIFEST == *"errors"* ]]; then echo "No image found for ${{ inputs.docker-image }}:${tag} 🚫" @@ -210,6 +246,13 @@ runs: else echo "Image found for ${{ inputs.docker-image }}:${tag} 🐋 ⬇" foundImage=true + + # Extract the Content-Type returned by registry + DETECTED_CONTENT_TYPE=$(grep -i "^Content-Type:" headers.txt | cut -d' ' -f2 | tr -d '\r') + + # Extract the correct digest from headers (works for lists and single images) + DIGEST=$(grep -i "^Docker-Content-Digest:" headers.txt | cut -d' ' -f2 | tr -d '\r') + break 2 fi done @@ -223,11 +266,12 @@ runs: fi echo "Retagging image with release version and :latest tags for ${{ inputs.docker-image }} 🏷" - curl --fail-with-body -X PUT -H "Content-Type: ${CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.tag }}" - curl --fail-with-body -X PUT -H "Content-Type: ${CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.latest }}" + echo "Using Content-Type: ${DETECTED_CONTENT_TYPE}" + + # Use the detected Content-Type to PUT the manifest back + curl --fail-with-body -X PUT -H "Content-Type: ${DETECTED_CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.tag }}" + curl --fail-with-body -X PUT -H "Content-Type: ${DETECTED_CONTENT_TYPE}" -u '${{ inputs.docker-username }}:${{ inputs.docker-password }}' -d "${MANIFEST}" "${{ inputs.docker-registry-api }}${{ inputs.docker-image}}/manifests/${{ steps.preparation.outputs.latest }}" - # Get the digest of the image - DIGEST=$(echo $MANIFEST | jq .config.digest | tr -d '"') echo "digest=$DIGEST" >> $GITHUB_OUTPUT - name: Checkout GitOps Repository