diff --git a/.github/act/multiRunnerTest.yml b/.github/act/multiRunnerTest.yml index 552da892..cdf9f477 100644 --- a/.github/act/multiRunnerTest.yml +++ b/.github/act/multiRunnerTest.yml @@ -37,10 +37,10 @@ jobs: - name: Prepare run: | platform=${{ matrix.platform }} - echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set short git commit SHA id: appvars @@ -64,17 +64,17 @@ jobs: run: | echo appversion=$APP_VERSION >>${GITHUB_OUTPUT} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: | - ${{ env.DOCKERHUB_SLUG }} - ${{ env.GHCR_SLUG }} - labels: | - org.opencontainers.image.title=Multi-Scrobbler - org.opencontainers.image.description=Scrobble from many sources to many clients - org.opencontainers.image.vendor=FoxxMD + # - name: Extract metadata (tags, labels) for Docker + # id: meta + # uses: docker/metadata-action@v5 + # with: + # images: | + # ${{ env.DOCKERHUB_SLUG }} + # ${{ env.GHCR_SLUG }} + # labels: | + # org.opencontainers.image.title=Multi-Scrobbler + # org.opencontainers.image.description=Scrobble from many sources to many clients + # org.opencontainers.image.vendor=FoxxMD - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -138,6 +138,19 @@ jobs: path: /tmp/digests pattern: digests-* merge-multiple: true + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -155,7 +168,7 @@ jobs: type=edge # maybe re-enable branch-named tags in the futures - #type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }} + type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }} # tag non-prelease as latest -- has a higher priority than regular tag so it shows first in registries type=match,pattern=\d.\d.\d$,priority=901 @@ -169,27 +182,17 @@ jobs: org.opencontainers.image.description=Scrobble from many sources to many clients org.opencontainers.image.vendor=FoxxMD - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - name: Create manifest list and push working-directory: /tmp/digests run: | - docker buildx imagetools create $(jq -cr '.tags | map(select(startswith("${{ env.DOCKERHUB_SLUG }}")) | "-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.DOCKERHUB_SLUG }}@sha256:%s ' *) - docker buildx imagetools create $(jq -cr '.tags | map(select(startswith("${{ env.GHCR_SLUG }}")) | "-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.GHCR_SLUG }}@sha256:%s ' *) + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.GHCR_SLUG }}@sha256:%s ' *) - # - name: Inspect image - # run: | - # docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }} \ No newline at end of file + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }} + docker buildx imagetools inspect ${{ env.GHCR_SLUG }}:${{ steps.meta.outputs.version }} \ No newline at end of file diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index acc797d2..54a4ed83 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,5 +1,9 @@ name: PR Workflow +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.head.sha }} + cancel-in-progress: true + on: pull_request_target: types: @@ -17,11 +21,11 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - release-snapshot: - name: Release snapshot - runs-on: ubuntu-latest + build-snapshot: + name: Build OCI snapshot needs: test if: contains(github.event.pull_request.labels.*.name, 'safe to test') + runs-on: ${{ matrix.os }} permissions: packages: write contents: read @@ -29,64 +33,168 @@ jobs: fail-fast: false matrix: include: - - dockerfile: ./Dockerfile - suffix: '' - platforms: 'linux/amd64,linux/arm64' + - os: ubuntu-latest + arch: amd64 + platform: linux/amd64 + - os: ubuntu-24.04-arm + arch: arm64 + platform: linux/arm64 steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + # list all registries to push to and join all non-empty with comma + # https://unix.stackexchange.com/a/693165/116849 + # https://stackoverflow.com/a/9429887/1469797 + strings=("${{vars.DOCKERHUB_SLUG}}" "${{vars.GHCR_SLUG}}") + for i in ${!strings[@]}; do [[ -z ${strings[i]} ]] && unset strings[i]; done + joined_string=$(IFS=, ; echo "${strings[*]}") + echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV + + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set short git commit SHA + run: | + short=$(echo "${{ github.event.pull_request.head.sha }}" | head -c7) + echo "COMMIT_SHORT_SHA=$short" >> $GITHUB_ENV + + - name: Get App Version + id: appversion + env: + APP_VERSION: ${{ format('pr{0}-{1}', github.event.number, env.COMMIT_SHORT_SHA ) }} + run: | + echo appversion=$APP_VERSION >>${GITHUB_OUTPUT} + + - name: Login to Docker Hub + if: ${{ vars.DOCKERHUB_SLUG != '' }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to GitHub Container Registry + if: ${{ vars.GHCR_SLUG != '' }} + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + # metadata extract for docker labels/image names is done in merge job - name: Set up Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub + # https://github.com/docker/build-push-action/issues/671#issuecomment-1619353328 + # for caching + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + build-args: | + APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}} + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true + #cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }} + #cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }} + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + release-snapshot: + name: Merge OCI Images and Push + if: ${{ vars.DOCKERHUB_SLUG != '' || vars.GHCR_SLUG != '' }} + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + needs: + - build-snapshot + - test + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Login to Docker Hub + if: ${{ vars.DOCKERHUB_SLUG != '' }} uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry + if: ${{ vars.GHCR_SLUG != '' }} uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata (tags, labels) id: meta uses: docker/metadata-action@v5 with: images: | - foxxmd/multi-scrobbler - ghcr.io/foxxmd/multi-scrobbler + ${{ vars.DOCKERHUB_SLUG }} + ${{ vars.GHCR_SLUG }} + # generate Docker tags based on the following events/attributes + # https://github.com/docker/metadata-action/issues/247#issuecomment-1511259674 for NOT is default branch, eventually tags: | - type=ref,event=pr,suffix=${{ matrix.suffix }} + type=ref,event=pr flavor: | latest=false + labels: | + org.opencontainers.image.title=Multi-Scrobbler + org.opencontainers.image.description=Scrobble from many sources to many clients + org.opencontainers.image.vendor=FoxxMD + + - name: Create manifest list and push dockerhub + if: ${{ vars.DOCKERHUB_SLUG != '' }} + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ vars.DOCKERHUB_SLUG }}@sha256:%s ' *) + + - name: Create manifest list and push gchr + if: ${{ vars.GHCR_SLUG != '' }} + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ vars.GHCR_SLUG }}@sha256:%s ' *) + + - name: Inspect image dockerhub + if: ${{ vars.DOCKERHUB_SLUG != '' }} + run: | + docker buildx imagetools inspect ${{ vars.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }} - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.head.sha }} - - - name: Set short git commit SHA + - name: Inspect image ghcr + if: ${{ vars.GHCR_SLUG != '' }} run: | - short=$(echo "${{ github.event.pull_request.head.sha }}" | head -c7) - echo "COMMIT_SHORT_SHA=$short" >> $GITHUB_ENV + docker buildx imagetools inspect ${{ vars.GHCR_SLUG }}:${{ steps.meta.outputs.version }} - - name: Build and push - id: docker_build - uses: docker/build-push-action@v5 - env: - APP_VERSION: ${{ format('pr{0}-{1}', github.event.number, env.COMMIT_SHORT_SHA ) }} - with: - context: . - build-args: | - APP_BUILD_VERSION=${{env.APP_VERSION}} - file: ${{ matrix.dockerfile }} - push: ${{ !env.ACT}} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: ${{ matrix.platforms }} combine-and-comment: name: Leave comment diff --git a/.github/workflows/publishImage.yml b/.github/workflows/publishImage.yml index 991f7dc4..2b7244d5 100644 --- a/.github/workflows/publishImage.yml +++ b/.github/workflows/publishImage.yml @@ -1,5 +1,11 @@ +# Based on https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners + name: Publish Docker image to Dockerhub +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + on: workflow_dispatch: push: @@ -18,26 +24,54 @@ on: # release: # types: [ published ] +# define in GH Repository -> Actions -> Variables (or act .variables) to enable pushing to registries +# -- will only push to registries that are defined +# EX +# DOCKERHUB_SLUG=foxxmd/multi-scrobbler +# GHCR_SLUG=ghcr.io/foxxmd/multi-scrobbler + jobs: test: if: github.event_name != 'pull_request' uses: ./.github/workflows/testAndSanity.yml - push_to_registry: - name: Build and push container images - if: github.event_name != 'pull_request' - runs-on: ubuntu-latest + build: + name: Build OCI Images + if: ${{ github.event_name != 'pull_request' && (vars.DOCKERHUB_SLUG != '' || vars.GHCR_SLUG != '') }} needs: test - # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token + runs-on: ${{ matrix.os }} permissions: packages: write contents: read + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + arch: amd64 + platform: linux/amd64 + - os: ubuntu-24.04-arm + arch: arm64 + platform: linux/arm64 steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + # list all registries to push to and join all non-empty with comma + # https://unix.stackexchange.com/a/693165/116849 + # https://stackoverflow.com/a/9429887/1469797 + strings=("${{vars.DOCKERHUB_SLUG}}" "${{vars.GHCR_SLUG}}") + for i in ${!strings[@]}; do [[ -z ${strings[i]} ]] && unset strings[i]; done + joined_string=$(IFS=, ; echo "${strings[*]}") + echo "REGISTRIES_JOINED=$joined_string" >> $GITHUB_ENV + - name: Check out the repo uses: actions/checkout@v4 - name: Set short git commit SHA - id: vars + id: appvars # https://dev.to/hectorleiva/github-actions-and-creating-a-short-sha-hash-8b7 # short sha available under env.COMMIT_SHORT_SHA run: | @@ -46,33 +80,116 @@ jobs: echo "COMMIT_SHORT_SHA=$calculatedSha" >> $GITHUB_ENV echo "COMMIT_BRANCH=$branchName" >> $GITHUB_ENV - - name: Log in to Docker Hub + - name: Get App Version + id: appversion + env: + # use release instead of tags once version is correctly parsed + #APP_VERSION: ${{ github.event.release.tag_name }} + + # https://github.com/actions/runner/issues/409#issuecomment-752775072 + # https://stackoverflow.com/a/69919067/1469797 + APP_VERSION: ${{ contains(github.ref, 'refs/tags/') && github.ref_name || format('{0}-{1}', env.COMMIT_BRANCH, env.COMMIT_SHORT_SHA ) }} + run: | + echo appversion=$APP_VERSION >>${GITHUB_OUTPUT} + + - name: Login to Docker Hub + if: ${{ github.event_name != 'pull_request' && vars.DOCKERHUB_SLUG != '' }} + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Login to GitHub Container Registry + if: ${{ github.event_name != 'pull_request' && vars.GHCR_SLUG != '' }} + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + # metadata extract for docker labels/image names is done in merge job + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # https://github.com/docker/build-push-action/issues/671#issuecomment-1619353328 + # for caching + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + build-args: | + APP_BUILD_VERSION=${{steps.appversion.outputs.appversion}} + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,"name=${{ env.REGISTRIES_JOINED }}",push-by-digest=true,name-canonical=true,push=true + #cache-from: type=gha,scope=build-${{ env.PLATFORM_PAIR }} + #cache-to: type=gha,scope=build-${{ env.PLATFORM_PAIR }} + + - name: Export digest + run: | + mkdir -p /tmp/digests + digest="${{ steps.build.outputs.digest }}" + touch "/tmp/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: /tmp/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + name: Merge OCI Images and Push + if: ${{ github.event_name != 'pull_request' && (vars.DOCKERHUB_SLUG != '' || vars.GHCR_SLUG != '') }} + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + needs: + - build + - test + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + + - name: Login to Docker Hub + if: ${{ github.event_name != 'pull_request' && vars.DOCKERHUB_SLUG != '' }} uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Login to GitHub Container Registry + if: ${{ github.event_name != 'pull_request' && vars.GHCR_SLUG != '' }} uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata (tags, labels) id: meta uses: docker/metadata-action@v5 with: images: | - foxxmd/multi-scrobbler - ghcr.io/foxxmd/multi-scrobbler + ${{ vars.DOCKERHUB_SLUG }} + ${{ vars.GHCR_SLUG }} # generate Docker tags based on the following events/attributes # https://github.com/docker/metadata-action/issues/247#issuecomment-1511259674 for NOT is default branch, eventually tags: | type=edge - # maybe re-enable branch-named tags in the futures - #type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }} + # push with branch name as tag if not master/main + type=ref,event=branch,enable=${{ !endsWith(github.ref, 'master') }} # tag non-prelease as latest -- has a higher priority than regular tag so it shows first in registries type=match,pattern=\d.\d.\d$,priority=901 @@ -81,28 +198,31 @@ jobs: type=semver,pattern={{version}} # flavor: | # latest=false + labels: | + org.opencontainers.image.title=Multi-Scrobbler + org.opencontainers.image.description=Scrobble from many sources to many clients + org.opencontainers.image.vendor=FoxxMD + + - name: Create manifest list and push dockerhub + if: ${{ vars.DOCKERHUB_SLUG != '' }} + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ vars.DOCKERHUB_SLUG }}@sha256:%s ' *) + + - name: Create manifest list and push gchr + if: ${{ vars.GHCR_SLUG != '' }} + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ vars.GHCR_SLUG }}@sha256:%s ' *) + + - name: Inspect image dockerhub + if: ${{ vars.DOCKERHUB_SLUG != '' }} + run: | + docker buildx imagetools inspect ${{ vars.DOCKERHUB_SLUG }}:${{ steps.meta.outputs.version }} - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Build and push Docker image - env: - # use release instead of tags once version is correctly parsed - #APP_VERSION: ${{ github.event.release.tag_name }} - - # https://github.com/actions/runner/issues/409#issuecomment-752775072 - # https://stackoverflow.com/a/69919067/1469797 - APP_VERSION: ${{ contains(github.ref, 'refs/tags/') && github.ref_name || format('{0}-{1}', env.COMMIT_BRANCH, env.COMMIT_SHORT_SHA ) }} - uses: docker/build-push-action@v5 - with: - context: . - # https://github.com/docker/build-push-action/issues/1026#issue-2041857786 - build-args: | - APP_BUILD_VERSION=${{env.APP_VERSION}} - push: ${{ !env.ACT }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64,linux/arm64 + - name: Inspect image ghcr + if: ${{ vars.GHCR_SLUG != '' }} + run: | + docker buildx imagetools inspect ${{ vars.GHCR_SLUG }}:${{ steps.meta.outputs.version }} \ No newline at end of file