Refactor: backporting features, fixes from v2.1.0 #42
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Required repository secrets: | |
| # DOCKERHUB_USERNAME - Docker Hub account username, used for `docker login` and pushes. | |
| # DOCKERHUB_TOKEN - Docker Hub access token (NOT an account password; a 2FA-protected | |
| # password cannot be used) with "Read, Write, Delete" scope. Pushing | |
| # images only needs Read & Write, but peter-evans/dockerhub-description | |
| # requires the Delete scope as well -- a Read & Write token returns | |
| # 403 Forbidden when updating the description. Create one at | |
| # https://hub.docker.com/settings/security. | |
| name: build-test-push | |
| on: | |
| push: | |
| branches: | |
| - master | |
| pull_request: | |
| concurrency: | |
| # Cancel superseded runs on a branch/PR when a new commit is pushed, but let master | |
| # runs finish so an in-flight publish isn't interrupted (half-built manifest list or | |
| # an unpublished directory). | |
| group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} | |
| env: | |
| REPO_NAME: emmercm/libtorrent | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| directory: | |
| - "0.16" | |
| - "1.0" | |
| - "1.1" | |
| - "1.2-py2" | |
| - "1.2-py3" | |
| - "2.0" | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - name: Run hadolint | |
| # Pinned to a full version: hadolint-action does not publish a moving major tag | |
| uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0 | |
| with: | |
| dockerfile: ${{ matrix.directory }}/Dockerfile | |
| # Decide which directories to build: those whose files changed, plus any whose | |
| # image tag is not yet published (e.g. a version bump, which changes this file | |
| # rather than the directory). Unchanged, already-published directories are skipped. | |
| setup: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| # build job's matrix: cross product of selected directories and platforms | |
| build_matrix: ${{ steps.select.outputs.build_matrix }} | |
| # merge job's matrix: selected directories only (one manifest list per directory) | |
| merge_matrix: ${{ steps.select.outputs.merge_matrix }} | |
| # whether any directory was selected to build at all | |
| has_builds: ${{ steps.select.outputs.has_builds }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - name: Detect changed directories | |
| uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4 | |
| id: filter | |
| with: | |
| filters: | | |
| "0.16": "0.16/**" | |
| "1.0": "1.0/**" | |
| "1.1": "1.1/**" | |
| "1.2-py2": "1.2-py2/**" | |
| "1.2-py3": "1.2-py3/**" | |
| "2.0": "2.0/**" | |
| - name: Select directories to build | |
| id: select | |
| env: | |
| CHANGED: ${{ steps.filter.outputs.changes }} | |
| # Buildable directories with their libtorrent version and extra tags. | |
| # The implicit ${VERSION} and ${VERSION}-alpine tags are always added too. | |
| # https://github.com/arvidn/libtorrent/releases | |
| CANDIDATES: | | |
| [ | |
| {"directory": "0.16", "version": "0.16.17", "tags": "0.16 0.16-alpine 0 0-alpine"}, | |
| {"directory": "1.0", "version": "1.0.11", "tags": "1.0 1.0-alpine"}, | |
| {"directory": "1.1", "version": "1.1.14", "tags": "1.1 1.1-alpine"}, | |
| {"directory": "1.2-py2", "version": "1.2.11", "tags": ""}, | |
| {"directory": "1.2-py3", "version": "1.2.20", "tags": "1.2 1.2-alpine 1 1-alpine"}, | |
| {"directory": "2.0", "version": "2.0.13", "tags": "2.0 2.0-alpine 2 2-alpine latest"} | |
| ] | |
| run: | | |
| SELECTED='[]' | |
| while read -r ENTRY; do | |
| DIRECTORY=$(jq -r '.directory' <<< "${ENTRY}") | |
| VERSION=$(jq -r '.version' <<< "${ENTRY}") | |
| if jq -e --arg d "${DIRECTORY}" 'index($d) != null' <<< "${CHANGED}" > /dev/null; then | |
| echo "${DIRECTORY}: files changed, building" | |
| elif ! curl -fsS "https://hub.docker.com/v2/repositories/${REPO_NAME}/tags/${VERSION}" &> /dev/null; then | |
| echo "${DIRECTORY}: ${REPO_NAME}:${VERSION} not yet published, building" | |
| else | |
| echo "${DIRECTORY}: unchanged and already published, skipping" | |
| continue | |
| fi | |
| SELECTED=$(jq -c ". + [${ENTRY}]" <<< "${SELECTED}") | |
| done < <(jq -c '.[]' <<< "${CANDIDATES}") | |
| # Each platform builds on the closest runner architecture to minimize QEMU | |
| # emulation: amd64 natively on amd64, and the arm variants on ARM hosts | |
| # (arm64v8 natively; armv7/armv6 via aarch32 emulation). | |
| PLATFORMS='[ | |
| {"name": "linux/amd64", "slug": "amd64", "runner": "ubuntu-latest"}, | |
| {"name": "linux/arm64/v8", "slug": "arm64v8", "runner": "ubuntu-24.04-arm"}, | |
| {"name": "linux/arm/v7", "slug": "armv7", "runner": "ubuntu-24.04-arm"}, | |
| {"name": "linux/arm/v6", "slug": "armv6", "runner": "ubuntu-24.04-arm"} | |
| ]' | |
| echo "Selected directories: ${SELECTED}" | |
| echo "merge_matrix={\"include\":${SELECTED}}" >> "${GITHUB_OUTPUT}" | |
| echo "build_matrix={\"entry\":${SELECTED},\"platform\":$(jq -c . <<< "${PLATFORMS}")}" >> "${GITHUB_OUTPUT}" | |
| if [[ "${SELECTED}" == "[]" ]]; then | |
| echo "has_builds=false" >> "${GITHUB_OUTPUT}" | |
| else | |
| echo "has_builds=true" >> "${GITHUB_OUTPUT}" | |
| fi | |
| # One job per (directory, platform). Each builds and tests a single-platform image, | |
| # then (on master) pushes it to the registry by digest only -- no tags. The tags are | |
| # applied later by the merge job, so nothing usable is published until everything passes. | |
| build: | |
| needs: | |
| - lint | |
| - setup | |
| if: needs.setup.outputs.has_builds == 'true' | |
| runs-on: ${{ matrix.platform.runner }} | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.setup.outputs.build_matrix) }} | |
| env: | |
| DIRECTORY: ${{ matrix.entry.directory }} | |
| VERSION: ${{ matrix.entry.version }} | |
| PLATFORM: ${{ matrix.platform.name }} | |
| # Cache scope keeps each directory+platform's layers separate | |
| CACHE_SCOPE: ${{ matrix.entry.directory }}-${{ matrix.platform.slug }} | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| # Needed to run the armv7/armv6 (aarch32) images on the arm64 host during testing | |
| - name: Set up QEMU | |
| uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 | |
| - name: Build image for testing | |
| uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 | |
| with: | |
| context: ${{ env.DIRECTORY }} | |
| platforms: ${{ env.PLATFORM }} | |
| build-args: | | |
| VERSION=${{ env.VERSION }} | |
| tags: ${{ env.REPO_NAME }}:${{ env.VERSION }} | |
| load: true | |
| cache-from: type=gha,scope=${{ env.CACHE_SCOPE }} | |
| cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }} | |
| - name: Test image | |
| run: | | |
| # container-structure-test runs on the host, so fetch the runner's architecture | |
| case "${RUNNER_ARCH}" in | |
| X64) CST_ARCH=amd64 ;; | |
| ARM64) CST_ARCH=arm64 ;; | |
| *) echo "Unsupported runner architecture: ${RUNNER_ARCH}" >&2; exit 1 ;; | |
| esac | |
| curl -LO "https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-${CST_ARCH}" | |
| chmod +x "container-structure-test-linux-${CST_ARCH}" | |
| sudo mv "container-structure-test-linux-${CST_ARCH}" /usr/local/bin/container-structure-test | |
| container-structure-test test --config "${DIRECTORY}/container-structure-test.yml" --image "${REPO_NAME}:${VERSION}" | |
| - name: Log in to Docker Hub | |
| if: github.ref == 'refs/heads/master' | |
| uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Push image by digest | |
| id: push | |
| if: github.ref == 'refs/heads/master' | |
| uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7 | |
| with: | |
| context: ${{ env.DIRECTORY }} | |
| platforms: ${{ env.PLATFORM }} | |
| build-args: | | |
| VERSION=${{ env.VERSION }} | |
| outputs: type=image,name=${{ env.REPO_NAME }},push-by-digest=true,name-canonical=true,push=true | |
| cache-from: type=gha,scope=${{ env.CACHE_SCOPE }} | |
| - name: Export digest | |
| if: github.ref == 'refs/heads/master' | |
| run: | | |
| mkdir -p digests | |
| echo "${{ steps.push.outputs.digest }}" > "digests/${{ matrix.platform.slug }}" | |
| - name: Upload digest | |
| if: github.ref == 'refs/heads/master' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: digests-${{ matrix.entry.directory }}-${{ matrix.platform.slug }} | |
| path: digests/* | |
| if-no-files-found: error | |
| # One job per directory: combine its per-platform digests into a single multi-arch | |
| # manifest list and apply all of the directory's tags. Runs only after every build | |
| # job succeeded, so a failure anywhere prevents any tags from being published. | |
| merge: | |
| needs: | |
| - setup | |
| - build | |
| if: github.ref == 'refs/heads/master' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.setup.outputs.merge_matrix) }} | |
| env: | |
| VERSION: ${{ matrix.version }} | |
| TAGS: ${{ matrix.tags }} | |
| steps: | |
| - name: Download digests | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 | |
| with: | |
| pattern: digests-${{ matrix.directory }}-* | |
| path: digests | |
| merge-multiple: true | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4 | |
| - name: Log in to Docker Hub | |
| uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| - name: Create and push manifest list | |
| run: | | |
| TAG_ARGS=() | |
| for TAG in "${VERSION}" "${VERSION}-alpine" ${TAGS}; do | |
| TAG_ARGS+=(--tag "${REPO_NAME}:${TAG}") | |
| done | |
| SOURCES=() | |
| for FILE in digests/*; do | |
| SOURCES+=("${REPO_NAME}@$(cat "${FILE}")") | |
| done | |
| docker buildx imagetools create "${TAG_ARGS[@]}" "${SOURCES[@]}" | |
| - name: Inspect manifest list | |
| run: docker buildx imagetools inspect "${REPO_NAME}:${VERSION}" | |
| # Independent of the image build: the Docker Hub description tracks README.md, so | |
| # it is updated on every master push even when no directory needed rebuilding. | |
| update-description: | |
| if: github.ref == 'refs/heads/master' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| - name: Read short description from README | |
| id: readme | |
| run: echo "short=$(grep '^\w' README.md | head -1)" >> "${GITHUB_OUTPUT}" | |
| - name: Update Docker Hub description | |
| # Can't use 2FA: https://github.com/docker/roadmap/issues/115 | |
| uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5 | |
| with: | |
| username: ${{ secrets.DOCKERHUB_USERNAME }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| repository: ${{ env.REPO_NAME }} | |
| short-description: ${{ steps.readme.outputs.short }} | |
| # !!! This check should be required by GitHub !!! | |
| build-status-check: | |
| if: always() | |
| needs: | |
| - lint | |
| - setup | |
| - build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: All checks passed | |
| uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1 | |
| with: | |
| allowed-skips: build | |
| jobs: ${{ toJSON(needs) }} |