Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ GitHub Actions for working with Docker
- [Docker lint](../README-lint.md)
- [Docker build](../README-build.md)
- [Docker push](../README-push.md)
- [Docker resolve-image](actions/resolve-image/README.md)

## Golden Path

Expand Down
50 changes: 50 additions & 0 deletions .github/actions/resolve-image/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# `gha-docker/resolve-image`

Resolves the Docker image that was built during a PR by looking up git tags created by the [push](../../../README-push.md) workflow. Intended for use in CD workflows that run after a PR is merged to `main`.

> [!TIP]
> This is useful for only building the docker image once in the CI/CD pipelines.

When the push workflow builds and pushes a Docker image, it also creates a git tag with the image tag value (e.g. `my-branch.20260409-SHA1234567`). This action finds the merged PR for the current commit, reconstructs the sanitised branch name, and looks up the most recent matching git tag to resolve the exact image that was built.

> [!NOTE]
> If no matching tag is found (e.g. docs-only changes), the action succeeds with empty `image` and `image_tag` outputs. Downstream deploy steps can use this to skip deployment.

## Usage

Add the action as a step in your CD workflow. Requires checkout with full history and tags.

```yml
jobs:
deploy:
if: github.event_name == 'push'
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true

- uses: entur/gha-docker/.github/actions/resolve-image@v1
id: resolve-image

- if: steps.resolve-image.outputs.image != ''
run: echo "Deploying ${{ steps.resolve-image.outputs.image }}"
```

## Inputs

| INPUT | TYPE | REQUIRED | DEFAULT | DESCRIPTION |
| ------------ | ------ | -------- | ------------- | ------------------------------------------------------- |
| `image_name` | string | false | `"repo_name"` | Image name to resolve. Defaults to the repository name. |

## Outputs

| OUTPUT | DESCRIPTION |
| ----------- | -------------------------------------------------------------------------------------------- |
| `image` | Resolved image name and tag (`image_name:image_tag`), empty if no image was built for the PR |
| `image_tag` | Resolved image tag, empty if no image was built for the PR |
| `pr_number` | The PR number that was merged at the current commit |
68 changes: 68 additions & 0 deletions .github/actions/resolve-image/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Resolve Image
description: >
Resolve the Docker image name and tag for a merged PR by looking up
the git tag created by the Docker push workflow. Requires the repository
to be checked out with fetch-depth: 0 and fetch-tags: true.

inputs:
image_name:
description: "Image name to resolve. Defaults to the repository name."
default: "repo_name"

outputs:
image:
description: "Resolved image name and tag (image_name:image_tag), empty if no image was built for the PR"
value: ${{ steps.resolve.outputs.image }}
image_tag:
description: "Resolved image tag, empty if no image was built for the PR"
value: ${{ steps.resolve.outputs.image_tag }}
pr_number:
description: "The PR number that was merged at the current commit"
value: ${{ steps.find-pr.outputs.pr_number }}

runs:
using: composite
steps:
- name: Find merged PR
id: find-pr
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER=$(gh pr list --search "$GITHUB_SHA" --state merged --json number --jq '.[0].number')
if [ -z "$PR_NUMBER" ]; then
echo "::error::Could not find merged PR for commit $GITHUB_SHA"
exit 1
fi
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"

BRANCH=$(gh pr view "$PR_NUMBER" --json headRefName --jq '.headRefName')
echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT"

- name: Sanitize branch name
id: sanitize
uses: entur/gha-docker/.github/actions/sanitize-ref@v1
with:
ref: ${{ steps.find-pr.outputs.branch }}

- name: Resolve image from git tag
id: resolve
shell: bash
env:
IMAGE_NAME: ${{ inputs.image_name }}
BRANCH: ${{ steps.sanitize.outputs.ref }}
PR_NUMBER: ${{ steps.find-pr.outputs.pr_number }}
run: |
if [ "$IMAGE_NAME" = "repo_name" ]; then
IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
fi

IMAGE_TAG=$(git tag -l "${BRANCH}.*" --sort=-creatordate | head -1)
if [ -z "$IMAGE_TAG" ]; then
echo "::warning::No git tag matching '${BRANCH}.*' (PR #${PR_NUMBER}) — no image was built for this PR, skipping deploy"
exit 0
fi

echo "Resolved image: ${IMAGE_NAME}:${IMAGE_TAG} (from PR #${PR_NUMBER})"
echo "image=${IMAGE_NAME}:${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
echo "image_tag=${IMAGE_TAG}" >> "$GITHUB_OUTPUT"
22 changes: 22 additions & 0 deletions .github/actions/sanitize-ref/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# `gha-docker/sanitize-ref`

Sanitizes a git ref name for use in Docker image tags. Used internally by the [push](../../../README-push.md) workflow and [resolve-image](../resolve-image/README.md) action to ensure consistent branch name handling.

## Sanitization rules

1. Truncate to 43 characters (reserves space for `.YYYYMMDD-SHA1234567` suffix)
2. Remove trailing `-`
3. Lowercase and remove Norwegian characters (`ÆØÅæøå`)
4. Replace `/`, `.`, `!` with `-`

## Inputs

| INPUT | TYPE | REQUIRED | DESCRIPTION |
| ----- | ------ | -------- | ------------------------ |
| `ref` | string | true | Git ref name to sanitize |

## Outputs

| OUTPUT | DESCRIPTION |
| ------ | ------------------ |
| `ref` | Sanitized ref name |
33 changes: 33 additions & 0 deletions .github/actions/sanitize-ref/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Sanitize Ref
description: >
Sanitize a git ref name for use in Docker image tags.
Truncates to 43 chars, lowercases, removes Norwegian characters,
and replaces /, ., ! with hyphens.

inputs:
ref:
description: "Git ref name to sanitize"
required: true

outputs:
ref:
description: "Sanitized ref name"
value: ${{ steps.sanitize.outputs.ref }}

runs:
using: composite
steps:
- name: Sanitize ref
id: sanitize
shell: bash
env:
RAW_REF: ${{ inputs.ref }}
run: |
REF="${RAW_REF}"
REF=${REF:0:43} # truncate to max_len - len(.YYYYMMDD-SHA1234567)
REF=$(echo "$REF" | sed 's/[-]$//') # remove trailing -
REF=$(echo "$REF" | tr '[:upper:]' '[:lower:]' | tr -d '[ÆØÅæøå]') # to ASCII lower case
REF=${REF//\//-} # replace / with -
REF=${REF//./-} # replace . with -
REF=${REF//!/-} # replace ! with -
echo "ref=${REF}" >> "$GITHUB_OUTPUT"
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ updates:
- ".github/workflows/build.yml"
- ".github/workflows/lint.yml"
- ".github/workflows/push.yml"
- ".github/actions"
groups:
minor-and-patch:
applies-to: version-updates
Expand All @@ -25,6 +26,7 @@ updates:
- "/.github/workflows/build.yml"
- "/.github/workflows/lint.yml"
- "/.github/workflows/push.yml"
- "/.github/actions/resolve-image"
groups:
minor-and-patch:
applies-to: version-updates
Expand Down
25 changes: 15 additions & 10 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,24 @@ jobs:
shell: bash
run: |
echo "GHA_DOCKER_PUSH_DATE=$(date +'%Y%m%d')" >> $GITHUB_ENV
# Convert ref name to a valid git & docker tag name
if [[ "${GITHUB_EVENT_NAME}" = "pull_request" ]]; then
BRANCH_NAME=${GITHUB_HEAD_REF}
echo "ref=${GITHUB_HEAD_REF}" >> "$GITHUB_OUTPUT"
else
BRANCH_NAME=${GITHUB_REF_NAME}
echo "ref=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
fi
BRANCH_NAME=${BRANCH_NAME:0:43} # truncate to max_len - len(.YYYYMMDD-SHA1234567)
BRANCH_NAME=$(echo "$BRANCH_NAME" | sed s'/[-]$//') # remove trailing -
BRANCH_NAME=$(echo "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | tr -d '[ÆØÅæøå]') # to ASCII lower case
BRANCH_NAME=${BRANCH_NAME//\//-} # replace / with -
BRANCH_NAME=${BRANCH_NAME//./-} # replace . with -
BRANCH_NAME=${BRANCH_NAME//!/-} # replace ! with -
echo "GHA_DOCKER_PUSH_BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV

- name: Sanitize branch name
id: sanitize-branch
uses: entur/gha-docker/.github/actions/sanitize-ref@v1
with:
ref: ${{ steps.set-env.outputs.ref }}

- name: Set sanitized branch name
id: set-branch
shell: bash
env:
SANITIZED_REF: ${{ steps.sanitize-branch.outputs.ref }}
run: echo "GHA_DOCKER_PUSH_BRANCH_NAME=${SANITIZED_REF}" >> $GITHUB_ENV

- if: env.GHA_DOCKER_PUSH_IMAGE_NAME == 'repo_name'
name: Set image artifact name
Expand Down