redhat-actions / buildah-build

GitHub Action to use 'buildah' to build a container image.
https://github.com/marketplace/actions/buildah-build
MIT License
136 stars 35 forks source link

[FEATURE] Matrix Strategy Multi-Arch example #121

Open kaovilai opened 1 year ago

kaovilai commented 1 year ago

Is your feature request related to a problem? Please describe.

Clone of https://github.com/docker/docs/issues/16593

Describe the solution you'd like

similar to https://github.com/docker/docs/pull/17180

Describe alternatives you've considered

Additional context

This should allow us to use more runner resources for longer multiarch builds.

graph TD;
    Workflow---|amd64|runner1;
    Workflow---|arm64|runner2;
    Workflow---|arm32v5|runner3;
    Workflow---|arm32v6|runner4;
    Workflow---|arm32v7|runner5;
    Workflow---|arm64v8|runner6;
    Workflow---|i386|runner7;
    Workflow---|ppc64le|runner8;
    Workflow---|s390x|runner9;
    runner1-->Z[podman push multi-arch-image]
    runner2-->Z
    runner3-->Z
    runner4-->Z
    runner5-->Z
    runner6-->Z
    runner7-->Z
    runner8-->Z
    runner9-->Z
Raboo commented 10 months ago

I was also caught with the same problem.

I am able to build multiple architectures. But not publish, it simply overwrites the tag so only one architecture is available in ghcr.io.

name: Build container images
on: [push, workflow_dispatch]

concurrency:
  group: '${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'
  cancel-in-progress: true

jobs:
  build-image:
    name: Build image
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    strategy:
      matrix:
        platform: [linux/amd64, linux/arm64]

    steps:
    - name: Checkout code
      uses: actions/checkout@v3
      with:
        fetch-depth: 0
        submodules: true

    - name: cache podman
      uses: actions/cache@v3
      with:
        path: ~/.local/share/containers
        key: podman

    - name: Set up QEMU
      uses: docker/setup-qemu-action@v2

    - name: set lower case owner name
      run: |
        echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
      env:
        OWNER: '${{ github.repository_owner }}'

    - name: Container meta
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: |
          ghcr.io/${{ env.OWNER_LC }}/rtfm
        tags: |
          type=semver,pattern={{version}},value=${{ inputs.version }}
          type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }}
          type=semver,pattern={{major}},value=${{ inputs.version }}
          type=ref,event=branch
          type=ref,event=pr
          # type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
        flavor: |
          latest=${{ github.ref_name == github.event.repository.default_branch }}

    - name: Build Image
      id: build-image
      uses: redhat-actions/buildah-build@v2
      with:
        image: rtfm
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        oci: true
        containerfiles: |
          ./Dockerfile
        platforms: ${{ matrix.platform }}

    # I guess this is the part that needs some type of resource sharing between the runners.
    - name: Push image to GHCR
      uses: redhat-actions/push-to-registry@v2
      id: push
      with:
        image: ${{ steps.build-image.outputs.image }}
        tags: ${{ steps.build-image.outputs.tags }}
        username: ${{ github.actor }}
        password: ${{ github.token }}
        registry: ghcr.io/${{ env.OWNER_LC }}

    - name: Print image url
      run: echo "Image pushed to ${{ steps.push.outputs.registry-paths }}"

Anyone has a good solution for this?

kaovilai commented 10 months ago

you would have to create manifests list containing different (already pushed single-arch, built by different runners) tags then push the manifest list in the final runner

Raboo commented 10 months ago

Ok, I cracked it!

Here is a working example using multiple runners to build for each architecture, that later upload their images to github temporary storage and a runner that downloads the different architecture images and merges them and publishes to GHCR.

name: Build container images
on: [push, workflow_dispatch]

concurrency:
  group: '${{ github.event.pull_request.head.label || github.head_ref || github.ref }}'

jobs:
  build:
    name: Build image
    runs-on: ubuntu-latest
    strategy:
      matrix:
        platform: [linux/amd64, linux/arm64]

    steps:
    - name: Checkout code
      uses: actions/checkout@v3
      with:
        fetch-depth: 0
        submodules: true

    - name: cache podman storage
      uses: actions/cache@v3
      with:
        path: ~/.local/share/containers/storage
        key: podman-storage-${{ matrix.platform }}

    - name: Set up QEMU
      uses: docker/setup-qemu-action@v2

    - name: set lower case owner name
      run: |
        echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
      env:
        OWNER: '${{ github.repository_owner }}'

    - name: export architecture name (removing os prefix)
      run: |
        echo "PODMAN_ARCH=${PLATFORM#*/}" >>${GITHUB_ENV}
      env:
        PLATFORM: ${{ matrix.platform }}

    - name: Container meta
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: |
          ghcr.io/${{ env.OWNER_LC }}/rtfm
        tags: |
          type=raw,value=build
        flavor: |
          suffix=-${{ env.PODMAN_ARCH }}

    - name: Build Image
      id: build-image
      uses: redhat-actions/buildah-build@v2
      with:
        image: rtfm
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        oci: true
        containerfiles: |
          ./Dockerfile
        platforms: ${{ matrix.platform }}

    - run: skopeo copy containers-storage:ghcr.io/${{ env.OWNER_LC }}/rtfm:build-${{ env.PODMAN_ARCH }} oci-archive:/tmp/${{ env.PODMAN_ARCH }}-oci.tar

    - name: Upload digest
      uses: actions/upload-artifact@v3
      with:
        name: rtfm-build-${{ env.PODMAN_ARCH }}
        path: /tmp/${{ env.PODMAN_ARCH }}-oci.tar
        if-no-files-found: error
        retention-days: 1

  upload:
    name: Upload images
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    needs: build
    steps:

    - name: Download podman
      uses: actions/download-artifact@v3
      with:
        name: rtfm-build-amd64
        path: /tmp
    - name: Download podman
      uses: actions/download-artifact@v3
      with:
        name: rtfm-build-arm64
        path: /tmp

    - name: set lower case owner name
      run: |
        echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV}
      env:
        OWNER: '${{ github.repository_owner }}'

    - run: podman manifest create ghcr.io/${{ env.OWNER_LC }}/rtfm:manifest
    - run: podman manifest add ghcr.io/${{ env.OWNER_LC }}/rtfm:manifest oci-archive:/tmp/arm64-oci.tar
    - run: podman manifest add ghcr.io/${{ env.OWNER_LC }}/rtfm:manifest oci-archive:/tmp/amd64-oci.tar
    # - run: podman manifest inspect ghcr.io/${{ env.OWNER_LC }}/rtfm:manifest

    - name: Container meta
      id: meta
      uses: docker/metadata-action@v4
      with:
        images: |
          ghcr.io/${{ env.OWNER_LC }}/rtfm
        tags: |
          type=semver,pattern={{version}},value=${{ inputs.version }}
          type=semver,pattern={{major}}.{{minor}},value=${{ inputs.version }}
          type=semver,pattern={{major}},value=${{ inputs.version }}
          type=ref,event=branch
          type=ref,event=pr
          # type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/') }}
        flavor: |
          latest=${{ github.ref_name == github.event.repository.default_branch }}

    - name: add tags
      run: |
        # fix multi-line issue from steps.meta.outputs.tags
        podman tag ghcr.io/${{ env.OWNER_LC }}/rtfm:manifest $(echo "${{ steps.meta.outputs.tags }}" | tr '\n' ' ')

    - name: Push image to GHCR
      uses: redhat-actions/push-to-registry@v2
      id: push
      with:
        image: rtfm
        tags: ${{ steps.meta.outputs.tags }}
        username: ${{ github.actor }}
        password: ${{ github.token }}
        registry: ghcr.io/${{ env.OWNER_LC }}

    - name: Print image url
      run: echo "Image pushed to ${{ steps.push.outputs.registry-paths }}"

I guess we now need to document this example somewhere.

kaovilai commented 10 months ago

Awesome!