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
81 changes: 81 additions & 0 deletions .github/actions/docker-tags.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env bash

#
# SPDX-License-Identifier: Apache-2.0
# © Crown Copyright 2025. This work has been developed by the National Digital Twin Programme and is legally
# attributed to the Department for Business and Trade (UK) as the governing entity.
#

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -e

function usage() {
echo "Usage: $0 IMAGE TAGS"
echo " IMAGE: The image to tag"
echo " TAGS: A comma-separated list of tags to apply"
echo "Optional env vars:"
echo " STAGED_TAG: The tag to use as the source image (default: staged)"
}

if [ "$#" -ne 2 ]; then
usage
exit 1
fi

IMAGE="$1"
JOINED_TAGS="$2"
STAGED_TAG="${STAGED_TAG:-staged}"

if [ -z "$IMAGE" ]; then
echo "Error: IMAGE is required"
usage
exit 1
fi

if [[ ! "$IMAGE" =~ ^[a-zA-Z0-9_./-]+$ ]]; then
echo "Error: Invalid image name '$IMAGE'"
exit 1
fi

if [ -z "$JOINED_TAGS" ]; then
echo "Error: TAGS is required"
usage
exit 1
fi

if [[ ! "$STAGED_TAG" =~ ^[a-zA-Z0-9_./-]+$ ]]; then
echo "Error: Invalid staged tag name '$STAGED_TAG'"
exit 1
fi

# Check that the image exists
if ! docker image inspect "$IMAGE:$STAGED_TAG" &> /dev/null; then
echo "Error: Image $IMAGE:$STAGED_TAG does not exist"
exit 1
fi

IFS=',' read -r -a TAGS <<< "$JOINED_TAGS"

echo "Tagging $IMAGE with tags:" "${TAGS[@]}"

for TAG in "${TAGS[@]}"; do
# Remove all spaces from the tag
tag="${TAG//[[:space:]]/}"
if [[ ! "$tag" =~ ^[a-zA-Z0-9_./-]+$ ]]; then
echo "Error: Invalid tag name '$tag'"
exit 1
fi
echo "Applying tag: '$tag'"
docker tag "$IMAGE:$STAGED_TAG" "$IMAGE:$tag"
done
87 changes: 87 additions & 0 deletions .github/workflows/docker-ghcr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# SPDX-License-Identifier: Apache-2.0
# © Crown Copyright 2025. This work has been developed by the National Digital Twin Programme
# and is legally attributed to the Department for Business and Trade (UK) as the governing entity.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied./.m2/settings.xml
# See the License for the specific language governing permissions and
# limitations under the License.

name: Build and release docker images to GHCR

on:
workflow_call:
inputs:
image_tag:
required: true
description: "The tag(s) to apply to the docker images, if multiple use a comma separated list. Don't use staged as a tag."
type: string
jar_version:
required: true
description: "The version of the jar to use for the docker image"
type: string
docker_target:
required: true
description: "The target of the multistage docker build to use"
type: string
dry_run:
required: false
description: "Dry Run. Whether to push the images to GHCR or not"
type: boolean

jobs:
release:
name: Build and release docker images
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Validate docker target
run: |
if [[ "${{ inputs.docker_target }}" != "management-node" ]]; then
echo "Docker target is invalid. Use management-node"
exit 1
fi

- name: Checkout repo
uses: actions/checkout@v5

- name: Login to ghcr.io
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Format repo name
run: echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV}

- name: Get server and client jars
uses: actions/download-artifact@v5
with:
pattern: management-node-*.jar
path: target
merge-multiple: true

- name: Build Server Image
run: docker build --no-cache --build-arg JAR_NAME="management-node-${{ inputs.jar_version }}" -t ghcr.io/${REPO}/management-node:staged -f "${{ github.workspace }}/docker/Dockerfile" --target ${{ inputs.docker_target }} .

- name: Tag Server Image with tag(s) ${{ inputs.image_tag }}
run: |
./.github/actions/docker-tags.sh "ghcr.io/${REPO}/management-node" "${{ inputs.image_tag }}"
docker rmi ghcr.io/${REPO}/management-node:staged

- name: Push Server Image
if: ${{ !inputs.dry_run }}
run: docker push --all-tags ghcr.io/${REPO}/management-node


2 changes: 1 addition & 1 deletion .github/workflows/publish-github-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download SBOM Artifact
uses: actions/download-artifact@v5
uses: actions/download-artifact@v6
with:
name: sbom

Expand Down
119 changes: 119 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# SPDX-License-Identifier: Apache-2.0
# © Crown Copyright 2025. This work has been developed by the National Digital Twin Programme
# and is legally attributed to the Department for Business and Trade (UK) as the governing entity.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

name: Release

env:
MAVEN_CLI_OPTS: "--batch-mode --no-transfer-progress"

on:
# Hopefully this will eventually be replaced with the release event instead
workflow_dispatch:
# To eventually be replaced with just using the version from the pom
inputs:
image_tag:
description: Tag for the docker image
required: true
type: string
dry_run:
description: |
Dry run.
Toggle for whether to push images or not.
If toggled then the built images will not be pushed.
required: false
default: false
type: boolean

permissions:
contents: read
packages: write
id-token: write

jobs:
# Make sure that the current code runs
verify:
runs-on: ubuntu-latest
outputs:
project_version: ${{ steps.get-version.outputs.project_version }}
steps:
- uses: actions/checkout@v5
- name: Setup Java/Maven
uses: actions/setup-java@v5
with:
java-version: 21
distribution: "temurin"
cache: maven
server-password: "GH_PACKAGES_PAT"
- name: Get version
id: get-version
run: echo project_version=$(./mvnw $MAVEN_CLI_OPTS help:evaluate -Dexpression=project.version -q -DforceStdout) >> $GITHUB_OUTPUT
- name: Build packages
env:
GH_PACKAGES_PAT: ${{ secrets.GH_PACKAGES_PAT }}
run: ./mvnw $MAVEN_CLI_OPTS package
- uses: actions/upload-artifact@v4
name: Persist server
id: persist-server
with:
name: management-node-${{ steps.get-version.outputs.project_version }}.jar
path: target/management-node-${{ steps.get-version.outputs.project_version }}.jar
retention-days: 1


publish:
name: Publish to github packages
needs: verify
runs-on: ubuntu-latest
env:
GITHUB_ACTOR: ${{ github.actor }}
GH_PACKAGES_PAT: ${{ secrets.GH_PACKAGES_PAT }}
steps:
- uses: actions/checkout@v5
- name: Setup Java/Maven
uses: actions/setup-java@v5
with:
java-version: 21
distribution: "temurin"
cache: maven
server-password: "GH_PACKAGES_PAT"
- name: Build packages
run: ./mvnw $MAVEN_CLI_OPTS package -DskipTests
- name: Publish package
run: ./mvnw $MAVEN_CLI_OPTS package -DskipTests

release-ghcr:
name: "Build and release docker images to GHCR with tags '${{ inputs.image_tag }}, latest'"
needs: verify
uses: ./.github/workflows/docker-ghcr.yml
secrets: inherit
with:
image_tag: "${{ inputs.image_tag }},latest"
jar_version: ${{ needs.verify.outputs.project_version }}
dry_run: ${{ inputs.dry_run }}
docker_target: management-node

cleanup:
name: Artifact cleanup
runs-on: ubuntu-latest
needs:
- release-ghcr
- verify
if: ${{ needs.verify.result == 'success' }}
steps:
- uses: geekyeggo/delete-artifact@v5
name: Delete server artifact
with:
name: management-node-${{ needs.verify.outputs.project_version }}.jar
7 changes: 5 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ COPY src ./src
RUN mvn -B clean package -DskipTests

# Runtime stage
FROM eclipse-temurin:21-jdk-alpine
# Name this stage so CI can target it (matches --target management-node in workflows)
FROM eclipse-temurin:21-jdk-alpine AS management-node

# Create non-root user and group
RUN addgroup -S app && adduser -S -G app -u 10001 app
Expand All @@ -28,7 +29,9 @@ WORKDIR /app
RUN mkdir -p /app/docker /app/logs /app/tmp && chown -R app:app /app

# Copy application jar from build stage
COPY --from=build /build/target/management-node-0.90.0.jar /app/app.jar
# Use the jar name provided by CI via --build-arg JAR_NAME="management-node-${version}"
ARG JAR_NAME
COPY --from=build /build/target/${JAR_NAME}.jar /app/app.jar
RUN chown app:app /app/app.jar

# Use non-root user from here on
Expand Down
Loading