0

I've been digging around for a while and can't quite figure out how to accomplish this.

What I have now: End-to-end, I have two workflows. One monitors for new releases of Pi-hole and the other builds a docker image when new commits happen.

Goal: Using GitHub Workflows, only when committing to the master branch, I want to build a single Docker image that is tagged with master, latest, and every tag on the commit (usually a version tag), if they exist. This way, whether I do a commit to master or my Pi-hole monitor workflow does a commit, the appropriate docker image tags will be published from only a single build run.

Use case 1: I commit an update to master with a change to Dockerfile. The result should be that a new docker image is published with both master and latest tags. A single docker image should be used so the SHAs for the tags match. The current Pi-hole version-tagged image should remain untouched.

This currently works! I get one docker build run which publishes the image under master and latest.

Use case 2: My Pi-hole monitor workflow detects a new Pi-hole release. The result should be that a new docker image is published with three tags: master, latest, and the Pi-hole release tag (e.g. pihole-v5.10). A single docker image should be used so the SHAs for the tags match.

This does not currently work. This triggers my workflow twice. So, two docker builds run. latest is updated twice (whichever finishes first). The master and version tags (pihole-v*.*) have different SHAs.

Workflow #1: Pi-hole monitor

name: Pi-hole Monitor
on:
  schedule:
    - cron:  '0 8 * * *'
  workflow_dispatch:
jobs:
  check-pihole-version:
    runs-on: ubuntu-latest
    steps:
      # https://github.com/actions/checkout
      - name: Checkout repository
        uses: actions/checkout@v2
        # https://stackoverflow.com/questions/67550727/push-event-doesnt-trigger-workflow-on-push-paths-github-actions
        with:
          token: ${{ secrets.BUILD_AUTOMATION_TOKEN }}

      - name: Fetch latest release version of Pi-hole
        id: pi-hole_ver
        run: |
          curl -sL https://api.github.com/repos/pi-hole/pi-hole/releases/latest | \
          jq -r ".tag_name" > pihole-latest.txt
          echo ::set-output name=pihole-latest::$(cat pihole-latest.txt)

      - name: Check for modified files
        id: git-check
        run: echo ::set-output name=modified::$([ -z "`git status --porcelain`" ] && echo "false" || echo "true")

      - name: Commit latest release version
        if: steps.git-check.outputs.modified == 'true'
        run: |
          git config --global user.name 'Hossy'
          git config --global user.email 'Hossy@users.noreply.github.com'
          git commit -am "New Pi-hole release ${{ steps.pi-hole_ver.outputs.pihole-latest }}"
          git tag -am "Pi-hole ${{ steps.pi-hole_ver.outputs.pihole-latest }}" pihole-${{ steps.pi-hole_ver.outputs.pihole-latest }}
          git push origin pihole-${{ steps.pi-hole_ver.outputs.pihole-latest }}
          git push

Workflow #2: Docker builder

name: Publish Docker Image

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
  schedule:
    - cron: '37 8 1,15 * *'
  push:
    branches: [ master ]
    tags: [ 'pihole-**' ]
    paths-ignore: [ '.github/**' ]
  pull_request:
    branches: [ master ]
  workflow_dispatch:

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      packages: write

    steps:
      # Login against a Docker registry except on PR
      # https://github.com/docker/login-action
      - name: Log into registry ${{ env.REGISTRY }}
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v2.0.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Extract metadata (tags, labels) for Docker
      # https://github.com/docker/metadata-action
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v4.0.1
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          # set latest tag for master branch
          tags: |
            type=schedule
            type=ref,event=branch
            type=ref,event=tag
            type=ref,event=pr
            type=raw,value=latest,enable={{is_default_branch}}

      # Build and push Docker image with Buildx (don't push on PR)
      # https://github.com/docker/build-push-action
      - name: Build and push Docker image
        uses: docker/build-push-action@v3.0.0
        with:
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}

Other things that might help that I've looked at and are still tinkering with:

Hossy
  • 139
  • 1
  • 2
  • 11

1 Answers1

0

After posting this question, I continued to dig and I found a solution. I'm not going to claim it's a pretty solution, but it accomplishes the goal.

I'd also like to add that, at the very most, the GitHub API is severely lacking. But I digress... to the good stuff!

What I ended up doing is:

  1. Using a GitHub API call to GET /repos/:owner/:repo/tags, retrieve every tag that exists on the repository. This is the only API call I found that would return the commit SHA of the tag. All the others kept returning the object SHA of the tag itself (useless to me).

Example output (JSON array):

[
    {
        "name": "pihole-v5.10",
        "zipball_url": "https://api.github.com/repos/Hossy/pihole/zipball/refs/tags/pihole-v5.10",
        "tarball_url": "https://api.github.com/repos/Hossy/pihole/tarball/refs/tags/pihole-v5.10",
        "commit": {
            "sha": "e696d33d0dca3df5157cbc8558cc303cec027851",
            "url": "https://api.github.com/repos/Hossy/pihole/commits/e696d33d0dca3df5157cbc8558cc303cec027851"
        },
        "node_id": "MDM6UmVmMjc4NDM1ODg3OnJlZnMvdGFncy9waWhvbGUtdjUuMTA="
    }
]
  1. Parse the JSON output with jq and filter to the tag that has a commit SHA that matches the workflow's commit SHA.
  2. Do some string manipulation to merge the tags from docker/metadata-action and GitHub API (jq) to build a superset of tags for docker/build-push-action.

My new docker builder workflow:

name: Publish Docker Image

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

on:
  schedule:
    - cron: '37 8 1,15 * *'
  push:
    branches: [ master ]
    #tags: [ 'pihole-**' ]
    paths-ignore: [ '.github/**' ]
  pull_request:
    branches: [ master ]
  # workflow_dispatch:

env:
  # Use docker.io for Docker Hub if empty
  REGISTRY: ghcr.io
  # github.repository as <account>/<repo>
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:

    runs-on: ubuntu-latest
    permissions:
      # contents: read
      packages: write

    steps:
      # Login against a Docker registry except on PR
      # https://github.com/docker/login-action
      - name: Log into registry ${{ env.REGISTRY }}
        if: github.event_name != 'pull_request'
        uses: docker/login-action@v2.0.0
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Extract metadata (tags, labels) for Docker
      # https://github.com/docker/metadata-action
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v4.0.1
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          # set latest tag for master branch
          tags: |
            type=schedule
            type=ref,event=branch
            type=ref,event=tag
            type=ref,event=pr
            type=raw,value=latest,enable={{is_default_branch}}

      # Retrieve all repository tags from the GitHub API using undocumented API call to get all tags
      # https://github.com/actions/github-script
      - name: Get all tags
        id: all-tags
        uses: actions/github-script@v6
        with:
          script: |
            const path = "/repos/" + "${{ github.repository }}" + "/tags"
            const parameters = "{'" + "${{ github.repository_owner }}" + "', '" + "${{ github.repository }}" + "'}"
            return github.rest.git.getTag(path,parameters)
      
      # Prepare JSON output for Unix command line
      # https://github.com/mad9000/actions-find-and-replace-string
      - name: Format jq result
        id: formatted-jq
        uses: mad9000/actions-find-and-replace-string@2
        with:
          source: ${{ steps.all-tags.outputs.result }}
          find: "'"
          replace: "\\\'"

      # Parse Github API output and search for tags only matching the current commit SHA 
      - name: Search all tags for commit
        id: tag-results
        run: echo ::set-output name=tags::"$( echo '${{ steps.formatted-jq.outputs.value }}' | jq -r ".data | .[] | select( .commit.sha == \"${{ github.sha }}\" ) | .name" )"
      
      # Merge the tag lists from docker/metadata-action and GitHub API
      - name: Build tag list
        id: tag-list
        run: |
          echo ::set-output name=tags::"$(
            echo -n "${{ steps.meta.outputs.tags }}" | tr '\n' ','
            for r in `echo "${{ steps.tag-results.outputs.tags }}" | tr '\n' ' '`; do echo -n ,${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$r | tr '[:upper:]' '[:lower:]'; done
          )"

      # Build and push Docker image with Buildx (don't push on PR)
      # https://github.com/docker/build-push-action
      - name: Build and push Docker image
        uses: docker/build-push-action@v3.0.0
        with:
          # context: .
          push: ${{ github.event_name != 'pull_request' }}
          tags: ${{ steps.tag-list.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
Hossy
  • 139
  • 1
  • 2
  • 11