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
82 changes: 76 additions & 6 deletions .github/workflows/build_all_images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,10 @@ name: build_all_images
NO_CACHE:
required: true
type: boolean
permissions:
attestations: write
contents: read
packages: write
id-token: write
permissions: {}
jobs:
discover_folders:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
outputs:
base_node_folders: ${{ steps.find-folders.outputs.base_node }}
node_24_language_folders: ${{ steps.find-folders.outputs.node_24_languages }}
Expand All @@ -39,8 +35,67 @@ jobs:
echo "projects=$project_folders"
} >> "$GITHUB_OUTPUT"

build_tool_images:
# build common tool images with a lower scoped github token
# as it uses a 3rd party docker image with github cli installed to verify attestation of tflint binary
# and we dont want to make a high scoped token available to that image
# token needs attestation read so it can verify attestation of tflint binary
name: Build tool images for on ${{ matrix.arch }}
runs-on: '${{ matrix.runner }}'
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-22.04
- arch: arm64
runner: ubuntu-22.04-arm
permissions:
contents: read
attestations: read
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
fetch-depth: 0
persist-credentials: false
- name: build_grype
run: |
make build-grype
docker save "local_grype:latest" -o grype_image.tar
- name: build_syft
run: |
make build-syft
docker save "local_syft:latest" -o syft_image.tar
- name: build_grant
run: |
make build-grant
docker save "local_grant:latest" -o grant_image.tar

- name: build_tflint
run: |
make build-tflint
docker save "local_tflint:latest" -o tflint_image.tar
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
name: Upload docker images
with:
name: docker_artifact_${{ matrix.arch }}
path: |
grype_image.tar
syft_image.tar
grant_image.tar
tflint_image.tar
package_base_docker_image:
uses: ./.github/workflows/build_multi_arch_image.yml
permissions:
attestations: write
contents: read
packages: write
id-token: write
needs: [build_tool_images]
with:
tag_latest: ${{ inputs.tag_latest }}
docker_tag: ${{ inputs.docker_tag }}
Expand All @@ -51,6 +106,11 @@ jobs:
needs:
- package_base_docker_image
- discover_folders
permissions:
attestations: write
contents: read
packages: write
id-token: write
strategy:
fail-fast: false
matrix:
Expand All @@ -68,6 +128,11 @@ jobs:
- package_base_docker_image
- package_base_node_images
- discover_folders
permissions:
attestations: write
contents: read
packages: write
id-token: write
strategy:
fail-fast: false
matrix:
Expand All @@ -84,6 +149,11 @@ jobs:
needs:
- package_node_24_language_docker_images
- discover_folders
permissions:
attestations: write
contents: read
packages: write
id-token: write
strategy:
fail-fast: false
matrix:
Expand Down
36 changes: 21 additions & 15 deletions .github/workflows/build_multi_arch_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,6 @@ jobs:
- arch: arm64
runner: ubuntu-22.04-arm
steps:
- name: Free Disk Space for Docker
uses: endersonmenezes/free-disk-space@7901478139cff6e9d44df5972fd8ab8fcade4db1
with:
remove_android: true
remove_dotnet: true
remove_haskell: true
remove_tool_cache: true
rm_cmd: rm
remove_packages: >-
azure-cli google-cloud-cli microsoft-edge-stable
google-chrome-stable firefox postgresql* temurin-* *llvm* mysql*
dotnet-sdk-*
remove_packages_one_command: true
- name: Login to github container registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121
with:
Expand All @@ -69,11 +56,30 @@ jobs:
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
with:
node-version: '24.14.0'
- name: docker_artifact download
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
with:
name: docker_artifact_${{ matrix.arch }}
path: images/
- name: extract docker images
run: |
for image in images/*.tar; do
docker load -i "$image"
done
rm -rf images
- name: setup syft and grype
run: |
mkdir -p "$RUNNER_TEMP/bin"
docker build --output="$RUNNER_TEMP/bin" -f "src/base/.devcontainer/Dockerfile.syft" src/base/.devcontainer/
docker build --output="$RUNNER_TEMP/bin" -f "src/base/.devcontainer/Dockerfile.grype" src/base/.devcontainer/
id=$(docker create local_grype:latest)
docker cp "$id":/grype - | tar -xOf - grype > "$RUNNER_TEMP/bin/grype"
chmod +x "$RUNNER_TEMP/bin/grype"
docker rm -v "$id"

mkdir -p "$RUNNER_TEMP/bin"
id=$(docker create local_syft:latest)
docker cp "$id":/syft - | tar -xOf - syft > "$RUNNER_TEMP/bin/syft"
chmod +x "$RUNNER_TEMP/bin/syft"
docker rm -v "$id"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- name: make install
run: |
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ jobs:
contents: read
packages: read
quality_checks:
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@e798d5aee897de6f7dc387dd5623fcd9ba4c8929
needs:
- get_config_values
permissions:
contents: read
packages: read
id-token: write
with:
pinned_image: ${{ needs.get_config_values.outputs.pinned_image }}
secrets:
Expand All @@ -35,6 +39,7 @@ jobs:
build_all_images:
needs:
- tag_release
- get_config_values
uses: ./.github/workflows/build_all_images.yml
permissions:
attestations: write
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,15 @@ jobs:
contents: read
packages: read
quality_checks:
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@e798d5aee897de6f7dc387dd5623fcd9ba4c8929
needs:
- get_config_values
with:
pinned_image: ${{ needs.get_config_values.outputs.pinned_image }}
permissions:
contents: read
packages: read
id-token: write
secrets:
SONAR_TOKEN: '${{ secrets.SONAR_TOKEN }}'
pr_title_format_check:
Expand Down Expand Up @@ -80,6 +84,7 @@ jobs:
needs:
- get_issue_number
- get_commit_id
- get_config_values
uses: ./.github/workflows/build_all_images.yml
permissions:
attestations: write
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ jobs:
contents: read
packages: read
quality_checks:
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@f2d4d6942115472d3f08316cd25f400b02a9dc69
uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks-devcontainer.yml@e798d5aee897de6f7dc387dd5623fcd9ba4c8929
needs:
- get_config_values
permissions:
contents: read
packages: read
id-token: write
with:
pinned_image: ${{ needs.get_config_values.outputs.pinned_image }}
secrets:
Expand All @@ -38,6 +42,7 @@ jobs:
build_all_images:
needs:
- tag_release
- get_config_values
uses: ./.github/workflows/build_all_images.yml
permissions:
attestations: write
Expand Down
38 changes: 31 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ guard-%:

.PHONY: install install-python install-node install-hooks build-base-image build-node-24-image build-node-24-python-3-10-image build-node-24-python-3-12-image build-node-24-python-3-13-image build-node-24-python-3-14-image \
build-eps-storage-terraform-image build-eps-data-extract-image build-fhir-facade-image build-node-24-python-3-14-golang-1-24-image build-node-24-python-3-14-java-24-image \
build-regression-tests-image build-all build-image build-githubactions-image scan-image scan-image-json shell-image lint test lint-githubactions lint-githubaction-scripts clean
build-regression-tests-image build-all build-image build-githubactions-image scan-image scan-image-json shell-image lint test lint-githubactions lint-githubaction-scripts clean \
build-syft build-grype build-grant build-tflint
install: install-python install-node install-hooks

install-python:
poetry install

install-node:
npm install
npm ci --ignore-scripts

install-hooks: install-python
poetry run pre-commit install --install-hooks --overwrite
Expand All @@ -43,7 +44,7 @@ build-node-24-python-3-14-image:
CONTAINER_NAME=node_24_python_3_14 BASE_VERSION_TAG=local-build BASE_FOLDER=languages IMAGE_TAG=local-build $(MAKE) build-image

build-eps-storage-terraform-image:
CONTAINER_NAME=eps_storage_terraform BASE_VERSION_TAG=local-build BASE_FOLDER=projects IMAGE_TAG=local-build $(MAKE) build-image
CONTAINER_NAME=eps-storage-terraform BASE_VERSION_TAG=local-build BASE_FOLDER=projects IMAGE_TAG=local-build $(MAKE) build-image

build-eps-data-extract-image:
CONTAINER_NAME=eps_data_extract BASE_VERSION_TAG=local-build BASE_FOLDER=projects IMAGE_TAG=local-build $(MAKE) build-image
Expand All @@ -65,14 +66,37 @@ build-all: build-base-image build-node-24-image build-node-24-python-3-10-image
build-regression-tests-image

build-syft:
docker build -f src/base/.devcontainer/Dockerfile.syft --tag local_syft:latest src/base/.devcontainer/
@if docker image inspect local_syft:latest >/dev/null 2>&1; then \
echo "Image local_syft:latest already exists. Skipping build."; \
else \
docker build -f src/base/.devcontainer/Dockerfile.syft --tag local_syft:latest src/base/.devcontainer/; \
fi
build-grype:
docker build -f src/base/.devcontainer/Dockerfile.grype --tag local_grype:latest src/base/.devcontainer/
@if docker image inspect local_grype:latest >/dev/null 2>&1; then \
echo "Image local_grype:latest already exists. Skipping build."; \
else \
docker build -f src/base/.devcontainer/Dockerfile.grype --tag local_grype:latest src/base/.devcontainer/; \
fi

build-grant:
docker build -f src/base/.devcontainer/Dockerfile.grant --tag local_grant:latest src/base/.devcontainer/
@if docker image inspect local_grant:latest >/dev/null 2>&1; then \
echo "Image local_grant:latest already exists. Skipping build."; \
else \
docker build -f src/base/.devcontainer/Dockerfile.grant --tag local_grant:latest src/base/.devcontainer/; \
fi

build-tflint:
@if docker image inspect local_tflint:latest >/dev/null 2>&1; then \
echo "Image local_tflint:latest already exists. Skipping build."; \
else \
docker buildx build \
--secret id=GH_TOKEN,env=GITHUB_TOKEN \
-f src/projects/eps-storage-terraform/.devcontainer/Dockerfile.tflint \
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker buildx build does not necessarily load the resulting image into the local Docker engine unless --load (or an appropriate --output) is specified. The subsequent docker image inspect local_tflint:latest / docker save local_tflint:latest flow in CI will fail if the image was built but not loaded. Add --load (or switch to docker build) to ensure local_tflint:latest exists locally.

Suggested change
-f src/projects/eps-storage-terraform/.devcontainer/Dockerfile.tflint \
-f src/projects/eps-storage-terraform/.devcontainer/Dockerfile.tflint \
--load \

Copilot uses AI. Check for mistakes.
--tag local_tflint:latest \
src/projects/eps-storage-terraform/.devcontainer/; \
fi

build-image: build-syft build-grype build-grant guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG
build-image: build-syft build-grype build-grant build-tflint guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build-image now always depends on build-tflint, which introduces an extra build step (and a required GITHUB_TOKEN secret) even when building unrelated devcontainers. This can break local builds and adds avoidable CI time. Consider making build-tflint conditional (e.g., only when CONTAINER_NAME=eps-storage-terraform), or moving the tflint build into the eps-storage-terraform Dockerfile so other images don't pay the cost.

Suggested change
build-image: build-syft build-grype build-grant build-tflint guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG
build-image: build-syft build-grype build-grant guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG
if [ "$${CONTAINER_NAME}" = "eps_storage_terraform" ]; then \
$(MAKE) build-tflint; \
fi; \

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Apr 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build-image now always depends on build-tflint, which requires a GitHub token secret to be present and pulls/builds an extra image even when building unrelated containers (e.g., base, node_24_*). This makes local builds and CI builds for other images fail or do unnecessary work. Consider only building local_tflint when the target container actually needs it (e.g., conditionally in the eps-storage-terraform build, or by making the Dockerfile fall back when local_tflint is absent).

Suggested change
build-image: build-syft build-grype build-grant build-tflint guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG
build-image: build-syft build-grype build-grant guard-CONTAINER_NAME guard-BASE_VERSION_TAG guard-BASE_FOLDER guard-IMAGE_TAG
if [ "$${CONTAINER_NAME}" = "eps_storage_terraform" ]; then \
$(MAKE) build-tflint; \
fi; \

Copilot uses AI. Check for mistakes.
workspace_folder="$${CONTAINER_NAME}"; \
case "$${CONTAINER_NAME}" in \
eps_*) workspace_folder="$$(printf '%s' "$${CONTAINER_NAME}" | tr '_' '-')" ;; \
Expand Down
4 changes: 2 additions & 2 deletions src/base/.devcontainer/.tool-versions
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
shellcheck 0.11.0
direnv 2.37.1
actionlint 1.7.11
actionlint 1.7.12
ruby 3.3.0
yq 4.52.4
yq 4.52.5
2 changes: 2 additions & 0 deletions src/projects/eps-storage-terraform/.devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ARG BASE_VERSION_TAG=latest
ARG BASE_IMAGE=ghcr.io/nhsdigital/eps-devcontainers/node_24_python_3_13:${BASE_VERSION_TAG}

FROM local_tflint:latest AS tflint-build
FROM ${BASE_IMAGE}

ARG SCRIPTS_DIR=/usr/local/share/eps
Expand All @@ -26,6 +27,7 @@ USER root
COPY --chmod=755 scripts ${SCRIPTS_DIR}/${CONTAINER_NAME}
WORKDIR ${SCRIPTS_DIR}/${CONTAINER_NAME}
RUN ./root_install.sh
COPY --from=tflint-build /tflint /usr/local/bin/tflint

USER vscode

Expand Down
13 changes: 13 additions & 0 deletions src/projects/eps-storage-terraform/.devcontainer/Dockerfile.tflint
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM serversideup/github-cli:2.89.0 AS build
ARG TARGETARCH
ARG TFLINT_VERSION="v0.61.0"
COPY --chmod=755 scripts/install_tflint.sh /tmp/install_tflint.sh
RUN --mount=type=secret,id=GH_TOKEN,env=GH_TOKEN \
INSTALL_DIR=/tmp/tflint/ \
ARCH="${TARGETARCH}" \
VERSION="${TFLINT_VERSION}" \
Comment on lines +7 to +8
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The install script reads TARGETARCH and TFLINT_VERSION, but this RUN invocation sets ARCH and VERSION instead. As written, install_tflint.sh will exit because TARGETARCH/TFLINT_VERSION are unset, so the tflint image build will fail. Pass the expected variable names (or update the script to consume ARCH/VERSION consistently).

Suggested change
ARCH="${TARGETARCH}" \
VERSION="${TFLINT_VERSION}" \
TARGETARCH="${TARGETARCH}" \
TFLINT_VERSION="${TFLINT_VERSION}" \

Copilot uses AI. Check for mistakes.
/tmp/install_tflint.sh

FROM scratch
COPY --from=build /tmp/tflint/tflint /tflint
ENTRYPOINT ["/tflint"]
Loading
Loading