diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f48dbe3..a8e4bfe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,42 +4,68 @@ on: pull_request: branches: - main - schedule: - - cron: '05 10 * * *' # 10:05am UTC everyday - merge_group: - push: - branches: - - main paths-ignore: - - '**/README.md' + - "**.md" + schedule: + - cron: "05 10 * * *" # 10:05am UTC everyday + merge_group: workflow_dispatch: env: - IMAGE_NAME: "main" # the name of the image produced by this build, matches repo names + IMAGE_NAME: "rs-main-test" # the name of the image produced by this build, matches repo names IMAGE_DESC: "CentOS Stream-based image for basing off of " IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}" DEFAULT_TAG: "latest" CENTOS_VERSION: "stream10" + PLATFORMS: 'amd64 arm64' concurrency: group: ${{ github.workflow }}-${{ github.ref || github.run_id }} cancel-in-progress: true jobs: + generate_matrix: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Set matrix + id: set-matrix + run: | + # turn the comma separated string into a list + platforms=(${{ env.PLATFORMS }}) + MATRIX="{\"include\":[]}" + for platform in "${platforms[@]}"; do + MATRIX=$(echo $MATRIX | jq ".include += [{\"platform\": \"$platform\"}]") + done + echo "matrix=$(echo $MATRIX | jq -c '.')" >> $GITHUB_OUTPUT + build_push: name: Build and push image - runs-on: ubuntu-24.04 - + runs-on: ${{ matrix.platform == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }} + needs: generate_matrix + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: ${{fromJson(needs.generate_matrix.outputs.matrix)}} permissions: contents: read packages: write id-token: write steps: + - name: Install dependencies + if: matrix.platform == 'arm64' + run: | + sudo apt update -y + sudo apt install -y \ + podman + - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Maximize build space + if: matrix.platform != 'arm64' uses: ublue-os/remove-unwanted-software@5a8b0374222a6fffddb1be9516b5fece9483bed0 # v8 with: remove-codeql: true @@ -49,10 +75,116 @@ jobs: - name: Check Just Syntax shell: bash + run: just check + + - name: Build Image + id: build-image + shell: bash + run: | + just=$(which just) + sudo $just build "${IMAGE_NAME}" "${DEFAULT_TAG}" + + - name: Run Rechunker + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + id: rechunk + uses: hhd-dev/rechunk@v1.1.0 + with: + rechunk: "ghcr.io/hhd-dev/rechunk:v1.1.0" + ref: "localhost/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" + prev-ref: "${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" + skip_compression: true + version: ${{ env.CENTOS_VERSION }} + + - name: Load Image + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + id: load + run: | + IMAGE=$(sudo podman pull ${{ steps.rechunk.outputs.ref }}) + sudo rm -rf ${{ steps.rechunk.outputs.location }} + sudo podman image tag $IMAGE ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} + + IMAGE=${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }} + IMAGE_DIGEST=$(sudo podman image inspect --format '{{.Digest}}' $IMAGE) + echo "image=$IMAGE" >> $GITHUB_OUTPUT + echo "digest=$IMAGE_DIGEST" >> $GITHUB_OUTPUT + + - name: Login to GitHub Container Registry + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + env: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + run: | + echo ${{ secrets.GITHUB_TOKEN }} | sudo podman login -u ${{ github.actor }} --password-stdin $registry + echo ${{ secrets.GITHUB_TOKEN }} | docker login -u ${{ github.actor }} --password-stdin $registry + + # Push the image to GHCR (Image Registry) + - name: Push to GHCR + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + id: push + env: + IMAGE_REGISTRY: ${{ env.IMAGE_REGISTRY }} + IMAGE_NAME: ${{ env.IMAGE_NAME }} + IMAGE_DIGEST: ${{ steps.load.outputs.digest }} + PLATFORM: ${{ matrix.platform }} run: | - just check + sudo podman tag ${{ env.IMAGE_REGISTRY }}/${IMAGE_NAME}:${DEFAULT_TAG} $IMAGE_REGISTRY/$IMAGE_NAME:$DEFAULT_TAG-$PLATFORM + for i in {1..3}; do + sudo podman push --digestfile=/tmp/digestfile $IMAGE_REGISTRY/$IMAGE_NAME:$DEFAULT_TAG-$PLATFORM && break || sleep $((5 * i)); + done + REMOTE_IMAGE_DIGEST=$(cat /tmp/digestfile) + echo "remote_image_digest=$REMOTE_IMAGE_DIGEST" >> $GITHUB_OUTPUT + cat /tmp/digestfile - - name: Get current date + # This section is optional and only needs to be enabled in you plan on distributing + # your project to others to consume. You will need to create a public and private key + # using Cosign and save the private key as a repository secret in Github for this workflow + # to consume. For more details, review the image signing section of the README. + - name: Install Cosign + uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + + - name: Sign Image + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + run: | + IMAGE_FULL="${{ env.IMAGE_REGISTRY }}/${IMAGE_NAME}" + cosign sign -y --key env://COSIGN_PRIVATE_KEY ${IMAGE_FULL}@${{ steps.push.outputs.remote_image_digest }} + env: + TAGS: ${{ steps.push.outputs.digest }} + COSIGN_EXPERIMENTAL: false + COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} + + - name: Create Job Outputs + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + env: + IMAGE_NAME: ${{ env.IMAGE_NAME }} + PLATFORM: ${{ matrix.platform }} + DIGEST: ${{ steps.push.outputs.remote_image_digest }} + run: | + mkdir -p /tmp/outputs/digests + echo "${DIGEST}" > /tmp/outputs/digests/${IMAGE_NAME}-${PLATFORM}.txt + + - name: Upload Output Artifacts + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + uses: actions/upload-artifact@v4 + with: + name: ${{ env.IMAGE_NAME }}-${{ matrix.platform }} + retention-days: 1 + if-no-files-found: error + path: | + /tmp/outputs/digests/*.txt + + manifest: + runs-on: ubuntu-latest + # if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + needs: + - build_push + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Get Build Date id: date run: | # Should generate a timestamp like what is defined on the ArtifactHub documentation @@ -93,68 +225,65 @@ jobs: sep-tags: " " sep-annotations: " " - - name: Build Image - id: build-image - shell: bash + - name: Fetch Build Outputs + uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4 + with: + pattern: ${{ env.IMAGE_NAME }}-* + merge-multiple: true + path: /tmp/artifacts + + - name: Load Outputs + id: load-outputs run: | - just=$(which just) - sudo $just build "${IMAGE_NAME}" "${DEFAULT_TAG}" + DIGESTS_JSON=$(jq -n '{}') + for digest_file in /tmp/artifacts/*.txt; do + # Extract the platform from the file name + PLATFORM=$(basename $digest_file | rev | cut -d'-' -f1 | rev | cut -d'.' -f1) + DIGEST=$(cat $digest_file) + # Add the platform and digest to the JSON object + DIGESTS_JSON=$(echo "$DIGESTS_JSON" | jq --arg key "$PLATFORM" --arg value "$DIGEST" '. + {($key): $value}') + done + echo "DIGESTS_JSON=$(echo $DIGESTS_JSON | jq -c '.')" >> $GITHUB_OUTPUT - # Reprocess raw-img using rechunker which will delete it - - name: Run Rechunker - id: rechunk - uses: hhd-dev/rechunk@0aee79e67dc9354779924ccc0fcdd7ef0b874d0d # v1.1.2 - with: - rechunk: 'ghcr.io/hhd-dev/rechunk:v1.0.1' - ref: "localhost/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" - prev-ref: "${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.DEFAULT_TAG }}" - skip_compression: true - version: ${{ env.CENTOS_VERSION }} - labels: ${{ steps.metadata.outputs.labels }} # Rechunk strips out all the labels during build, this needs to be reapplied here with newline separator + - name: Create Manifest + id: create-manifest + run: | + podman manifest create ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }} + echo "MANIFEST=${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT - - name: Load in podman and tag + - name: Populate Manifest + env: + MANIFEST: ${{ steps.create-manifest.outputs.MANIFEST }} + DIGESTS_JSON: ${{ steps.load-outputs.outputs.DIGESTS_JSON }} + LABELS: ${{ steps.metadata.outputs.labels }} run: | - IMAGE=$(podman pull ${{ steps.rechunk.outputs.ref }}) - sudo rm -rf ${{ steps.rechunk.outputs.output }} - for tag in ${{ steps.metadata.outputs.tags }}; do - podman tag $IMAGE ${{ env.IMAGE_NAME }}:$tag + DIGESTS=$(echo "$DIGESTS_JSON" | jq -c '.') + PLATFORMS=(${{ env.PLATFORMS }}) + for platform in ${PLATFORMS[@]}; do + digest=$(echo $DIGESTS | jq -r ".$platform") + echo "Adding ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}@$digest for $platform" + podman manifest add $MANIFEST ${{ env.IMAGE_REGISTRY }}/${{ env.IMAGE_NAME }}@$digest --arch $platform done - - name: Login to GitHub Container Registry - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3 - if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + # Apply the labels to the manifest + for label in $(echo $LABELS | tr ' ' '\n'); do + podman manifest annotate --index --annotation=$label $MANIFEST + done + + - name: Login to GHCR + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # Push the image to GHCR (Image Registry) - - name: Push To GHCR - uses: redhat-actions/push-to-registry@5ed88d269cf581ea9ef6dd6806d01562096bee9c # v2 - if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) - id: push - with: - registry: ${{ env.IMAGE_REGISTRY }} - image: ${{ env.IMAGE_NAME }} - tags: ${{ steps.metadata.outputs.tags }} - - # This section is optional and only needs to be enabled in you plan on distributing - # your project to others to consume. You will need to create a public and private key - # using Cosign and save the private key as a repository secret in Github for this workflow - # to consume. For more details, review the image signing section of the README. - - - name: Install Cosign - uses: sigstore/cosign-installer@dc72c7d5c4d10cd6bcb8cf6e3fd625a9e5e537da # v3.7.0 - if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) - - - name: Sign container image - if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch) + - name: Push Manifest + env: + MANIFEST: ${{ steps.create-manifest.outputs.MANIFEST }} + TAGS: ${{ steps.metadata.outputs.tags }} + IMAGE_REGISTRY: ${{ env.IMAGE_REGISTRY }} + IMAGE_NAME: ${{ env.IMAGE_NAME }} run: | - IMAGE_FULL="${{ env.IMAGE_REGISTRY }}/${IMAGE_NAME}" - for tag in ${{ steps.metadata.outputs.tags }}; do - cosign sign -y --key env://COSIGN_PRIVATE_KEY $IMAGE_FULL:$tag + for tag in $(echo $TAGS | tr ' ' '\n'); do + podman manifest push --all=false $MANIFEST $IMAGE_REGISTRY/$IMAGE_NAME:$tag done - env: - TAGS: ${{ steps.push.outputs.digest }} - COSIGN_EXPERIMENTAL: false - COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }} diff --git a/Containerfile b/Containerfile index 5031baf..8d3f806 100644 --- a/Containerfile +++ b/Containerfile @@ -9,7 +9,5 @@ COPY build.sh /tmp/build.sh RUN --mount=type=bind,from=config,src=/rpms,dst=/tmp/rpms chmod +x /tmp/build.sh && \ /tmp/build.sh && \ dnf clean all && \ - ostree container commit - -# Just gotta get this green! -RUN bootc container lint + ostree container commit && \ + bootc container lint diff --git a/Justfile b/Justfile index 48a45db..a274bb4 100644 --- a/Justfile +++ b/Justfile @@ -91,7 +91,7 @@ build $target_image=image_name $tag=default_tag: podman build \ "${BUILD_ARGS[@]}" \ --pull=newer \ - --tag "${image_name}:${tag}" \ + --tag "${target_image}:${tag}" \ . _rootful_load_image $target_image=image_name $tag=default_tag: