From b5d89f8895530e366bd9fd65f2c057465491aefd Mon Sep 17 00:00:00 2001 From: Daniel Schmidt Date: Tue, 7 Oct 2025 17:59:00 -0700 Subject: [PATCH] Split workflows for reusability - Separates workflow into distinct stages: - meta: determines the base (sha) and permatags (branch, git-tag) to apply to the image - build: builds the multi-platform dockerfile with a build-specific tag - test: a repo-specific workflow that runs the tests - tag: applies permatags to the tested image - This is a proof-of-concept on the path towards centralizing and versioning the reusable parts of our workflows. The common elements (meta, build, and tag) would be centralized, with individual repos only having to write a test workflow to run their tests and a ci workflow to weave the different reusable workflows together. --- .github/workflows/build.yml | 108 ++++++---------------------------- .github/workflows/ci.yml | 34 +++++++++++ .github/workflows/meta.yml | 42 +++++++++++++ .github/workflows/release.yml | 62 ------------------- .github/workflows/tag.yml | 34 +++++++++++ .github/workflows/test.yml | 34 +++++++++++ 6 files changed, 163 insertions(+), 151 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/meta.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/tag.yml create mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b06f9ad..868d397 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,21 +1,19 @@ -name: Build / Test / Push +name: Build image on: - push: - branches: - - '**' - workflow_dispatch: - -env: - BUILD_SUFFIX: -build-${{ github.run_id }}_${{ github.run_attempt }} - DOCKER_METADATA_SET_OUTPUT_ENV: 'true' + workflow_call: + outputs: + image: + description: ID of the image created by the workflow run + value: ${{ jobs.merge.outputs.image }} jobs: build: runs-on: ${{ matrix.runner }} outputs: - build-image-arm: ${{ steps.gen-output.outputs.image-arm64 }} - build-image-x64: ${{ steps.gen-output.outputs.image-x64 }} + image-digest-arm: ${{ steps.gen-output.outputs.image-digest-arm64 }} + image-digest-x64: ${{ steps.gen-output.outputs.image-digest-x64 }} + build-tag: ${{ steps.build-meta.outputs.tags }} strategy: fail-fast: false matrix: @@ -41,7 +39,7 @@ jobs: uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} - tags: type=sha,suffix=${{ env.BUILD_SUFFIX }} + tags: type=sha,suffix=-build-${{ github.run_id }}_${{ github.run_attempt }} # Build cache is shared among all builds of the same architecture - id: cache-meta @@ -71,17 +69,18 @@ jobs: - id: gen-output name: Write arch-specific image digest to outputs run: | - echo "image-${RUNNER_ARCH,,}=${{ steps.get-registry.outputs.registry }}@${{ steps.build.outputs.digest }}" | tee -a "$GITHUB_OUTPUT" + echo "image-digest-${RUNNER_ARCH,,}=${{ steps.get-registry.outputs.registry }}@${{ steps.build.outputs.digest }}" | tee -a "$GITHUB_OUTPUT" merge: + name: create multi-platform image + needs: build runs-on: ubuntu-24.04 - needs: - - build env: - DOCKER_APP_IMAGE_ARM64: ${{ needs.build.outputs.build-image-arm }} - DOCKER_APP_IMAGE_X64: ${{ needs.build.outputs.build-image-x64 }} + DOCKER_APP_IMAGE: ${{ needs.build.outputs.build-tag }} + DOCKER_APP_IMAGE_DIGEST_ARM64: ${{ needs.build.outputs.image-digest-arm }} + DOCKER_APP_IMAGE_DIGEST_X64: ${{ needs.build.outputs.image-digest-x64 }} outputs: - build-image: ${{ steps.meta.outputs.tags }} + image: ${{ needs.build.outputs.build-tag }} steps: - name: Checkout code uses: actions/checkout@v4 @@ -96,77 +95,8 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=sha,suffix=-build-${{ github.run_id }}_${{ github.run_attempt }} - - name: Push the multi-platform image run: | docker buildx imagetools create \ - --tag "$DOCKER_METADATA_OUTPUT_TAGS" \ - "$DOCKER_APP_IMAGE_ARM64" "$DOCKER_APP_IMAGE_X64" - - test: - runs-on: ubuntu-24.04 - needs: - - merge - env: - COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml - DOCKER_APP_IMAGE: ${{ needs.merge.outputs.build-image }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Docker Compose - uses: docker/setup-compose-action@v1 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Run the test script - run: | - docker compose run --rm --user root app chown -R avplayer:avplayer artifacts - docker compose up --detach --wait - docker compose exec app bin/test - - push: - runs-on: ubuntu-24.04 - needs: - - merge - - test - env: - DOCKER_APP_IMAGE: ${{ needs.merge.outputs.build-image }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Produce permanent image tags - id: branch-meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=sha - type=ref,event=branch - type=raw,value=latest,enable={{is_default_branch}} - - - name: Retag and push the image - run: | - docker pull "$DOCKER_APP_IMAGE" - echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$DOCKER_APP_IMAGE" - docker push --all-tags "$(echo "$DOCKER_APP_IMAGE" | cut -f1 -d:)" + --tag "$DOCKER_APP_IMAGE" \ + "$DOCKER_APP_IMAGE_DIGEST_ARM64" "$DOCKER_APP_IMAGE_DIGEST_X64" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cb05aa6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: Build / Test / Tag + +on: + push: + branches: + - '**' + tags: + - '**' + +jobs: + meta: + name: Determine image permatags + uses: ./.github/workflows/meta.yml + + build: + name: Build the image + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + uses: ./.github/workflows/build.yml + + test: + name: Test built image + if: ${{ !startsWith(github.ref, 'refs/tags/') }} + needs: build + uses: ./.github/workflows/test.yml + with: + image: ${{ needs.build.outputs.image }} + + tag: + name: Tag/push tested image + needs: [build, test, meta] + uses: ./.github/workflows/tag.yml + with: + image: ${{ needs.build.outputs.image || needs.meta.outputs.base-tag }} + tags: ${{ needs.meta.outputs.permatags }} diff --git a/.github/workflows/meta.yml b/.github/workflows/meta.yml new file mode 100644 index 0000000..34c33b4 --- /dev/null +++ b/.github/workflows/meta.yml @@ -0,0 +1,42 @@ +name: Extract image metadata + +on: + workflow_call: + outputs: + base-tag: + description: SHA-based tag for the built ref (org/repo:sha-1234567) + value: ${{ jobs.meta.outputs.base-tag }} + permatags: + description: Space-separated list of all tags to apply to the ref + value: ${{ jobs.meta.outputs.permatags }} + +jobs: + meta: + runs-on: ubuntu-24.04 + outputs: + base-tag: ${{ steps.base-tag.outputs.tags }} + permatags: ${{ steps.permatags.outputs.tags }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - id: base-tag + name: Determine base tag (for git-tag builds) + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: type=sha + + - id: permatags + name: Determine permatags + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=ref,event=tag + type=semver,event=tag,pattern={{major}} + type=semver,event=tag,pattern={{major}}.{{minor}} + type=semver,event=tag,pattern={{version}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index d6564d0..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Push Release Tags - -on: - push: - tags: - - '**' - workflow_dispatch: - -env: - DOCKER_METADATA_SET_OUTPUT_ENV: 'true' - -jobs: - retag: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Determine the sha-based image tag to retag - id: get-base-image - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - tags: type=sha - - - name: Verify that the image was previously built - env: - BASE_IMAGE: ${{ steps.get-base-image.outputs.tags }} - run: | - docker pull "$BASE_IMAGE" - - - name: Produce release tags - id: tag-meta - uses: docker/metadata-action@v5 - with: - images: ghcr.io/${{ github.repository }} - flavor: latest=false - tags: | - type=ref,event=tag - type=semver,pattern={{major}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{version}} - - - name: Retag the pulled image - env: - BASE_IMAGE: ${{ steps.get-base-image.outputs.tags }} - run: | - echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$BASE_IMAGE" - docker push --all-tags "$(echo "$BASE_IMAGE" | cut -f1 -d:)" diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml new file mode 100644 index 0000000..0db5ccb --- /dev/null +++ b/.github/workflows/tag.yml @@ -0,0 +1,34 @@ +name: Tag Docker image + +on: + workflow_call: + inputs: + # Image to be retagged. This must exist in the registry. + image: + type: string + + # Space-separated list of fully qualified image tags (including registry address) + # Format matches the output of `docker/metadata-action`. + tags: + type: string + +env: + DOCKER_APP_IMAGE: ${{ inputs.image }} + DOCKER_METADATA_OUTPUT_TAGS: ${{ inputs.tags }} + +jobs: + tag: + runs-on: ubuntu-24.04 + steps: + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Apply tags to the image + run: | + docker pull "$DOCKER_APP_IMAGE" + echo "$DOCKER_METADATA_OUTPUT_TAGS" | tr ' ' '\n' | xargs -n1 docker tag "$DOCKER_APP_IMAGE" + docker push --all-tags "$(echo "$DOCKER_APP_IMAGE" | cut -f1 -d:)" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..385b4a4 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: Test Docker image + +on: + workflow_call: + inputs: + image: + type: string + +env: + COMPOSE_FILE: docker-compose.yml:docker-compose.ci.yml + DOCKER_APP_IMAGE: ${{ inputs.image }} + +jobs: + test: + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Compose + uses: docker/setup-compose-action@v1 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Run the test script + run: | + docker compose run --rm --user root app chown -R avplayer:avplayer artifacts + docker compose up --detach --wait + docker compose exec app bin/test