mirror of
https://github.com/ublue-os/brew.git
synced 2026-11-03 21:49:43 -09:00
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: inffy <10782843+inffy@users.noreply.github.com>
250 lines
11 KiB
YAML
250 lines
11 KiB
YAML
name: Build container image
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
schedule:
|
|
- cron: "0 1 * * SUN"
|
|
push:
|
|
branches:
|
|
- main
|
|
paths-ignore:
|
|
- "**/README.md"
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
IMAGE_DESC: "OCI image for Homebrew"
|
|
IMAGE_KEYWORDS: "homebrew"
|
|
IMAGE_LOGO_URL: "https://avatars.githubusercontent.com/u/136393846?s=200&v=4"
|
|
IMAGE_NAME: "${{ github.event.repository.name }}"
|
|
IMAGE_REGISTRY: "ghcr.io/${{ github.repository_owner }}"
|
|
DEFAULT_TAG: "latest"
|
|
|
|
jobs:
|
|
build_push:
|
|
name: Build and push image
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
id-token: write
|
|
timeout-minutes: 60
|
|
strategy:
|
|
fail-fast: true
|
|
matrix:
|
|
platform: ["amd64", "arm64"]
|
|
runs-on: ${{ matrix.platform == 'amd64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
|
|
|
|
- name: Build image
|
|
run: podman build -t "${IMAGE_NAME}:${DEFAULT_TAG}" -f ./Containerfile .
|
|
|
|
- name: Login to GitHub Container Registry
|
|
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
LOGIN_USER: ${{ github.actor }}
|
|
run: |
|
|
echo "${GITHUB_TOKEN}" | podman login -u "${LOGIN_USER}" --password-stdin "ghcr.io"
|
|
echo "${GITHUB_TOKEN}" | docker login -u "${LOGIN_USER}" --password-stdin "ghcr.io"
|
|
|
|
- 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 }}
|
|
MAX_RETRIES: 3
|
|
run: |
|
|
set -x
|
|
|
|
for i in $(seq "${MAX_RETRIES}"); do
|
|
podman push --digestfile=/tmp/digestfile "localhost/${IMAGE_NAME}:${DEFAULT_TAG}" "${IMAGE_REGISTRY}/${IMAGE_NAME}:${DEFAULT_TAG}-${PLATFORM}" && break || sleep $((5 * i));
|
|
done
|
|
echo "remote_image_digest=$(< /tmp/digestfile)" | tee "${GITHUB_OUTPUT}"
|
|
|
|
- name: Install Cosign
|
|
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.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)
|
|
env:
|
|
REMOTE_IMAGE_DIGEST: ${{ steps.push.outputs.remote_image_digest }}
|
|
COSIGN_EXPERIMENTAL: false
|
|
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
|
|
run: cosign sign -y --new-bundle-format=false --use-signing-config=false --key env://COSIGN_PRIVATE_KEY "${IMAGE_REGISTRY}/${IMAGE_NAME}@${REMOTE_IMAGE_DIGEST}"
|
|
|
|
- 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: |
|
|
set -x
|
|
mkdir -p /tmp/outputs/digests
|
|
echo "${DIGEST}" | tee "/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@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
with:
|
|
name: ${{ env.IMAGE_NAME }}-${{ matrix.platform }}
|
|
retention-days: 1
|
|
if-no-files-found: error
|
|
path: |
|
|
/tmp/outputs/digests/*.txt
|
|
|
|
manifest:
|
|
name: Create Manifest
|
|
runs-on: ubuntu-latest
|
|
if: always()
|
|
needs:
|
|
- build_push
|
|
container:
|
|
image: docker.io/library/alpine:latest
|
|
options: --privileged --security-opt seccomp=unconfined
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
id-token: write
|
|
steps:
|
|
- name: Install dependencies
|
|
run: apk add bash coreutils curl docker findutils fuse-overlayfs git grep jq podman sed
|
|
|
|
# Necessary until bootc supports Cosign v3
|
|
- name: Install old cosign
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
# Thank you Bri! :3 @b-
|
|
curl --retry 3 -H "Authorization: Bearer ${GITHUB_TOKEN}" -fsSLo "/usr/bin/cosign" "https://github.com/sigstore/cosign/releases/download/v2.6.1/cosign-linux-amd64"
|
|
chmod +x /usr/bin/cosign
|
|
|
|
- name: Exit on failure
|
|
shell: bash
|
|
env:
|
|
JOBS: ${{ toJson(needs) }}
|
|
run: |
|
|
for i in $(jq -r 'to_entries[] | .value.result' <<< "${JOBS}"); do
|
|
if [ "$i" != "success" ] && [ "$i" != "skipped" ]; then
|
|
echo "Status check not okay!"
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
# This generates a timestamp like what is defined on the ArtifactHub documentation
|
|
# E.G: 2022-02-08T15:38:15Z'
|
|
# https://artifacthub.io/docs/topics/repositories/container-images/
|
|
# https://linux.die.net/man/1/date
|
|
- name: Get current date
|
|
id: date
|
|
run: echo "date=$(date -u +%Y\-%m\-%d\T%H\:%M\:%S\Z)" | tee "${GITHUB_OUTPUT}"
|
|
|
|
- name: Checkout repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
- name: Read Brew Version
|
|
id: brew_version
|
|
run: echo "version=$(cat BREW_VERSION)" >> $GITHUB_OUTPUT
|
|
|
|
- name: Image Metadata
|
|
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5
|
|
id: metadata
|
|
with:
|
|
tags: |
|
|
type=raw,value=${{ env.DEFAULT_TAG }}
|
|
type=raw,value=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}}
|
|
type=raw,value={{date 'YYYYMMDD'}}
|
|
type=raw,value=${{ steps.brew_version.outputs.version }}
|
|
type=ref,event=pr
|
|
type=sha,enable=${{ github.event_name == 'pull_request' }}
|
|
labels: |
|
|
containers.bootc=1
|
|
io.artifacthub.package.deprecated=false
|
|
io.artifacthub.package.keywords=${{ env.IMAGE_KEYWORDS }}
|
|
io.artifacthub.package.license=Apache-2.0
|
|
io.artifacthub.package.logo-url=${{ env.IMAGE_LOGO_URL }}
|
|
io.artifacthub.package.prerelease=false
|
|
io.artifacthub.package.readme-url=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/${{ github.sha }}/README.md
|
|
org.opencontainers.image.created=${{ steps.date.outputs.date }}
|
|
org.opencontainers.image.description=${{ env.IMAGE_DESC }}
|
|
org.opencontainers.image.documentation=https://raw.githubusercontent.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/${{ github.sha }}/README.md
|
|
org.opencontainers.image.source=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/blob/${{ github.sha }}/Containerfile
|
|
org.opencontainers.image.title=${{ env.IMAGE_NAME }}
|
|
org.opencontainers.image.url=https://github.com/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}/tree/${{ github.sha }}
|
|
org.opencontainers.image.vendor=${{ github.repository_owner }}
|
|
org.opencontainers.image.version=${{ env.DEFAULT_TAG }}.{{date 'YYYYMMDD'}}
|
|
|
|
- name: Fetch Build Outputs
|
|
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
|
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
with:
|
|
pattern: ${{ env.IMAGE_NAME }}-*
|
|
merge-multiple: true
|
|
path: /tmp/artifacts
|
|
|
|
- name: Load Outputs
|
|
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
|
shell: bash
|
|
id: load-outputs
|
|
run: |
|
|
set -x
|
|
OUTCSV="$(mktemp)"
|
|
for digest_file in /tmp/artifacts/*.txt; do
|
|
# Extract the platform from the file name
|
|
PLATFORM="$(basename "${digest_file}" ".txt" | sed 's/.*-//g')"
|
|
IMAGE_NAME="$(basename "${digest_file}" ".txt" | grep -Po ".*-" | sed 's/-$//')"
|
|
DIGEST="$(< "${digest_file}")"
|
|
echo "${PLATFORM},${IMAGE_NAME},${DIGEST}" | tee -a "${OUTCSV}"
|
|
done
|
|
echo "OUTPUT_CSV=$(base32 -w 0 < "${OUTCSV}")" | tee -a "${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
|
|
run: |
|
|
echo "${{ secrets.GITHUB_TOKEN }}" | podman login -u "${{ github.actor }}" --password-stdin "${REGISTRY}"
|
|
echo "${{ secrets.GITHUB_TOKEN }}" | docker login -u "${{ github.actor }}" --password-stdin "${REGISTRY}"
|
|
|
|
- name: Publish manifest with unified architecture and sign
|
|
shell: bash
|
|
if: github.event_name != 'pull_request' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
|
env:
|
|
COSIGN_EXPERIMENTAL: false
|
|
COSIGN_PRIVATE_KEY: ${{ secrets.SIGNING_SECRET }}
|
|
LABELS: ${{ steps.metadata.outputs.labels }}
|
|
OUTPUT_CSV: ${{ steps.load-outputs.outputs.OUTPUT_CSV }}
|
|
TAGS: ${{ steps.metadata.outputs.tags }}
|
|
run: |
|
|
set -x
|
|
|
|
OUTPUT_CSV_FILE="$(mktemp)"
|
|
echo "${OUTPUT_CSV}" | base32 -d | tee "${OUTPUT_CSV_FILE}"
|
|
|
|
# This is insanely dumb, please PR if you have a better solution ~ @tulilirockz :3c
|
|
IMAGES=$(cat "${OUTPUT_CSV_FILE}" | while IFS=$'\n' read -r ENTRY ; do echo "${ENTRY}" | cut -d, -f2 ; done | sort | uniq | tr "\n" " " | sed 's/.$//')
|
|
for TARGET_IMAGE in ${IMAGES} ; do
|
|
TARGET_MANIFEST="${IMAGE_REGISTRY}/${TARGET_IMAGE}"
|
|
podman manifest create "${TARGET_MANIFEST}"
|
|
|
|
PLATFORMS=$(grep -F -e "${IMAGE}" "${OUTPUT_CSV_FILE}" | while IFS=$'\n' read -r ENTRY ; do echo "${ENTRY}" | cut -d, -f1 ; done | sort | uniq | tr "\n" " " | sed 's/.$//')
|
|
for TARGET_PLATFORM in ${PLATFORMS} ; do
|
|
TARGET_DIGEST="$(grep -F -e ",${TARGET_IMAGE}," "${OUTPUT_CSV_FILE}" | grep -F -e "${TARGET_PLATFORM}" | cut -d, -f3 | tr -d "\n")"
|
|
podman manifest add "${TARGET_MANIFEST}" "${TARGET_MANIFEST}@${TARGET_DIGEST}" --arch "${TARGET_PLATFORM}"
|
|
done
|
|
|
|
while IFS= read -r LABEL; do
|
|
podman manifest annotate --index --annotation "${LABEL}" "${TARGET_MANIFEST}"
|
|
done <<< "${LABELS}"
|
|
|
|
while IFS= read -r TAG; do
|
|
podman manifest push --all=false --digestfile=/tmp/digestfile "${TARGET_MANIFEST}" "${TARGET_MANIFEST}:${TAG}"
|
|
done <<< "${TAGS}"
|
|
|
|
cosign sign -y --new-bundle-format=false --use-signing-config=false --key env://COSIGN_PRIVATE_KEY "${TARGET_MANIFEST}@$(< /tmp/digestfile)"
|
|
done
|