From c1ef3264b30a2d64d3c15334b8974beff804a773 Mon Sep 17 00:00:00 2001 From: nikan Negaresh Date: Tue, 11 Nov 2025 10:49:20 +0000 Subject: [PATCH 1/6] Add workflows for Docker image tagging, release processes, and GHCR deployment --- .github/actions/docker-tags.sh | 81 ++++++++++++++++++++ .github/workflows/docker-ghcr.yml | 87 ++++++++++++++++++++++ .github/workflows/release.yaml | 119 ++++++++++++++++++++++++++++++ 3 files changed, 287 insertions(+) create mode 100755 .github/actions/docker-tags.sh create mode 100644 .github/workflows/docker-ghcr.yml create mode 100644 .github/workflows/release.yaml diff --git a/.github/actions/docker-tags.sh b/.github/actions/docker-tags.sh new file mode 100755 index 0000000..dd5573f --- /dev/null +++ b/.github/actions/docker-tags.sh @@ -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 diff --git a/.github/workflows/docker-ghcr.yml b/.github/workflows/docker-ghcr.yml new file mode 100644 index 0000000..e594e26 --- /dev/null +++ b/.github/workflows/docker-ghcr.yml @@ -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. +# 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 + + diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..eae1262 --- /dev/null +++ b/.github/workflows/release.yaml @@ -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 deploy -DskipTests --settings .m2/settings.xml + + 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 From 8a354b89ed18e344b4c36f04c9b707c2928be351 Mon Sep 17 00:00:00 2001 From: nikan-negaresh-informed <84400913+nikan-negaresh-informed@users.noreply.github.com> Date: Tue, 11 Nov 2025 10:50:43 +0000 Subject: [PATCH 2/6] feat(OSPO): synchronise OSPO workflows --- .github/workflows/publish-github-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-github-release.yml b/.github/workflows/publish-github-release.yml index 8e35068..9449b6c 100644 --- a/.github/workflows/publish-github-release.yml +++ b/.github/workflows/publish-github-release.yml @@ -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 From 6e2c3cc615b93de8902ac74dda2d5efceb5f0b63 Mon Sep 17 00:00:00 2001 From: nikan Negaresh Date: Tue, 11 Nov 2025 13:50:12 +0000 Subject: [PATCH 3/6] Remove unnecessary Maven settings reference in workflows --- .github/workflows/docker-ghcr.yml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-ghcr.yml b/.github/workflows/docker-ghcr.yml index e594e26..7ecccc2 100644 --- a/.github/workflows/docker-ghcr.yml +++ b/.github/workflows/docker-ghcr.yml @@ -10,7 +10,7 @@ # # 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. +# 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. diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index eae1262..6cbf7cd 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -92,7 +92,7 @@ jobs: - name: Build packages run: ./mvnw $MAVEN_CLI_OPTS package -DskipTests - name: Publish package - run: ./mvnw $MAVEN_CLI_OPTS deploy -DskipTests --settings .m2/settings.xml + run: ./mvnw $MAVEN_CLI_OPTS deploy -DskipTests release-ghcr: name: "Build and release docker images to GHCR with tags '${{ inputs.image_tag }}, latest'" From ae4aad6a69d5c4afaba20ef9183e7aa58467a6f5 Mon Sep 17 00:00:00 2001 From: nikan Negaresh Date: Tue, 11 Nov 2025 14:00:55 +0000 Subject: [PATCH 4/6] Remove unnecessary Maven settings reference in workflows --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 6cbf7cd..fc39819 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -92,7 +92,7 @@ jobs: - name: Build packages run: ./mvnw $MAVEN_CLI_OPTS package -DskipTests - name: Publish package - run: ./mvnw $MAVEN_CLI_OPTS deploy -DskipTests + run: ./mvnw $MAVEN_CLI_OPTS package -DskipTests release-ghcr: name: "Build and release docker images to GHCR with tags '${{ inputs.image_tag }}, latest'" From 890b0dd2678542de19a6f9deffdceaf8de62cc99 Mon Sep 17 00:00:00 2001 From: nikan Negaresh Date: Tue, 11 Nov 2025 14:07:56 +0000 Subject: [PATCH 5/6] Name runtime stage in Dockerfile to align with CI workflows --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index dacf6f9..89f5f62 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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 From 019ea08b89f99ed70ff007eb84281da94933c50e Mon Sep 17 00:00:00 2001 From: nikan Negaresh Date: Tue, 11 Nov 2025 14:14:37 +0000 Subject: [PATCH 6/6] Name runtime stage in Dockerfile to align with CI workflows --- docker/Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 89f5f62..522ea9b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -29,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