Skip to content
Draft
57 changes: 57 additions & 0 deletions .github/actions/image-metadata/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# GitHub Action: Image metadata

Author: **Digdir Platform Team**

## Description

This composite action generates Docker image metadata for workflows that need a
consistent image name and tag.

It supports

- Custom image tags via `image-tag`
- Package-version tags via `package-version`
- Explicit version strings via `version`
- Snapshot stripping when building from `main` or tag refs
- Auto-generated tags when no explicit tag is provided
- Container registry selection via `container-registry` or `registry-url`
- Automatic `image-name` fallback to the current repository name

## Inputs

| Input | Description | Required | Default |
| :---- | :---------- | :------- | :------ |
| `image-name` | Docker image name without registry. Defaults to repository name if unset. | false | `""` |
| `container-registry` | Container registry host (e.g. `creiddev.azurecr.io`, `ghcr.io`). | false | `""` |
| `registry-url` | Alternate registry URL if `container-registry` is not provided. | false | `""` |
| `image-tag` | Custom image tag. Overrides auto-generation. | false | `""` |
| `package-version` | Use package version as image tag when provided. | false | `""` |
| `version` | Use explicit version string as image tag when provided. | false | `""` |
| `version-pom-path` | Evaluate Maven `pom.xml` to derive the version when no explicit tag is provided. | false | `` |
| `strip-snapshot` | Strip `-SNAPSHOT` from version when building from `main` or tag refs. | false | `false` |
| `auto-generate-tag` | Generate a tag from the date and SHA when no explicit tag is provided. | false | `true` |

## Outputs

| Output | Description |
| :----- | :---------- |
| `image-name` | Fully qualified image name including registry. |
| `image-tag` | Image tag. |

## Example usage

```yaml
steps:
- name: Set image metadata
id: image-metadata
uses: felleslosninger/github-workflows/.github/actions/image-metadata@main
with:
image-name: my-app
container-registry: creiddev.azurecr.io
```

## How it works

The action validates registry and image-name inputs, chooses the best available
tag source, and writes both values to outputs for later build, scan, and
publishing steps.
155 changes: 155 additions & 0 deletions .github/actions/image-metadata/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
name: Image metadata
description: Composite action for generating container image metadata
author: Digdir Platform Team

inputs:
image-name:
description: Container image name without registry. If unset, the repository name is used.
default: ""
required: false
container-registry:
description: Container registry host (e.g. creiddev.azurecr.io or ghcr.io).
default: ""
required: false
registry-url:
description: Alternate registry URL if container-registry is not provided.
default: ""
required: false
image-tag:
description: Custom image tag. If set, this is used instead of auto-generation.
default: ""
required: false
package-version:
description: Package version used as image tag when provided.
default: ""
required: false
version:
description: Version string used as image tag when provided.
default: ""
required: false
version-pom-path:
description: Maven pom.xml path used to derive image tag when version is not provided.
default: ""
required: false
strip-snapshot:
description: Strip '-SNAPSHOT' from version when building from main or tag refs.
default: "false"
required: false
auto-generate-tag:
description: Whether an image tag should be auto-generated when no explicit tag is provided.
default: "true"
required: false

outputs:
image-name:
description: The fully qualified image name
value: ${{ steps.get-name.outputs.image-name }}
image-tag:
description: The image tag
value: ${{ steps.get-tag.outputs.image-tag }}

runs:
using: composite
steps:
- name: Start image metadata summary
shell: bash
run: echo "### Image metadata" >> "$GITHUB_STEP_SUMMARY"

- name: Write inputs to summary
uses: felleslosninger/github-workflows/.github/actions/json-to-summary@main
with:
json-payload: ${{ toJson(inputs) }}

- name: Determine image name
id: get-name
shell: bash
env:
CONTAINER_REGISTRY: ${{ inputs.container-registry }}
REGISTRY_URL: ${{ inputs.registry-url }}
IMAGE_NAME: ${{ inputs.image-name }}
REPOSITORY_NAME: ${{ github.event.repository.name }}
REPOSITORY_OWNER: ${{ github.repository_owner }}
run: |
set -euo pipefail

registry="${CONTAINER_REGISTRY:-$REGISTRY_URL}"

if [ -z "$registry" ]; then
echo "::error:: Missing container-registry or registry-url input."
{
echo "> [!WARNING]"
echo "> **Input validation failed:** You must provide either container-registry or registry-url."
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi

image_name="${IMAGE_NAME:-$REPOSITORY_NAME}"

if [ "$registry" = "ghcr.io" ]; then
image_name="$registry/$REPOSITORY_OWNER/$image_name"
else
image_name="$registry/$image_name"
fi

echo "image-name=$image_name" >> "$GITHUB_OUTPUT"
echo "- Image name: \`$image_name\`" >> "$GITHUB_STEP_SUMMARY"

- name: Determine image tag
id: get-tag
shell: bash
env:
IMAGE_TAG: ${{ inputs.image-tag }}
PACKAGE_VERSION: ${{ inputs.package-version }}
VERSION: ${{ inputs.version }}
VERSION_POM_PATH: ${{ inputs.version-pom-path }}
STRIP_SNAPSHOT: ${{ inputs.strip-snapshot }}
AUTO_GENERATE_TAG: ${{ inputs.auto-generate-tag }}
run: |
set -euo pipefail

auto_tag="$(TZ=UTC date +'%Y-%m-%d-%H%M')-${GITHUB_SHA::8}"
tag_source=""

if [ -n "$IMAGE_TAG" ]; then
image_tag="$IMAGE_TAG"
tag_source="custom image tag"

elif [ -n "$PACKAGE_VERSION" ]; then
image_tag="$PACKAGE_VERSION"
tag_source="package version"

elif [ -n "$VERSION" ]; then
image_tag="$VERSION"
tag_source="provided version"

elif [ -n "$VERSION_POM_PATH" ]; then
set +e
pom_version=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout -f "$VERSION_POM_PATH" 2>/dev/null)
set -e

if [ -n "$pom_version" ] && [ "$pom_version" != "null" ]; then
image_tag="$pom_version"
if [[ "$STRIP_SNAPSHOT" == "true" ]] && ([[ "$GITHUB_REF" == "refs/heads/main" ]] || [[ "$GITHUB_REF" == refs/tags/* ]]); then
image_tag="${image_tag/-SNAPSHOT/}"
fi
tag_source="Maven version"
else
image_tag="$auto_tag"
tag_source="fallback auto-generated tag"
fi

elif [[ "$AUTO_GENERATE_TAG" == "true" ]]; then
image_tag="$auto_tag"
tag_source="auto-generated tag"

else
echo "::error:: No image tag available. Provide image-tag, package-version, version, or allow auto-generate-tag."
{
echo "> [!WARNING]"
echo "> **Input validation failed:** No image tag was provided and auto-generate-tag is disabled."
} >> "$GITHUB_STEP_SUMMARY"
exit 1
fi

echo "image-tag=$image_tag" >> "$GITHUB_OUTPUT"
echo "- Image tag: \`$image_tag\` (Source: $tag_source)" >> "$GITHUB_STEP_SUMMARY"
6 changes: 3 additions & 3 deletions .github/workflows/ci-build-publish-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ jobs:
run-spring-boot-build:
needs: input-checks
if: inputs.application-type == 'spring-boot'
uses: felleslosninger/github-workflows/.github/workflows/ci-spring-boot-build-publish-image.yml@main
uses: felleslosninger/github-workflows/.github/workflows/ci-spring-boot-build-publish-image.yml@PF-2305-image-metadata-composite-action
with:
image-name: ${{ inputs.image-name }}
image-pack: ${{ inputs.image-pack }}
Expand Down Expand Up @@ -191,7 +191,7 @@ jobs:
run-quarkus-build:
needs: input-checks
if: inputs.application-type == 'quarkus'
uses: felleslosninger/github-workflows/.github/workflows/ci-quarkus-build-publish-image.yml@main
uses: felleslosninger/github-workflows/.github/workflows/ci-quarkus-build-publish-image.yml@PF-2305-image-metadata-composite-action
with:
image-name: ${{ inputs.image-name }}
image-pack: ${{ inputs.image-pack }}
Expand All @@ -216,7 +216,7 @@ jobs:
run-docker-build:
needs: input-checks
if: inputs.application-type == 'docker'
uses: felleslosninger/github-workflows/.github/workflows/ci-docker-build-publish-image.yml@main
uses: felleslosninger/github-workflows/.github/workflows/ci-docker-build-publish-image.yml@PF-2305-image-metadata-composite-action
with:
image-name: ${{ inputs.image-name }}
image-signing: ${{ inputs.image-signing }}
Expand Down
39 changes: 16 additions & 23 deletions .github/workflows/ci-docker-build-publish-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,43 +92,36 @@ jobs:
runs-on: ubuntu-latest

outputs:
image-tag: ${{ steps.set-image-tag.outputs.image-tag }}
image-tag: ${{ steps.image-metadata.outputs.image-tag }}
image-digest: ${{ steps.set-image-digest.outputs.image-digest }}

permissions:
id-token: write
contents: write

steps:
- name: Set image tag
id: set-image-tag
run: |
image_tag=$(date +'%Y-%m-%d-%H%M')-${GITHUB_SHA::8}
echo "image-tag=$image_tag" >> "$GITHUB_OUTPUT"
echo "- Image tag: $image_tag" >> "$GITHUB_STEP_SUMMARY"

- name: Set image name
id: set-image-name
run: |
image_name=${{ inputs.container-registry }}/${{ inputs.image-name || github.event.repository.name }}
echo "image-name=$image_name" >> "$GITHUB_OUTPUT"
echo "- Image name: $image_name" >> "$GITHUB_STEP_SUMMARY"
- name: Set image metadata
id: image-metadata
uses: felleslosninger/github-workflows/.github/actions/image-metadata@PF-2305-image-metadata-composite-action
with:
image-name: ${{ inputs.image-name }}
container-registry: ${{ inputs.container-registry }}

- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # pin@v5.0.0

- name: Build image
run: |
if [ "${{ inputs.add-git-package-token }}" = "true" ]; then
docker build --tag ${{ steps.set-image-name.outputs.image-name }}:${{ steps.set-image-tag.outputs.image-tag }} --file docker/Dockerfile --build-arg GIT_PACKAGE_TOKEN=${{ secrets.GITHUB_TOKEN }} .
docker build --tag ${{ steps.image-metadata.outputs.image-name }}:${{ steps.image-metadata.outputs.image-tag }} --file docker/Dockerfile --build-arg GIT_PACKAGE_TOKEN=${{ secrets.GITHUB_TOKEN }} .
else
docker build --tag ${{ steps.set-image-name.outputs.image-name }}:${{ steps.set-image-tag.outputs.image-tag }} --file ${{ inputs.application-path }}/Dockerfile .
docker build --tag ${{ steps.image-metadata.outputs.image-name }}:${{ steps.image-metadata.outputs.image-tag }} --file ${{ inputs.application-path }}/Dockerfile .
fi

- name: Run Trivy vulnerability scanner
uses: felleslosninger/github-workflows/.github/actions/trivy-scan@main
with:
image-ref: ${{ steps.set-image-name.outputs.image-name }}:${{ steps.set-image-tag.outputs.image-tag }}
image-ref: ${{ steps.image-metadata.outputs.image-name }}:${{ steps.image-metadata.outputs.image-tag }}
application-path: ${{ inputs.application-path }}
library-disable-scan: ${{ inputs.trivy-library-disable-scan }}
library-ignore-unfixed: ${{ inputs.trivy-library-ignore-unfixed }}
Expand All @@ -152,13 +145,13 @@ jobs:
ACR_NAME: ${{ inputs.container-registry }}

- name: "Push image"
run: docker push ${{ steps.set-image-name.outputs.image-name }}:${{ steps.set-image-tag.outputs.image-tag }}
run: docker push ${{ steps.image-metadata.outputs.image-name }}:${{ steps.image-metadata.outputs.image-tag }}

- name: Set image digest
id: set-image-digest
run: |
image_digest=$(docker inspect \
--format='{{.RepoDigests}}' ${{ steps.set-image-name.outputs.image-name }}:${{ steps.set-image-tag.outputs.image-tag }} \
--format='{{.RepoDigests}}' ${{ steps.image-metadata.outputs.image-name }}:${{ steps.image-metadata.outputs.image-tag }} \
| cut -d '@' -f 2 \
| cut -d ']' -f 1)
echo "image-digest=$image_digest" >> "$GITHUB_OUTPUT"
Expand All @@ -168,17 +161,17 @@ jobs:
uses: felleslosninger/github-workflows/.github/actions/trivy-sbom@main
with:
scan-type: image
artifact-id: ${{ steps.set-image-name.outputs.image-name }}
image-ref: "${{ steps.set-image-name.outputs.image-name }}@${{ steps.set-image-digest.outputs.image-digest }}"
version: ${{ steps.set-image-tag.outputs.image-tag }}
artifact-id: ${{ steps.image-metadata.outputs.image-name }}
image-ref: "${{ steps.image-metadata.outputs.image-name }}@${{ steps.set-image-digest.outputs.image-digest }}"
version: ${{ steps.image-metadata.outputs.image-tag }}
# This is already done in Trivy vuln scan step
skip-setup: true

- name: Image signing
if: ${{ inputs.image-signing == true }}
uses: felleslosninger/github-workflows/.github/actions/image-signing@main
with:
image: ${{ steps.set-image-name.outputs.image-name }}:${{ steps.set-image-tag.outputs.image-tag }}
image: ${{ steps.image-metadata.outputs.image-name }}:${{ steps.image-metadata.outputs.image-tag }}

notify-on-errors:
runs-on: ubuntu-latest
Expand Down
Loading
Loading