From c27ddbb5287b4b9690890cef10cfa5bc38b6ef07 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Wed, 18 Feb 2026 23:57:50 +0000 Subject: [PATCH 01/18] [AEA-0000] Use pre-built devcontainer --- .devcontainer/Dockerfile | 59 +-- .devcontainer/devcontainer.json | 25 +- .github/workflows/pull_request.yml | 34 +- .../workflows/quality-checks-devcontainer.yml | 440 ++++++++++++++++++ .github/workflows/release.yml | 30 +- .../workflows/tag-release-devcontainer.yml | 278 +++++++++++ .gitignore | 3 +- .pre-commit-config.yaml | 2 +- .tool-versions | 5 - .tool-versions.asdf | 2 - .trivyignore.yaml | 5 +- Makefile | 9 +- README.md | 80 ++++ scripts/check_python_licenses.sh | 15 - 14 files changed, 867 insertions(+), 120 deletions(-) create mode 100644 .github/workflows/quality-checks-devcontainer.yml create mode 100644 .github/workflows/tag-release-devcontainer.yml delete mode 100644 .tool-versions delete mode 100644 .tool-versions.asdf delete mode 100755 scripts/check_python_licenses.sh diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index f519b68..3903c81 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,53 +1,14 @@ -FROM mcr.microsoft.com/devcontainers/base:ubuntu - -# provide DOCKER_GID via build args if you need to force group id to match host -ARG DOCKER_GID +ARG IMAGE_NAME=node_24_python_3_14 +ARG IMAGE_VERSION=latest +FROM ghcr.io/nhsdigital/eps-devcontainers/${IMAGE_NAME}:${IMAGE_VERSION} +USER root # specify DOCKER_GID to force container docker group id to match host RUN if [ -n "${DOCKER_GID}" ]; then \ - if ! getent group docker; then \ - groupadd -g ${DOCKER_GID} docker; \ - else \ - groupmod -g ${DOCKER_GID} docker; \ - fi && \ - usermod -aG docker vscode; \ + if ! getent group docker; then \ + groupadd -g ${DOCKER_GID} docker; \ + else \ + groupmod -g ${DOCKER_GID} docker; \ + fi && \ + usermod -aG docker vscode; \ fi - -# Anticipate and resolve potential permission issues with apt -RUN mkdir -p /tmp && chmod 1777 /tmp - -RUN apt-get update \ - && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y dist-upgrade \ - && apt-get -y install --no-install-recommends htop vim curl git build-essential \ - libffi-dev libssl-dev libxml2-dev libxslt1-dev libjpeg8-dev libbz2-dev \ - zlib1g-dev unixodbc unixodbc-dev libsecret-1-0 libsecret-1-dev libsqlite3-dev \ - jq apt-transport-https ca-certificates gnupg-agent \ - software-properties-common bash-completion python3-pip make libbz2-dev \ - libreadline-dev libsqlite3-dev wget llvm libncurses5-dev libncursesw5-dev \ - xz-utils tk-dev liblzma-dev netcat-traditional libyaml-dev - -USER vscode - -# Install ASDF -RUN git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3 && \ - echo '. $HOME/.asdf/asdf.sh' >> ~/.bashrc && \ - echo '. $HOME/.asdf/completions/asdf.bash' >> ~/.bashrc - -ENV PATH="$PATH:/home/vscode/.asdf/bin/:/workspaces/eps-prescription-tracker-ui/node_modules/.bin:/workspaces/eps-common-workflows/.venv/bin" - -# Install ASDF plugins# -RUN asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git && \ - asdf plugin add actionlint && \ - asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git && \ - asdf plugin add poetry https://github.com/asdf-community/asdf-poetry.git && \ - asdf plugin add python - -WORKDIR /workspaces/eps-common-workflows - -ADD .tool-versions /workspaces/eps-common-workflows/.tool-versions -ADD .tool-versions /home/vscode/.tool-versions - -RUN asdf install python && \ - asdf install && \ - asdf reshim nodejs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 9510367..5914baa 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,15 +1,18 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu { - "name": "Ubuntu", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "name": "eps-common-workflows", "build": { "dockerfile": "Dockerfile", "context": "..", "args": { - "DOCKER_GID": "${env:DOCKER_GID:}" - } + "DOCKER_GID": "${env:DOCKER_GID:}", + "IMAGE_NAME": "node_24_python_3_14", + "IMAGE_VERSION": "v1.0.4", + "USER_UID": "${localEnv:USER_ID:}", + "USER_GID": "${localEnv:GROUP_ID:}" + }, + "updateRemoteUserUID": false }, + "postAttachCommand": "git-secrets --register-aws; git-secrets --add-provider -- cat /usr/share/secrets-scanner/nhsd-rules-deny.txt", "mounts": [ "source=${env:HOME}${env:USERPROFILE}/.aws,target=/home/vscode/.aws,type=bind", "source=${env:HOME}${env:USERPROFILE}/.ssh,target=/home/vscode/.ssh,type=bind", @@ -20,15 +23,7 @@ "remoteEnv": { "LOCAL_WORKSPACE_FOLDER": "${localWorkspaceFolder}" }, - "postAttachCommand": "make install && docker build -f /workspaces/eps-common-workflows/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . && pre-commit install --install-hooks -f", - "features": { - "ghcr.io/devcontainers/features/github-cli:1": {}, - "ghcr.io/devcontainers/features/docker-outside-of-docker:1": { - "version": "latest", - "moby": "true", - "installDockerBuildx": "true" - } - }, + "features": {}, "customizations": { "vscode": { "extensions": [ diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f606e1f..ff1a7a6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,36 +16,44 @@ jobs: AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} pr_title_format_check: uses: ./.github/workflows/pr_title_check.yml - get_asdf_version: + get_config_values: runs-on: ubuntu-22.04 outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} + devcontainer_version: ${{ steps.load-config.outputs.DEVCONTAINER_VERSION }} + devcontainer_image: ${{ steps.load-config.outputs.DEVCONTAINER_IMAGE }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - name: Load config value id: load-config run: | TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" + DEVCONTAINER_IMAGE=$(jq -r '.build.args.IMAGE_NAME' .devcontainer/devcontainer.json) + DEVCONTAINER_VERSION=$(jq -r '.build.args.IMAGE_VERSION' .devcontainer/devcontainer.json) + { + echo "TAG_FORMAT=$TAG_FORMAT" + echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE" + echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION" + } >> "$GITHUB_OUTPUT" quality_checks: - uses: ./.github/workflows/quality-checks.yml - needs: [get_asdf_version] + uses: ./.github/workflows/quality-checks-devcontainer.yml + needs: [get_config_values] with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} tag_release: - needs: [quality_checks, get_asdf_version] - uses: ./.github/workflows/tag-release.yml + needs: [quality_checks, get_config_values] + uses: ./.github/workflows/tag-release-devcontainer.yml + permissions: + contents: read + packages: read + attestations: read with: dry_run: true - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: ${{ github.event.pull_request.head.ref }} - tag_format: ${{ needs.get_asdf_version.outputs.tag_format }} + tag_format: ${{ needs.get_config_values.outputs.tag_format }} secrets: inherit diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml new file mode 100644 index 0000000..ee26da6 --- /dev/null +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -0,0 +1,440 @@ +name: Quality Checks + +on: + workflow_call: + secrets: + SONAR_TOKEN: + required: false + inputs: + run_sonar: + type: boolean + description: Toggle to run sonar code analyis on this repository. + default: true + required: false + run_docker_scan: + type: boolean + description: Toggle to run docker vulnerability scan on this repository. + default: false + required: false + docker_images: + type: string + description: comma separated list of docker image references to scan when docker scanning is enabled. + default: "" + required: false + runtime_docker_image: + type: string + required: true + +jobs: + quality_checks: + runs-on: ubuntu-22.04 + container: + image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + steps: + - &init_tool_versions + name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + + - &checkout + name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + ref: ${{ env.BRANCH_NAME }} + fetch-depth: 0 + + - &setup_npmrc + name: Setting up .npmrc + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + echo "//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc + echo "@nhsdigital:registry=https://npm.pkg.github.com" >> ~/.npmrc + + - &cache_npm + name: Cache npm dependencies + uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb + with: + path: ./node_modules + key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} + + - name: make install + run: | + make install + - name: Run secrets scan + run: | + make secret-scan + - name: Run actionlint + run: | + make actionlint + + - name: Check language tools used and setup trivy config + id: check_languages + run: | + if [ -f "pyproject.toml" ] && grep -q '\[tool.poetry\]' "pyproject.toml"; then + echo "****************" + echo "Detected a poetry project" + echo "****************" + echo "uses_poetry=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use poetry" + echo "****************" + echo "uses_poetry=false" >> "$GITHUB_OUTPUT" + fi + if [ -f pom.xml ]; then + echo "****************" + echo "Detected a Java project" + echo "****************" + echo "uses_java=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use Java" + echo "****************" + echo "uses_java=false" >> "$GITHUB_OUTPUT" + fi + if [ -f package-lock.json ]; then + echo "****************" + echo "Detected a Node.js project" + echo "****************" + echo "uses_node=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use Node.js" + echo "****************" + echo "uses_node=false" >> "$GITHUB_OUTPUT" + fi + if [ -f src/go.sum ]; then + echo "****************" + echo "Detected a Go project" + echo "****************" + echo "uses_go=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not use Go" + echo "****************" + echo "uses_go=false" >> "$GITHUB_OUTPUT" + fi + - name: Check licenses + run: | + make trivy-license-check + + - name: Show license scan output + if: always() + run: | + if [ -f license_scan.txt ]; then + cat .trivy_out/license_scan.txt + fi + - name: Run code lint + run: | + make lint + + - name: Run ShellCheck + run: | + make shellcheck + + - name: Run unit tests + run: | + make test + - name: make generate sbom + run: | + make trivy-generate-sbom + - name: Upload sbom + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: sbom.cdx.json + path: .trivy_out/sbom.cdx.json + + - name: Check python vulnerabilities + if: ${{ steps.check_languages.outputs.uses_poetry == 'true' }} + run: | + make trivy-scan-python + + - name: Check node vulnerabilities + if: ${{ steps.check_languages.outputs.uses_node == 'true' }} + run: | + make trivy-scan-node + - name: Check go vulnerabilities + if: ${{ steps.check_languages.outputs.uses_go == 'true' }} + run: | + make trivy-scan-go + - name: Check java vulnerabilities + if: ${{ steps.check_languages.outputs.uses_java == 'true' }} + run: | + make trivy-scan-java + - name: Show vulnerability output + if: always() + run: | + if [ -f .trivy_out/dependency_results_python.txt ]; then + cat .trivy_out/dependency_results_python.txt + fi + if [ -f .trivy_out/dependency_results_node.txt ]; then + cat .trivy_out/dependency_results_node.txt + fi + if [ -f .trivy_out/dependency_results_java.txt ]; then + cat .trivy_out/dependency_results_java.txt + fi + if [ -f .trivy_out/dependency_results_go.txt ]; then + cat .trivy_out/dependency_results_go.txt + fi + - name: "check is SONAR_TOKEN exists" + env: + super_secret: ${{ secrets.SONAR_TOKEN }} + if: ${{ env.super_secret != '' && inputs.run_sonar == true }} + run: echo "SONAR_TOKEN_EXISTS=true" >> "$GITHUB_ENV" + + - name: Run SonarQube analysis + if: ${{ steps.check_languages.outputs.uses_java == 'true' && env.SONAR_TOKEN_EXISTS == 'true' }} + run: | + # issues with sonar scanner and sslcontext-kickstart 9.1.0, forcing re-download + rm -rf ~/.m2/repository/io/github/hakky54/sslcontext-kickstart/9.1.0 + mvn dependency:get -U -Dartifact=io.github.hakky54:sslcontext-kickstart:9.1.0 + # run sonar scan + mvn sonar:sonar -Dsonar.token="$SONAR_TOKEN" + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + - name: SonarCloud Scan + uses: SonarSource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 + if: ${{ steps.check_languages.outputs.uses_java == 'false' && env.SONAR_TOKEN_EXISTS == 'true' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + + get_docker_images_to_scan: + runs-on: ubuntu-22.04 + container: + image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + outputs: + docker_images: ${{ steps.normalized_docker_images.outputs.images }} + steps: + - *init_tool_versions + - *checkout + - name: Determine docker images to scan + id: normalized_docker_images + env: + DOCKER_IMAGES: ${{ inputs.docker_images }} + run: | + if [ "${{ inputs.run_docker_scan }}" != "true" ]; then + echo "Docker scanning disabled; emitting empty image list." + echo 'images=[]' >> "$GITHUB_OUTPUT" + exit 0 + fi + + INPUT="${DOCKER_IMAGES}" + + if [ -z "$INPUT" ]; then + INPUT="[]" + fi + + normalize_to_json_array() { + local raw="$1" + + # If the input already looks like JSON, return as-is + if echo "$raw" | grep -q '^[[:space:]]*\['; then + echo "$raw" + return + fi + + local json="[" + local first=true + IFS=',' read -ra ITEMS <<< "$raw" + for item in "${ITEMS[@]}"; do + # Trim whitespace around each image reference + item=$(echo "$item" | xargs) + if [ -z "$item" ]; then + continue + fi + if [ "$first" = true ]; then + first=false + else + json+=", " + fi + json+="\"$item\"" + done + json+="]" + echo "$json" + } + + NORMALIZED=$(normalize_to_json_array "$INPUT") + + if [ "$NORMALIZED" = "[]" ]; then + echo "No docker images provided" + exit 1 + fi + + echo "Using provided docker images: $NORMALIZED" + echo "images=$NORMALIZED" >> "$GITHUB_OUTPUT" + + docker_vulnerability_scan: + runs-on: ubuntu-22.04 + needs: [get_docker_images_to_scan] + container: + image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + if: ${{ inputs.run_docker_scan == true }} + strategy: + matrix: + docker_image: ${{ fromJson(needs.get_docker_images_to_scan.outputs.docker_images) }} + steps: + - *init_tool_versions + - *checkout + - *setup_npmrc + - *cache_npm + + - name: make install + run: | + make install + + - name: Build docker images + run: | + make docker-build + env: + DOCKER_IMAGE: ${{ matrix.docker_image }} + + - name: Check docker vulnerabilities + run: | + make trivy-scan-docker + env: + DOCKER_IMAGE: ${{ matrix.docker_image }} + + - name: Show docker vulnerability output + if: always() + run: | + echo "Scan output for ${{ matrix.docker_image }}" + if [ -f .trivy_out/dependency_results_docker.txt ]; then + cat .trivy_out/dependency_results_docker.txt + fi + + IaC-validation: + runs-on: ubuntu-22.04 + container: + image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + steps: + - *init_tool_versions + - *checkout + - *setup_npmrc + - *cache_npm + + - name: Check for SAM templates + id: check_sam_templates + run: | + if [ -d "SAMtemplates" ]; then + echo "****************" + echo "Project has SAM templates" + echo "****************" + echo "sam_exists=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not have SAM templates" + echo "****************" + echo "sam_exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Check for cloudformation templates + id: check_cf_templates + run: | + if [ -d "cloudformation" ]; then + echo "****************" + echo "Project has cloudformation templates" + echo "****************" + echo "cf_exists=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not have cloudformation templates" + echo "****************" + echo "cf_exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Check for cdk + id: check_cdk + run: | + if [ -d "packages/cdk" ]; then + echo "****************" + echo "Project has cdk" + echo "****************" + echo "cdk_exists=true" >> "$GITHUB_OUTPUT" + else + echo "****************" + echo "Project does not have cdk" + echo "****************" + echo "cdk_exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Run cfn-lint + if: steps.check_sam_templates.outputs.sam_exists == 'true' || steps.check_cf_templates.outputs.cf_exists == 'true' + run: | + make cfn-lint + + - name: make install NodeJS + if: steps.check_cdk.outputs.cdk_exists == 'true' + run: | + make install-node compile + + - name: Run cdk-synth + if: steps.check_cdk.outputs.cdk_exists == 'true' + run: | + make cdk-synth + + - name: Run cfn-guard script for sam templates + run: | + make cfn-guard-sam-templates + + - name: Run cfn-guard script for cloudformation templates + if: steps.check_cf_templates.outputs.cf_exists == 'true' + run: | + make cfn-guard-cloudformation + - name: Run cfn-guard script for cdk templates + if: steps.check_cdk.outputs.cdk_exists == 'true' + run: | + make cfn-guard-cdk + + - name: Download terraform plans + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + pattern: "*_terraform_plan" + path: terraform_plans/ + merge-multiple: true + + - name: Check terraform plans exist + id: check_terraform_plans + run: | + if [ ! -d terraform_plans ]; then + echo "Terraform plans not present." + echo "terraform_plans_exist=false" >> "$GITHUB_OUTPUT" + else + echo "Terraform plans present:" + ls -l terraform_plans/ + echo "terraform_plans_exist=true" >> "$GITHUB_OUTPUT" + fi + + - name: Run cfn-guard script for terraform plans + if: steps.check_terraform_plans.outputs.terraform_plans_exist == 'true' + run: | + make cfn-guard-terraform + + - name: Show cfn-guard output + if: failure() + run: find .cfn_guard_out -type f -print0 | xargs -0 cat + + - name: Upload cfn_guard_output + if: failure() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f + with: + name: cfn_guard_output + path: .cfn_guard_out diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c4cc00a..e7957d2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,36 +8,40 @@ env: BRANCH_NAME: ${{ github.event.ref.BRANCH_NAME }} jobs: - get_asdf_version: + get_config_values: runs-on: ubuntu-22.04 outputs: - asdf_version: ${{ steps.asdf-version.outputs.version }} tag_format: ${{ steps.load-config.outputs.TAG_FORMAT }} + devcontainer_version: ${{ steps.load-config.outputs.DEVCONTAINER_VERSION }} + devcontainer_image: ${{ steps.load-config.outputs.DEVCONTAINER_IMAGE }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - - name: Get asdf version - id: asdf-version - run: echo "version=$(awk '!/^#/ && NF {print $1; exit}' .tool-versions.asdf)" >> "$GITHUB_OUTPUT" - name: Load config value id: load-config run: | TAG_FORMAT=$(yq '.TAG_FORMAT' .github/config/settings.yml) - echo "TAG_FORMAT=$TAG_FORMAT" >> "$GITHUB_OUTPUT" + DEVCONTAINER_IMAGE=$(jq -r '.build.args.IMAGE_NAME' .devcontainer/devcontainer.json) + DEVCONTAINER_VERSION=$(jq -r '.build.args.IMAGE_VERSION' .devcontainer/devcontainer.json) + { + echo "TAG_FORMAT=$TAG_FORMAT" + echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE" + echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION" + } >> "$GITHUB_OUTPUT" quality_checks: - needs: [get_asdf_version] - uses: ./.github/workflows/quality-checks.yml + needs: [get_config_values] + uses: ./.github/workflows/quality-checks-devcontainer.yml with: - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" secrets: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} tag_release: - needs: [quality_checks, get_asdf_version] - uses: ./.github/workflows/tag-release.yml + needs: [quality_checks, get_config_values] + uses: ./.github/workflows/tag-release-devcontainer.yml with: dry_run: false - asdfVersion: ${{ needs.get_asdf_version.outputs.asdf_version }} + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: main - tag_format: ${{ needs.get_asdf_version.outputs.tag_format }} + tag_format: ${{ needs.get_config_values.outputs.tag_format }} secrets: inherit diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml new file mode 100644 index 0000000..f796532 --- /dev/null +++ b/.github/workflows/tag-release-devcontainer.yml @@ -0,0 +1,278 @@ +name: Tag Release + +on: + workflow_call: + inputs: + dry_run: + description: "Whether to run in dry-run mode (true) or create actual tags (false)" + required: true + type: boolean + branch_name: + description: "The branch name to base the release on" + required: true + type: string + runtime_docker_image: + type: string + required: true + publish_packages: + description: "comma separated list of package folders to publish to an npm registry" + required: false + type: string + default: "" + tag_format: + description: "The tag format to use for the release tags" + required: false + type: string + default: "v${version}" + main_branch: + description: "The main branch name for releases" + required: false + type: string + default: "main" + extra_artifact_name: + description: "An extra artifact to include in the release" + required: false + type: string + extra_artifact_id: + description: "An id for the extra artifact" + required: false + type: string + extra_artifact_run_id: + description: "An run id for the extra artifact" + required: false + type: string + extra_artifact_repository: + description: "An repository for the extra artifact" + required: false + type: string + outputs: + version_tag: + value: ${{ jobs.tag_release.outputs.version_tag }} + change_set_version: + description: "The change set version for deployments" + value: ${{ jobs.tag_release.outputs.change_set_version }} + next_version_tag: + description: "The next version tag that will be created" + value: ${{ jobs.tag_release.outputs.next_version_tag }} + secrets: + NPM_TOKEN: + required: false + description: "NPM token to publish packages" +jobs: + tag_release: + runs-on: ubuntu-22.04 + container: + image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + options: --user 1001:1001 --group-add 128 + defaults: + run: + shell: bash + outputs: + version_tag: ${{steps.output_version_tag.outputs.VERSION_TAG}} + change_set_version: ${{ steps.output_change_set_version.outputs.CHANGE_SET_VERSION }} + next_version_tag: ${{ steps.output_version_tag.outputs.NEXT_VERSION_TAG }} + steps: + - name: copy .tool-versions + run: | + cp /home/vscode/.tool-versions "$HOME/.tool-versions" + - name: Clone calling repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + repository: ${{ github.repository }} + ref: ${{ github.sha }} + + - name: Checkout semantic-release workflow + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + repository: NHSDigital/eps-common-workflows + sparse-checkout-cone-mode: false + path: common_workflow_config + sparse-checkout: | + package.json + package-lock.json + release.config.cjs + releaseNotesTemplates/commit.hbs + - name: Install semantic release dependencies globally + run: | + cd common_workflow_config + dependencies="$(jq -r '.devDependencies | to_entries | map("\(.key)@\(.value)") | join(" ")' package.json)" + echo "Installing: $dependencies" + + # shellcheck disable=SC2086 + npm install --global $dependencies + echo "Copying semantic-release config and templates for use in the workflow" + cp release.config.cjs ../ + mkdir -p ../releaseNotesTemplates + cp releaseNotesTemplates/commit.hbs ../releaseNotesTemplates/ + echo "Current dir is ${PWD}" + - name: Setup Git branch for semantic-release + run: | + # When running from a PR, GitHub checks out a merge commit + # We need to ensure we're on the actual branch for semantic-release + git checkout -B "${BRANCH_NAME}" + git branch --show-current + env: + BRANCH_NAME: ${{ inputs.branch_name }} + + - name: Install Dependencies and Build Package + if: inputs.publish_packages != '' + run: | + make install + make build + + - name: Download extra artifact + if: ${{ inputs.extra_artifact_name != '' }} + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 + with: + artifact-ids: ${{ inputs.extra_artifact_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ inputs.extra_artifact_repository }} + run-id: ${{ inputs.extra_artifact_run_id }} + + - name: Set VERSION_TAG based on dry_run flag + id: output_version_tag + run: | + if [ "${{ inputs.dry_run }}" = "true" ]; then + # Determine semantic-release command based on branch + if [ "${BRANCH_NAME}" = "${MAIN_BRANCH}" ]; then + echo "on ${MAIN_BRANCH} branch" + npx semantic-release --dry-run --tag-format "${TAG_FORMAT}" > semantic-release-output.log + exit_code=1 + else + # For non-main branches, override the branches configuration + echo "overriding branches for semantic-release to ${BRANCH_NAME}" + + # we need to set GITHUB_REF and GITHUB_EVENT_NAME for semantic-release to work correctly + # but need to ensure that actionlint does not complain about unused variables + + # shellcheck disable=SC2034 + GITHUB_REF=refs/heads/${BRANCH_NAME} + # shellcheck disable=SC2034 + GITHUB_EVENT_NAME=push + echo "running semantic-release" + npx semantic-release --dry-run --branches "${BRANCH_NAME}" --branch "${BRANCH_NAME}" --tag-format "${TAG_FORMAT}" > semantic-release-output.log + echo "finish semantic-release with exit code $?" + exit_code=0 + fi + # Dry run mode: use short git SHA and get next version for summary + VERSION_TAG=$(git rev-parse --short HEAD) + echo "Getting next_version" + NEXT_VERSION=$(grep -i 'The next release version is' semantic-release-output.log | sed -E 's/.* ([[:digit:].]+)$/\1/' || true) + NEXT_VERSION=${NEXT_VERSION:-UNKNOWN} + echo "got next version" + # disabling shellcheck as replace does not work + # shellcheck disable=SC2001 + NEW_VERSION_TAG=$(echo "$TAG_FORMAT" | sed "s/\${version}/$NEXT_VERSION/") + echo "## VERSION TAG : ${VERSION_TAG}" >> "$GITHUB_STEP_SUMMARY" + echo "## NEXT TAG WILL BE : ${NEW_VERSION_TAG}" >> "$GITHUB_STEP_SUMMARY" + if [ "${NEXT_VERSION}" == "UNKNOWN" ] + then + echo "Could not get next tag. Here is the log from semantic-release" + cat semantic-release-output.log + exit ${exit_code} + fi + else + # Production mode: get next version and create actual tag + npx semantic-release --dry-run --tag-format "${TAG_FORMAT}" > semantic-release-output.log + NEXT_VERSION=$(grep -i 'The next release version is' semantic-release-output.log | sed -E 's/.* ([[:digit:].]+)$/\1/') + # disabling shellcheck as replace does not work + # shellcheck disable=SC2001 + VERSION_TAG=$(echo "$TAG_FORMAT" | sed "s/\${version}/$NEXT_VERSION/") + echo "## VERSION TAG : ${VERSION_TAG}" >> "$GITHUB_STEP_SUMMARY" + fi + echo "VERSION_TAG=${VERSION_TAG}" >> "$GITHUB_OUTPUT" + echo "VERSION_TAG=${VERSION_TAG}" >> "$GITHUB_ENV" + echo "NEXT_VERSION_TAG=${NEW_VERSION_TAG}" >> "$GITHUB_OUTPUT" + env: + GITHUB_TOKEN: ${{ github.token }} + BRANCH_NAME: ${{ inputs.branch_name }} + PUBLISH_PACKAGES: ${{ inputs.publish_packages }} + TAG_FORMAT: ${{ inputs.tag_format }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + MAIN_BRANCH: ${{ inputs.main_branch }} + EXTRA_ASSET: ${{ inputs.extra_artifact_name }} + + - name: Create semantic release tag + if: ${{ !inputs.dry_run }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + PUBLISH_PACKAGES: ${{ inputs.publish_packages }} + TAG_FORMAT: ${{ inputs.tag_format }} + MAIN_BRANCH: ${{ inputs.main_branch }} + EXTRA_ASSET: ${{ inputs.extra_artifact_name }} + run: | + npx semantic-release --tag-format "${TAG_FORMAT}" + + - name: Get release for editing + if: ${{ !inputs.dry_run }} + id: get_release + # version 1.2.4 + uses: cardinalby/git-get-release-action@5172c3a026600b1d459b117738c605fabc9e4e44 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + tag: ${{ steps.output_version_tag.outputs.VERSION_TAG }} + + - name: Edit Release + if: ${{ !inputs.dry_run }} + # version 1.2.0 + uses: irongut/EditRelease@ccf529ad26dddf9996e7dd0f24ca5da4ea507cc2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + id: ${{ steps.get_release.outputs.id }} + body: | + ## Info + [Release workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) - Workflow ID: ${{ github.run_id }} + + It was initialized by [${{ github.event.sender.login }}](${{ github.event.sender.html_url }}) + + - name: Checkout gh-pages branch + if: ${{ !inputs.dry_run }} + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + repository: ${{ github.repository }} + ref: gh-pages + path: gh-pages + + - name: Publish release notes to gh-pages + if: ${{ !inputs.dry_run }} + working-directory: gh-pages + env: + RELEASE_ID: ${{ steps.get_release.outputs.id }} + VERSION_TAG: ${{ steps.output_version_tag.outputs.VERSION_TAG }} + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + notes_dir="release_notes" + mkdir -p "$notes_dir" + note_file="$notes_dir/${VERSION_TAG}.md" + + gh api "/repos/${GH_REPO}/releases/${RELEASE_ID}" | jq -r '.body // ""' > "$note_file" + + if [ ! -s "$note_file" ]; then + echo "Release notes are empty; skipping gh-pages update." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + git add -f "$note_file" + if git diff --cached --quiet; then + echo "No changes detected in release notes; skipping commit." + exit 0 + fi + + git commit -m "docs: add release notes for ${VERSION_TAG}" + parallel --retries 10 --delay 3 ::: "git pull --rebase && git push" + + - name: Output Change Set Version + id: output_change_set_version + shell: bash + run: | + TIMESTAMP=$(date +%s) + VERSION=$(echo ${{ steps.output_version_tag.outputs.VERSION_TAG }} | tr . -) + echo CHANGE_SET_VERSION="$VERSION-$TIMESTAMP" >> "$GITHUB_OUTPUT" diff --git a/.gitignore b/.gitignore index 5642dae..0ed3cb4 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ .DS_Store release_notes .venv -.asdf \ No newline at end of file +.asdf +.trivy_out diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index db94a2e..f319077 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,7 +23,7 @@ repos: entry: bash args: - -c - - 'docker run -v "$LOCAL_WORKSPACE_FOLDER:/src" git-secrets --pre_commit_hook' + - "git-secrets --pre_commit_hook" language: system - id: lint-githubactions name: Lint github actions diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index 8605e6e..0000000 --- a/.tool-versions +++ /dev/null @@ -1,5 +0,0 @@ -nodejs 24.12.0 -actionlint 1.7.10 -shellcheck 0.11.0 -python 3.14.2 -poetry 2.2.1 diff --git a/.tool-versions.asdf b/.tool-versions.asdf deleted file mode 100644 index aa85979..0000000 --- a/.tool-versions.asdf +++ /dev/null @@ -1,2 +0,0 @@ -# define the .asdf-version to use here -0.18.0 \ No newline at end of file diff --git a/.trivyignore.yaml b/.trivyignore.yaml index b578dd9..6d8e073 100644 --- a/.trivyignore.yaml +++ b/.trivyignore.yaml @@ -10,6 +10,9 @@ vulnerabilities: - id: CVE-2026-25547 statement: isaacs/brace-expansion vulnerability accepted as risk - dependency of semantic-release expired_at: 2026-03-01 - - id: CVE-2026-0775 + - id: CVE-2026-0775 statement: npm vulnerability accepted as risk - dependency of semantic-release expired_at: 2026-03-01 + - id: CVE-2026-26960 + statement: tar vulnerability accepted as risk - dependency of semantic-release + expired_at: 2026-03-01 diff --git a/Makefile b/Makefile index aae27f8..e466290 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,8 @@ install-hooks: install-python deep-clean: find . -name 'node_modules' -type d -prune -exec rm -rf '{}' + -check-licenses: check-licenses-python - -check-licenses-python: - scripts/check_python_licenses.sh - lint: lint-githubactions lint-githubaction-scripts + echo "Linting complete" lint-githubactions: actionlint @@ -32,3 +28,6 @@ test: build: echo "Not implemented" + +%: + @$(MAKE) -f /usr/local/share/eps/Mk/common.mk $@ diff --git a/README.md b/README.md index 590766a..3aa7ea2 100644 --- a/README.md +++ b/README.md @@ -157,6 +157,43 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ``` +## quality checks - dev container version +This workflow runs common quality checks using a prebuilt devcontainer (https://github.com/NHSDigital/eps-devcontainers). +To use this, you must have overridden any common makefile targets described in https://github.com/NHSDigital/eps-devcontainers?tab=readme-ov-file#common-makefile-targets +#### Inputs + +- `run_sonar`: Whether to run sonar checks or not. +- `run_docker_scan`: whether to run a scan of docker images +- `docker_images`: csv list of docker images to scan. These must match images produced by make docker-build +- `runtime_docker_image`: the docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers +#### Secret Inputs +- `SONAR_TOKEN`: Token used to authenticate to sonar + +#### Outputs + +None + +#### Example + +To use this workflow in your repository, call it from another workflow file: + +```yaml +name: Release + +on: + workflow_dispatch: + +jobs: + quality_checks: + uses: NHSDigital/eps-common-workflows/.github/workflows/quality-checks.yml@f5c8313a10855d0cc911db6a9cd666494c00045a + needs: [get_config_values] + with: + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" + run_docker_scan: true + docker_images: fhir-facade,validator + secrets: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} +``` ## tag release @@ -197,6 +234,49 @@ jobs: publish_package: false ``` +## tag release - devcontainer version +This workflow uses the semantic-release npm package to generate a new version tag, changelog, and github release for a repo. +*The devcontainer MUST have node installed* +#### Inputs + +- `dry_run`: Whether to run in dry_run mode (do not create tags) or not +- `branch_name`: The branch name to base the release on +- `runtime_docker_image`: the docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers +- `publish_packages`: comma separated list of package folders to publish to an npm registry +- `tagFormat`: Default `v\\${version}`. A template for the version tag. +- `main_branch`: The branch to use for publishing. Defaults to main +- `extra_artifact_name`: optional param to include an extra artifact in the release +- `extra_artifact_id`: optional param of the extra artifact id to include in the release +- `extra_artifact_run_id`: optional param of the run id to download the extra artifact id to include in the release +- `extra_artifact_repository` optional param to indicate which repo the run to download the artifact was from + +#### Outputs + +- `version_tag`: The version tag created by semantic-release. +- `change_set_version`: A timestamped string that con be used for creating changesets. + +#### Example + +To use this workflow in your repository, call it from another workflow file: + +```yaml +name: Release + +on: + workflow_dispatch: + +jobs: + tag_release: + uses: NHSDigital/eps-common-workflows/.github/workflows/tag-release-devcontainer.yml@f5c8313a10855d0cc911db6a9cd666494c00045a + needs: [get_config_values] + with: + tagFormat: "v\\${version}-beta" + dry_run: true + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" + branch_name: main + publish_package: false +``` + ## Secret scanning docker diff --git a/scripts/check_python_licenses.sh b/scripts/check_python_licenses.sh deleted file mode 100755 index ea3de41..0000000 --- a/scripts/check_python_licenses.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -euo pipefail - -# known packages with dual licensing -IGNORE_PACKAGES=("PyGithub" "chardet" "text-unidecode") -LICENSES=$(poetry run pip-licenses --ignore-packages "${IGNORE_PACKAGES[@]}") -INCOMPATIBLE_LIBS=$(echo "$LICENSES" | grep 'GPL' || true) - -if [[ -z $INCOMPATIBLE_LIBS ]]; then - exit 0 -else - echo "The following libraries were found which are not compatible with this project's license:" - echo "$INCOMPATIBLE_LIBS" - exit 1 -fi From 3552e35b88bbfeff346a374c9a01a3533637963c Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 06:45:13 +0000 Subject: [PATCH 02/18] verify attestation --- .github/workflows/pull_request.yml | 5 + .github/workflows/verify_attestation.yml | 135 +++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 .github/workflows/verify_attestation.yml diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ff1a7a6..ea9741a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -37,6 +37,11 @@ jobs: echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE" echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION" } >> "$GITHUB_OUTPUT" + verify_attestation: + needs: get_config_values + uses: ./.github/workflows/verify_attestation.yml + with: + runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" quality_checks: uses: ./.github/workflows/quality-checks-devcontainer.yml needs: [get_config_values] diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml new file mode 100644 index 0000000..6d97273 --- /dev/null +++ b/.github/workflows/verify_attestation.yml @@ -0,0 +1,135 @@ +name: Verify image digest and attestation +"on": + workflow_call: + inputs: + runtime_docker_image: + required: true + type: string + description: Image reference as name:tag (for example node_24_python_3_12:v1.2.3) or fully qualified image ref + registry: + required: false + type: string + default: ghcr.io + namespace: + required: false + type: string + default: nhsdigital/eps-devcontainers + owner: + required: false + type: string + default: NHSDigital + signer_workflow: + required: false + type: string + default: "" + signer_repo: + required: false + type: string + default: "" + source_ref: + required: false + type: string + default: "" + predicate_type: + required: false + type: string + default: https://slsa.dev/provenance/v1 + bundle_from_oci: + required: false + type: boolean + default: false + outputs: + pinned_image: + description: Fully-qualified digest-pinned image reference + value: ${{ jobs.verify.outputs.pinned_image }} + resolved_digest: + description: Resolved digest for the supplied image reference + value: ${{ jobs.verify.outputs.resolved_digest }} + +jobs: + verify: + runs-on: ubuntu-22.04 + permissions: + contents: read + packages: read + attestations: read + outputs: + pinned_image: ${{ steps.resolve.outputs.pinned_image }} + resolved_digest: ${{ steps.resolve.outputs.resolved_digest }} + steps: + - name: Login to github container registry + if: startsWith(inputs.registry, 'ghcr.io') + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve digest and validate expected value + id: resolve + shell: bash + env: + RUNTIME_DOCKER_IMAGE: ${{ inputs.runtime_docker_image }} + REGISTRY: ${{ inputs.registry }} + NAMESPACE: ${{ inputs.namespace }} + run: | + set -euo pipefail + + if [[ "$RUNTIME_DOCKER_IMAGE" == *"/"* ]]; then + IMAGE_REF="$RUNTIME_DOCKER_IMAGE" + else + IMAGE_REF="${REGISTRY}/${NAMESPACE}/${RUNTIME_DOCKER_IMAGE}" + fi + + if [[ "$IMAGE_REF" == *@sha256:* ]]; then + IMAGE_BASE="${IMAGE_REF%@*}" + RESOLVED_DIGEST="${IMAGE_REF#*@}" + else + RESOLVED_DIGEST="$(docker buildx imagetools inspect "$IMAGE_REF" | awk '/^Digest:/ {print $2; exit}')" + IMAGE_BASE="${IMAGE_REF%:*}" + fi + + if [[ -z "$RESOLVED_DIGEST" ]]; then + echo "Could not resolve digest for image: $IMAGE_REF" >&2 + exit 1 + fi + + PINNED_IMAGE="${IMAGE_BASE}@${RESOLVED_DIGEST}" + echo "resolved_digest=${RESOLVED_DIGEST}" >> "$GITHUB_OUTPUT" + echo "pinned_image=${PINNED_IMAGE}" >> "$GITHUB_OUTPUT" + echo "Resolved and matched digest for ${IMAGE_REF}: ${RESOLVED_DIGEST}" + + - name: Verify attestation + shell: bash + env: + GH_TOKEN: ${{ github.token }} + OWNER: ${{ inputs.owner }} + SIGNER_WORKFLOW: ${{ inputs.signer_workflow }} + SIGNER_REPO: ${{ inputs.signer_repo }} + SOURCE_REF: ${{ inputs.source_ref }} + PREDICATE_TYPE: ${{ inputs.predicate_type }} + BUNDLE_FROM_OCI: ${{ inputs.bundle_from_oci }} + PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }} + run: | + set -euo pipefail + + args=("oci://${PINNED_IMAGE}" "--owner" "$OWNER" "--predicate-type" "$PREDICATE_TYPE") + + if [[ -n "$SIGNER_WORKFLOW" ]]; then + args+=("--signer-workflow" "$SIGNER_WORKFLOW") + fi + + if [[ -n "$SIGNER_REPO" ]]; then + args+=("--signer-repo" "$SIGNER_REPO") + fi + + if [[ -n "$SOURCE_REF" ]]; then + args+=("--source-ref" "$SOURCE_REF") + fi + + if [[ "$BUNDLE_FROM_OCI" == "true" ]]; then + args+=("--bundle-from-oci") + fi + + gh attestation verify "${args[@]}" + echo "Verified attestation for ${PINNED_IMAGE}" From be65ba3ca403fe77ded8a8ea45b109f0d410b4ca Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 06:51:36 +0000 Subject: [PATCH 03/18] use new image --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5914baa..be537cb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "args": { "DOCKER_GID": "${env:DOCKER_GID:}", "IMAGE_NAME": "node_24_python_3_14", - "IMAGE_VERSION": "v1.0.4", + "IMAGE_VERSION": "ci-228a6e2", "USER_UID": "${localEnv:USER_ID:}", "USER_GID": "${localEnv:GROUP_ID:}" }, From 135c0904c8173a48586237036f5d2c0edee1f0d0 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 06:58:49 +0000 Subject: [PATCH 04/18] debug --- .github/workflows/verify_attestation.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml index 6d97273..fd9687a 100644 --- a/.github/workflows/verify_attestation.yml +++ b/.github/workflows/verify_attestation.yml @@ -131,5 +131,7 @@ jobs: args+=("--bundle-from-oci") fi - gh attestation verify "${args[@]}" + echo "Running attestation verification with arguments: ${args[*]}" + gh attestation verify "${args[@]}" 2>&1 + echo "Verified attestation for ${PINNED_IMAGE}" From 40273265d9743c658b447c0427cfdffd279c9c74 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:05:05 +0000 Subject: [PATCH 05/18] force output --- .github/workflows/verify_attestation.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml index fd9687a..c6dbd7f 100644 --- a/.github/workflows/verify_attestation.yml +++ b/.github/workflows/verify_attestation.yml @@ -41,13 +41,13 @@ name: Verify image digest and attestation outputs: pinned_image: description: Fully-qualified digest-pinned image reference - value: ${{ jobs.verify.outputs.pinned_image }} + value: ${{ jobs.verify_attestation.outputs.pinned_image }} resolved_digest: description: Resolved digest for the supplied image reference - value: ${{ jobs.verify.outputs.resolved_digest }} + value: ${{ jobs.verify_attestation.outputs.resolved_digest }} jobs: - verify: + verify_attestation: runs-on: ubuntu-22.04 permissions: contents: read @@ -132,6 +132,5 @@ jobs: fi echo "Running attestation verification with arguments: ${args[*]}" - gh attestation verify "${args[@]}" 2>&1 - + GH_FORCE_TTY=120 gh attestation verify "${args[@]}" 2>&1 echo "Verified attestation for ${PINNED_IMAGE}" From c9e4e0cad536d5edc61470c4520e40e0133fa01e Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:09:41 +0000 Subject: [PATCH 06/18] better output --- .github/workflows/verify_attestation.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml index c6dbd7f..313082c 100644 --- a/.github/workflows/verify_attestation.yml +++ b/.github/workflows/verify_attestation.yml @@ -97,7 +97,9 @@ jobs: PINNED_IMAGE="${IMAGE_BASE}@${RESOLVED_DIGEST}" echo "resolved_digest=${RESOLVED_DIGEST}" >> "$GITHUB_OUTPUT" echo "pinned_image=${PINNED_IMAGE}" >> "$GITHUB_OUTPUT" - echo "Resolved and matched digest for ${IMAGE_REF}: ${RESOLVED_DIGEST}" + echo "Resolved image reference: ${IMAGE_REF}" + echo "Resolved digest: ${RESOLVED_DIGEST}" + echo "Resolved image reference: ${PINNED_IMAGE}" - name: Verify attestation shell: bash @@ -131,6 +133,5 @@ jobs: args+=("--bundle-from-oci") fi - echo "Running attestation verification with arguments: ${args[*]}" GH_FORCE_TTY=120 gh attestation verify "${args[@]}" 2>&1 echo "Verified attestation for ${PINNED_IMAGE}" From 443755fe37217157fbfe49518fe650f104b69f20 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:12:55 +0000 Subject: [PATCH 07/18] verify attestation --- .github/workflows/quality-checks-devcontainer.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index ee26da6..60af68c 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -26,8 +26,13 @@ on: required: true jobs: + verify_attestation: + uses: ./.github/workflows/verify_attestation.yml + with: + runtime_docker_image: "${{ inputs.runtime_docker_image }}" quality_checks: runs-on: ubuntu-22.04 + needs: verify_attestation container: image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} options: --user 1001:1001 --group-add 128 From e5dfa968f6cdffe5c91f231dbe9e7103ab773d00 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:16:26 +0000 Subject: [PATCH 08/18] verify attestation --- .github/workflows/pull_request.yml | 5 ----- .github/workflows/quality-checks-devcontainer.yml | 12 +++++++----- .github/workflows/tag-release-devcontainer.yml | 7 ++++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ea9741a..ff1a7a6 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -37,11 +37,6 @@ jobs: echo "DEVCONTAINER_IMAGE=$DEVCONTAINER_IMAGE" echo "DEVCONTAINER_VERSION=$DEVCONTAINER_VERSION" } >> "$GITHUB_OUTPUT" - verify_attestation: - needs: get_config_values - uses: ./.github/workflows/verify_attestation.yml - with: - runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" quality_checks: uses: ./.github/workflows/quality-checks-devcontainer.yml needs: [get_config_values] diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index 60af68c..358a7bb 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -34,7 +34,7 @@ jobs: runs-on: ubuntu-22.04 needs: verify_attestation container: - image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + image: ${{ needs.verify_attestation.outputs.pinned_image }} options: --user 1001:1001 --group-add 128 defaults: run: @@ -212,8 +212,9 @@ jobs: get_docker_images_to_scan: runs-on: ubuntu-22.04 + needs: verify_attestation container: - image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + image: ${{ needs.verify_attestation.outputs.pinned_image }} options: --user 1001:1001 --group-add 128 defaults: run: @@ -281,9 +282,9 @@ jobs: docker_vulnerability_scan: runs-on: ubuntu-22.04 - needs: [get_docker_images_to_scan] + needs: [get_docker_images_to_scan, verify_attestation] container: - image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + image: ${{ needs.verify_attestation.outputs.pinned_image }} options: --user 1001:1001 --group-add 128 defaults: run: @@ -324,8 +325,9 @@ jobs: IaC-validation: runs-on: ubuntu-22.04 + needs: verify_attestation container: - image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + image: ${{ needs.verify_attestation.outputs.pinned_image }} options: --user 1001:1001 --group-add 128 defaults: run: diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml index f796532..13df746 100644 --- a/.github/workflows/tag-release-devcontainer.yml +++ b/.github/workflows/tag-release-devcontainer.yml @@ -59,10 +59,15 @@ on: required: false description: "NPM token to publish packages" jobs: + verify_attestation: + uses: ./.github/workflows/verify_attestation.yml + with: + runtime_docker_image: "${{ inputs.runtime_docker_image }}" tag_release: runs-on: ubuntu-22.04 + needs: verify_attestation container: - image: ghcr.io/nhsdigital/eps-devcontainers/${{ inputs.runtime_docker_image }} + image: ${{ needs.verify_attestation.outputs.pinned_image }} options: --user 1001:1001 --group-add 128 defaults: run: From 6785154e704efbc1727d2f3aa778367257a55d9d Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:19:14 +0000 Subject: [PATCH 09/18] update trivyignore --- .trivyignore.yaml | 3 + package-lock.json | 491 +--------------------------------------------- package.json | 3 +- 3 files changed, 9 insertions(+), 488 deletions(-) diff --git a/.trivyignore.yaml b/.trivyignore.yaml index 6d8e073..2809a32 100644 --- a/.trivyignore.yaml +++ b/.trivyignore.yaml @@ -16,3 +16,6 @@ vulnerabilities: - id: CVE-2026-26960 statement: tar vulnerability accepted as risk - dependency of semantic-release expired_at: 2026-03-01 + - id: CVE-2026-26996 + statement: minimatch vulnerability accepted as risk - dependency of semantic-release + expired_at: 2026-04-01 diff --git a/package-lock.json b/package-lock.json index 0733795..f5a605f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "@semantic-release/github": "^12.0.5", "@semantic-release/release-notes-generator": "^14.1.0", "conventional-changelog-eslint": "^6.0.0", - "license-checker": "^25.0.1", "semantic-release": "^25.0.3" } }, @@ -132,6 +131,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -634,13 +634,6 @@ "dev": true, "license": "MIT" }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true, - "license": "ISC" - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -728,16 +721,6 @@ "dev": true, "license": "MIT" }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/array-ify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", @@ -745,20 +728,6 @@ "dev": true, "license": "MIT" }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/before-after-hook": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", @@ -773,17 +742,6 @@ "dev": true, "license": "MIT" }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1112,13 +1070,6 @@ "dot-prop": "^5.1.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -1326,17 +1277,6 @@ } } }, - "node_modules/debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -1347,17 +1287,6 @@ "node": ">=4.0.0" } }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1736,23 +1665,6 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/function-timeout": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", @@ -1817,28 +1729,6 @@ "traverse": "0.6.8" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -1878,19 +1768,6 @@ "node": ">=4" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -1914,13 +1791,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -2034,18 +1904,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2084,22 +1942,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2257,48 +2099,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/license-checker": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", - "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "read-installed": "~4.0.3", - "semver": "^5.5.0", - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-satisfies": "^4.0.0", - "treeify": "^1.1.0" - }, - "bin": { - "license-checker": "bin/license-checker" - } - }, - "node_modules/license-checker/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/license-checker/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -2430,6 +2230,7 @@ "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -2535,19 +2336,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -2558,19 +2346,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2620,43 +2395,6 @@ "node": ">=18" } }, - "node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-url": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", @@ -2827,13 +2565,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true, - "license": "ISC" - }, "node_modules/npm-run-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", @@ -4690,6 +4421,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4832,16 +4564,6 @@ "node": ">=0.10.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", @@ -4858,38 +4580,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, "node_modules/p-each-series": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", @@ -5098,16 +4788,6 @@ "node": ">=4" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5118,13 +4798,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -5225,49 +4898,6 @@ "rc": "cli.js" } }, - "node_modules/read-installed": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", - "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "slide": "~1.1.3", - "util-extend": "^1.0.1" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.2" - } - }, - "node_modules/read-installed/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/read-package-json": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", - "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", - "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.1", - "json-parse-even-better-errors": "^2.3.0", - "normalize-package-data": "^2.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, "node_modules/read-package-up": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", @@ -5350,20 +4980,6 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/readdir-scoped-modules": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", - "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "license": "ISC", - "dependencies": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, "node_modules/registry-auth-token": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", @@ -5387,27 +5003,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -5431,6 +5026,7 @@ "integrity": "sha512-WRgl5GcypwramYX4HV+eQGzUbD7UUbljVmS+5G1uMwX/wLgYuJAxGeerXJDMO2xshng4+FXqCgyB5QfClV6WjA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/error": "^4.0.0", @@ -5758,16 +5354,6 @@ "node": ">=8" } }, - "node_modules/slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", - "dev": true, - "license": "ISC", - "engines": { - "node": "*" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5785,18 +5371,6 @@ "dev": true, "license": "MIT" }, - "node_modules/spdx-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", - "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-find-index": "^1.0.2", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -5833,25 +5407,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/spdx-ranges": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", - "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", - "dev": true, - "license": "(MIT AND CC-BY-3.0)" - }, - "node_modules/spdx-satisfies": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz", - "integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "spdx-compare": "^1.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-ranges": "^2.0.0" - } - }, "node_modules/split2": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", @@ -6025,19 +5580,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tagged-tag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", @@ -6197,6 +5739,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -6230,16 +5773,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/treeify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", - "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -6360,13 +5893,6 @@ "dev": true, "license": "MIT" }, - "node_modules/util-extend": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", - "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", - "dev": true, - "license": "MIT" - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -6462,13 +5988,6 @@ "dev": true, "license": "MIT" }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 862b5db..ea9de84 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "@semantic-release/github": "^12.0.5", "@semantic-release/release-notes-generator": "^14.1.0", "conventional-changelog-eslint": "^6.0.0", - "license-checker": "^25.0.1", "semantic-release": "^25.0.3" } -} \ No newline at end of file +} From fe89e4b93ddd50d9b9f17dd17be4ce1658b42d48 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:33:40 +0000 Subject: [PATCH 10/18] fail attestation --- .devcontainer/devcontainer.json | 2 +- .github/workflows/verify_attestation.yml | 22 ++-------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index be537cb..601f388 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "args": { "DOCKER_GID": "${env:DOCKER_GID:}", "IMAGE_NAME": "node_24_python_3_14", - "IMAGE_VERSION": "ci-228a6e2", + "IMAGE_VERSION": "pr-23-d823049", "USER_UID": "${localEnv:USER_ID:}", "USER_GID": "${localEnv:GROUP_ID:}" }, diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml index 313082c..2bc27a5 100644 --- a/.github/workflows/verify_attestation.yml +++ b/.github/workflows/verify_attestation.yml @@ -21,23 +21,15 @@ name: Verify image digest and attestation signer_workflow: required: false type: string - default: "" + default: ".github/workflows/build_multi_arch_image.yml@refs/heads/main" signer_repo: required: false type: string - default: "" - source_ref: - required: false - type: string - default: "" + default: "NHSDigital/eps-devcontainers" predicate_type: required: false type: string default: https://slsa.dev/provenance/v1 - bundle_from_oci: - required: false - type: boolean - default: false outputs: pinned_image: description: Fully-qualified digest-pinned image reference @@ -108,9 +100,7 @@ jobs: OWNER: ${{ inputs.owner }} SIGNER_WORKFLOW: ${{ inputs.signer_workflow }} SIGNER_REPO: ${{ inputs.signer_repo }} - SOURCE_REF: ${{ inputs.source_ref }} PREDICATE_TYPE: ${{ inputs.predicate_type }} - BUNDLE_FROM_OCI: ${{ inputs.bundle_from_oci }} PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }} run: | set -euo pipefail @@ -125,13 +115,5 @@ jobs: args+=("--signer-repo" "$SIGNER_REPO") fi - if [[ -n "$SOURCE_REF" ]]; then - args+=("--source-ref" "$SOURCE_REF") - fi - - if [[ "$BUNDLE_FROM_OCI" == "true" ]]; then - args+=("--bundle-from-oci") - fi - GH_FORCE_TTY=120 gh attestation verify "${args[@]}" 2>&1 echo "Verified attestation for ${PINNED_IMAGE}" From a68cbd25e92026dd6f280ead7368eb4eefb2cf38 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:41:06 +0000 Subject: [PATCH 11/18] fix ref --- .github/workflows/verify_attestation.yml | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml index 2bc27a5..0c3ba01 100644 --- a/.github/workflows/verify_attestation.yml +++ b/.github/workflows/verify_attestation.yml @@ -18,14 +18,10 @@ name: Verify image digest and attestation required: false type: string default: NHSDigital - signer_workflow: + source_ref: required: false type: string - default: ".github/workflows/build_multi_arch_image.yml@refs/heads/main" - signer_repo: - required: false - type: string - default: "NHSDigital/eps-devcontainers" + default: "" predicate_type: required: false type: string @@ -98,8 +94,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} OWNER: ${{ inputs.owner }} - SIGNER_WORKFLOW: ${{ inputs.signer_workflow }} - SIGNER_REPO: ${{ inputs.signer_repo }} + SOURCE_REF: ${{ inputs.source_ref }} PREDICATE_TYPE: ${{ inputs.predicate_type }} PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }} run: | @@ -107,13 +102,10 @@ jobs: args=("oci://${PINNED_IMAGE}" "--owner" "$OWNER" "--predicate-type" "$PREDICATE_TYPE") - if [[ -n "$SIGNER_WORKFLOW" ]]; then - args+=("--signer-workflow" "$SIGNER_WORKFLOW") + if [[ -n "$SOURCE_REF" ]]; then + args+=("--source-ref" "$SOURCE_REF") fi - if [[ -n "$SIGNER_REPO" ]]; then - args+=("--signer-repo" "$SIGNER_REPO") - fi GH_FORCE_TTY=120 gh attestation verify "${args[@]}" 2>&1 echo "Verified attestation for ${PINNED_IMAGE}" From 7924143dffdbc5a2791a12688637a991d743bbff Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:43:26 +0000 Subject: [PATCH 12/18] test failure --- .github/workflows/quality-checks-devcontainer.yml | 1 + .github/workflows/verify_attestation.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index 358a7bb..f6ac754 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -30,6 +30,7 @@ jobs: uses: ./.github/workflows/verify_attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" + source_ref: "refs/heads/main" quality_checks: runs-on: ubuntu-22.04 needs: verify_attestation diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml index 0c3ba01..5af88f6 100644 --- a/.github/workflows/verify_attestation.yml +++ b/.github/workflows/verify_attestation.yml @@ -53,7 +53,7 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Resolve digest and validate expected value + - name: Resolve digest id: resolve shell: bash env: From fc2497b48f461b2cef6cd45cf32d993f89e241fe Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:52:35 +0000 Subject: [PATCH 13/18] use published from main --- .github/workflows/quality-checks-devcontainer.yml | 1 - .github/workflows/verify_attestation.yml | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index f6ac754..358a7bb 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -30,7 +30,6 @@ jobs: uses: ./.github/workflows/verify_attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" - source_ref: "refs/heads/main" quality_checks: runs-on: ubuntu-22.04 needs: verify_attestation diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify_attestation.yml index 5af88f6..55a3ab3 100644 --- a/.github/workflows/verify_attestation.yml +++ b/.github/workflows/verify_attestation.yml @@ -18,10 +18,10 @@ name: Verify image digest and attestation required: false type: string default: NHSDigital - source_ref: + use_published_from_main_image: required: false - type: string - default: "" + type: boolean + default: true predicate_type: required: false type: string @@ -94,7 +94,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} OWNER: ${{ inputs.owner }} - SOURCE_REF: ${{ inputs.source_ref }} + USE_PUBLISHED_FROM_MAIN_IMAGE: ${{ inputs.use_published_from_main_image }} PREDICATE_TYPE: ${{ inputs.predicate_type }} PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }} run: | @@ -102,8 +102,8 @@ jobs: args=("oci://${PINNED_IMAGE}" "--owner" "$OWNER" "--predicate-type" "$PREDICATE_TYPE") - if [[ -n "$SOURCE_REF" ]]; then - args+=("--source-ref" "$SOURCE_REF") + if [[ "$USE_PUBLISHED_FROM_MAIN_IMAGE" == "true" ]]; then + args+=("--source-ref" "refs/heads/main") fi From 0006560e059e0d9e871d55614c8c66abd0fb0a03 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:54:20 +0000 Subject: [PATCH 14/18] default to non main image for qc --- .github/workflows/quality-checks-devcontainer.yml | 1 + .github/workflows/tag-release-devcontainer.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index 358a7bb..6393155 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -30,6 +30,7 @@ jobs: uses: ./.github/workflows/verify_attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" + use_published_from_main_image: false quality_checks: runs-on: ubuntu-22.04 needs: verify_attestation diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml index 13df746..07ab08e 100644 --- a/.github/workflows/tag-release-devcontainer.yml +++ b/.github/workflows/tag-release-devcontainer.yml @@ -63,6 +63,7 @@ jobs: uses: ./.github/workflows/verify_attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" + use_published_from_main_image: false tag_release: runs-on: ubuntu-22.04 needs: verify_attestation From bc8f27e22506c3ed06174b2113fc6ffafabe60a0 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 07:57:51 +0000 Subject: [PATCH 15/18] verify from main --- .github/workflows/pull_request.yml | 1 + .github/workflows/release.yml | 1 + .github/workflows/tag-release-devcontainer.yml | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ff1a7a6..29e7093 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -56,4 +56,5 @@ jobs: runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: ${{ github.event.pull_request.head.ref }} tag_format: ${{ needs.get_config_values.outputs.tag_format }} + use_published_from_main_image: false secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e7957d2..ba9104a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,4 +44,5 @@ jobs: runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: main tag_format: ${{ needs.get_config_values.outputs.tag_format }} + use_published_from_main_image: true secrets: inherit diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml index 07ab08e..33cd12d 100644 --- a/.github/workflows/tag-release-devcontainer.yml +++ b/.github/workflows/tag-release-devcontainer.yml @@ -45,6 +45,9 @@ on: description: "An repository for the extra artifact" required: false type: string + use_published_from_main_image: + required: true + type: boolean outputs: version_tag: value: ${{ jobs.tag_release.outputs.version_tag }} @@ -63,7 +66,7 @@ jobs: uses: ./.github/workflows/verify_attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" - use_published_from_main_image: false + use_published_from_main_image: ${{ inputs.use_published_from_main_image }} tag_release: runs-on: ubuntu-22.04 needs: verify_attestation From 0ee2ecadd5c6951685e4ae3bbc2818c5028bf280 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 08:08:28 +0000 Subject: [PATCH 16/18] verify attestations --- .github/workflows/quality-checks-devcontainer.yml | 2 +- .github/workflows/tag-release-devcontainer.yml | 2 +- .../{verify_attestation.yml => verify-attestation.yml} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{verify_attestation.yml => verify-attestation.yml} (100%) diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index 6393155..b80b33a 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -27,7 +27,7 @@ on: jobs: verify_attestation: - uses: ./.github/workflows/verify_attestation.yml + uses: ./.github/workflows/verify-attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" use_published_from_main_image: false diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml index 33cd12d..474e68a 100644 --- a/.github/workflows/tag-release-devcontainer.yml +++ b/.github/workflows/tag-release-devcontainer.yml @@ -63,7 +63,7 @@ on: description: "NPM token to publish packages" jobs: verify_attestation: - uses: ./.github/workflows/verify_attestation.yml + uses: ./.github/workflows/verify-attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" use_published_from_main_image: ${{ inputs.use_published_from_main_image }} diff --git a/.github/workflows/verify_attestation.yml b/.github/workflows/verify-attestation.yml similarity index 100% rename from .github/workflows/verify_attestation.yml rename to .github/workflows/verify-attestation.yml From c9f328a67c7e2f7a92dfc481748076395c52dc3b Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 08:22:13 +0000 Subject: [PATCH 17/18] updates --- .github/workflows/pull_request.yml | 2 +- .../workflows/quality-checks-devcontainer.yml | 2 +- .github/workflows/release.yml | 2 +- .../workflows/tag-release-devcontainer.yml | 4 +- .github/workflows/verify-attestation.yml | 6 +- README.md | 133 +++++++++++++----- 6 files changed, 103 insertions(+), 46 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 29e7093..cdb4ec7 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -56,5 +56,5 @@ jobs: runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: ${{ github.event.pull_request.head.ref }} tag_format: ${{ needs.get_config_values.outputs.tag_format }} - use_published_from_main_image: false + verify_published_from_main_image: false secrets: inherit diff --git a/.github/workflows/quality-checks-devcontainer.yml b/.github/workflows/quality-checks-devcontainer.yml index b80b33a..6f8afad 100644 --- a/.github/workflows/quality-checks-devcontainer.yml +++ b/.github/workflows/quality-checks-devcontainer.yml @@ -30,7 +30,7 @@ jobs: uses: ./.github/workflows/verify-attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" - use_published_from_main_image: false + verify_published_from_main_image: false quality_checks: runs-on: ubuntu-22.04 needs: verify_attestation diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba9104a..5aeaab9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -44,5 +44,5 @@ jobs: runtime_docker_image: "${{ needs.get_config_values.outputs.devcontainer_image }}:githubactions-${{ needs.get_config_values.outputs.devcontainer_version }}" branch_name: main tag_format: ${{ needs.get_config_values.outputs.tag_format }} - use_published_from_main_image: true + verify_published_from_main_image: true secrets: inherit diff --git a/.github/workflows/tag-release-devcontainer.yml b/.github/workflows/tag-release-devcontainer.yml index 474e68a..ba93091 100644 --- a/.github/workflows/tag-release-devcontainer.yml +++ b/.github/workflows/tag-release-devcontainer.yml @@ -45,7 +45,7 @@ on: description: "An repository for the extra artifact" required: false type: string - use_published_from_main_image: + verify_published_from_main_image: required: true type: boolean outputs: @@ -66,7 +66,7 @@ jobs: uses: ./.github/workflows/verify-attestation.yml with: runtime_docker_image: "${{ inputs.runtime_docker_image }}" - use_published_from_main_image: ${{ inputs.use_published_from_main_image }} + verify_published_from_main_image: ${{ inputs.verify_published_from_main_image }} tag_release: runs-on: ubuntu-22.04 needs: verify_attestation diff --git a/.github/workflows/verify-attestation.yml b/.github/workflows/verify-attestation.yml index 55a3ab3..a996e34 100644 --- a/.github/workflows/verify-attestation.yml +++ b/.github/workflows/verify-attestation.yml @@ -18,7 +18,7 @@ name: Verify image digest and attestation required: false type: string default: NHSDigital - use_published_from_main_image: + verify_published_from_main_image: required: false type: boolean default: true @@ -94,7 +94,7 @@ jobs: env: GH_TOKEN: ${{ github.token }} OWNER: ${{ inputs.owner }} - USE_PUBLISHED_FROM_MAIN_IMAGE: ${{ inputs.use_published_from_main_image }} + VERIFY_PUBLISHED_FROM_MAIN_IMAGE: ${{ inputs.verify_published_from_main_image }} PREDICATE_TYPE: ${{ inputs.predicate_type }} PINNED_IMAGE: ${{ steps.resolve.outputs.pinned_image }} run: | @@ -102,7 +102,7 @@ jobs: args=("oci://${PINNED_IMAGE}" "--owner" "$OWNER" "--predicate-type" "$PREDICATE_TYPE") - if [[ "$USE_PUBLISHED_FROM_MAIN_IMAGE" == "true" ]]; then + if [[ "$VERIFY_PUBLISHED_FROM_MAIN_IMAGE" == "true" ]]; then args+=("--source-ref" "refs/heads/main") fi diff --git a/README.md b/README.md index 3aa7ea2..3413b65 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,25 @@ A collection of common workflows used by other EPS repositories The workflows that are available to use are -## Adding exclusions to trivy scanning -The quality checks job uses trivy to scan for vulnerabilities. +## Workflow Index + +- [Combine Dependabot PRs](#combine-dependabot-prs) +- [Dependabot Auto Approve and Merge](#dependabot-auto-approve-and-merge) +- [PR Title Check](#pr-title-check) +- [Quality Checks](#quality-checks) +- [Quality Checks - Dev Container Version](#quality-checks---dev-container-version) +- [Verify Image Digest and Attestation](#verify-image-digest-and-attestation) +- [Tag Release](#tag-release) +- [Tag Release - Devcontainer Version](#tag-release---devcontainer-version) + +## Other Docs + +- [Adding Exclusions to Trivy Scanning](#adding-exclusions-to-trivy-scanning) +- [Secret Scanning Docker](#secret-scanning-docker) +- [Run All Releases](#run-all-releases) + +## Adding Exclusions to Trivy Scanning +The quality checks job uses Trivy to scan for vulnerabilities. There may be times you want to add an exclusion for a known vulnerability that we are happy to accept To do this, in the calling repo, add trivy.yaml with this content ``` @@ -22,7 +39,7 @@ vulnerabilities: ``` See https://trivy.dev/docs/latest/configuration/filtering/#trivyignoreyaml for more details -## combine dependabot prs +## Combine Dependabot PRs This workflow can be called to combine multiple open Dependabot PRs into a single PR. @@ -68,7 +85,7 @@ jobs: ignoreLabel: ${{ github.event.inputs.ignoreLabel }} ``` -## dependabot auto approve and merge +## Dependabot Auto Approve and Merge This workflow can be called to automatically approve and merge Dependabot PRs as part of the pull request workflow. #### Requirements @@ -91,7 +108,7 @@ jobs: AUTOMERGE_APP_ID: ${{ secrets.AUTOMERGE_APP_ID }} AUTOMERGE_PEM: ${{ secrets.AUTOMERGE_PEM }} ``` -## pr title check +## PR Title Check This workflow checks that all pull requests have a title that matches the required format, and comments on the PR with a link to the relevant ticket if a ticket reference is found. #### Example @@ -110,7 +127,7 @@ jobs: uses: NHSDigital/eps-common-workflows/.github/workflows/pr_title_check.yml@f5c8313a10855d0cc911db6a9cd666494c00045a ``` -## quality checks +## Quality Checks This workflow runs common quality checks. To use this, you must have the following Makefile targets defined - install @@ -123,15 +140,15 @@ To use this, you must have the following Makefile targets defined #### Inputs -- `install_java`: Whether to install java or not -- `run_sonar`: Whether to run sonar checks or not. +- `install_java`: Whether to install Java or not +- `run_sonar`: Whether to run Sonar checks or not. - `asdfVersion`: Override the version of asdf to install. -- `reinstall_poetry`: If you are using this from a primarily python based project, you should set this to true to force a poetry reinstallation after python is installed -- `run_docker_scan`: whether to run a scan of docker images -- `docker_images`: csv list of docker images to scan. These must match images produced by make docker-build +- `reinstall_poetry`: If you are using this from a primarily Python based project, you should set this to true to force a poetry reinstallation after Python is installed +- `run_docker_scan`: whether to run a scan of Docker images +- `docker_images`: csv list of Docker images to scan. These must match images produced by make docker-build #### Secret Inputs -- `SONAR_TOKEN`: Token used to authenticate to sonar +- `SONAR_TOKEN`: Token used to authenticate to Sonar #### Outputs @@ -157,17 +174,17 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ``` -## quality checks - dev container version +## Quality Checks - Dev Container Version This workflow runs common quality checks using a prebuilt devcontainer (https://github.com/NHSDigital/eps-devcontainers). To use this, you must have overridden any common makefile targets described in https://github.com/NHSDigital/eps-devcontainers?tab=readme-ov-file#common-makefile-targets #### Inputs -- `run_sonar`: Whether to run sonar checks or not. -- `run_docker_scan`: whether to run a scan of docker images -- `docker_images`: csv list of docker images to scan. These must match images produced by make docker-build -- `runtime_docker_image`: the docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers +- `run_sonar`: Whether to run Sonar checks or not. +- `run_docker_scan`: whether to run a scan of Docker images +- `docker_images`: csv list of Docker images to scan. These must match images produced by make docker-build +- `runtime_docker_image`: the Docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers #### Secret Inputs -- `SONAR_TOKEN`: Token used to authenticate to sonar +- `SONAR_TOKEN`: Token used to authenticate to Sonar #### Outputs @@ -195,9 +212,48 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} ``` +## Verify Image Digest and Attestation +This workflow resolves an image reference to a pinned digest and verifies GitHub artifact attestation for that image. + +#### Inputs + +- `runtime_docker_image`: Image reference as `name:tag` (for example `node_24_python_3_12:v1.2.3`) or a fully qualified image reference. +- `registry`: Container registry host. Default: `ghcr.io` +- `namespace`: Image namespace/repository prefix. Default: `nhsdigital/eps-devcontainers` +- `owner`: GitHub owner used by `gh attestation verify --owner`. Default: `NHSDigital` +- `verify_published_from_main_image`: If true, verifies attestations published from `refs/heads/main`. Default: `true` +- `predicate_type`: Attestation predicate type. Default: `https://slsa.dev/provenance/v1` + +#### Outputs + +- `pinned_image`: Fully-qualified digest-pinned image reference. +- `resolved_digest`: Resolved digest for the supplied image reference. + +#### Example + +To use this workflow in your repository, call it from another workflow file: + +```yaml +name: Verify Devcontainer Image + +on: + workflow_dispatch: + +jobs: + verify_attestation: + uses: NHSDigital/eps-common-workflows/.github/workflows/verify-attestation.yml@f5c8313a10855d0cc911db6a9cd666494c00045a + with: + runtime_docker_image: node_24_python_3_12:githubactions-v1.2.3 + registry: ghcr.io + namespace: nhsdigital/eps-devcontainers + owner: NHSDigital + verify_published_from_main_image: true + predicate_type: https://slsa.dev/provenance/v1 +``` + -## tag release -This workflow uses the semantic-release npm package to generate a new version tag, changelog, and github release for a repo. +## Tag Release +This workflow uses the semantic-release npm package to generate a new version tag, changelog, and GitHub release for a repo. #### Inputs @@ -211,7 +267,7 @@ This workflow uses the semantic-release npm package to generate a new version ta #### Outputs - `version_tag`: The version tag created by semantic-release. -- `change_set_version`: A timestamped string that con be used for creating changesets. +- `change_set_version`: A timestamped string that can be used for creating changesets. #### Example @@ -234,14 +290,14 @@ jobs: publish_package: false ``` -## tag release - devcontainer version -This workflow uses the semantic-release npm package to generate a new version tag, changelog, and github release for a repo. -*The devcontainer MUST have node installed* +## Tag Release - Devcontainer Version +This workflow uses the semantic-release npm package to generate a new version tag, changelog, and GitHub release for a repo. +*The devcontainer MUST have Node installed* #### Inputs - `dry_run`: Whether to run in dry_run mode (do not create tags) or not - `branch_name`: The branch name to base the release on -- `runtime_docker_image`: the docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers +- `runtime_docker_image`: the Docker image to run everything on. This should just be the image name and tag pushed to https://github.com/NHSDigital/eps-devcontainers - `publish_packages`: comma separated list of package folders to publish to an npm registry - `tagFormat`: Default `v\\${version}`. A template for the version tag. - `main_branch`: The branch to use for publishing. Defaults to main @@ -249,11 +305,12 @@ This workflow uses the semantic-release npm package to generate a new version ta - `extra_artifact_id`: optional param of the extra artifact id to include in the release - `extra_artifact_run_id`: optional param of the run id to download the extra artifact id to include in the release - `extra_artifact_repository` optional param to indicate which repo the run to download the artifact was from +- `verify_published_from_main_image` indicates if we should verify the image was published from main branch in eps-devcontainers #### Outputs - `version_tag`: The version tag created by semantic-release. -- `change_set_version`: A timestamped string that con be used for creating changesets. +- `change_set_version`: A timestamped string that can be used for creating changesets. #### Example @@ -278,9 +335,9 @@ jobs: ``` -## Secret scanning docker +## Secret Scanning Docker -The secret scanning also has a dockerfile, which can be run against a repo in order to scan it manually (or as part of pre-commit hooks). This can be done like so: +The secret scanning also has a Dockerfile, which can be run against a repo in order to scan it manually (or as part of pre-commit hooks). This can be done like so: ```bash docker build -f https://raw.githubusercontent.com/NHSDigital/eps-workflow-quality-checks/refs/tags/v3.0.0/dockerfiles/nhsd-git-secrets.dockerfile -t git-secrets . docker run -v /path/to/repo:/src git-secrets --scan-history . @@ -302,7 +359,7 @@ In order to enable the pre-commit hook for secret scanning (to prevent developer } ``` -And the this pre-commit hook to the `.pre-commit-config.yaml` file: +And add this pre-commit hook to the `.pre-commit-config.yaml` file: ```yaml repos: - repo: local @@ -317,28 +374,28 @@ repos: language: system ``` -## Run all releases +## Run All Releases There are some scripts that can be used to trigger releases for all our repos. It is invoked by running `./scripts/run_all_release.sh`. -This first authenticates to github using github cli tools to get a valid github token. +This first authenticates to GitHub using GitHub CLI tools to get a valid GitHub token. It then has an array of repos which it loops through asking for confirmation if you want to run deployment for it. -For any that you have answered yes to, it then calls the python script `scripts/trigger_release.py`. +For any that you have answered yes to, it then calls the Python script `scripts/trigger_release.py`. -The python script will trigger the release.yml workflow for that repo and monitor the the run for it. +The Python script will trigger the release.yml workflow for that repo and monitor the run for it. When it reaches one of the steps release_qa, release_ref, release_int it will approve release to that environment. -Once the run reaches release_prod step, the python script will exit. -The python script will also exit if the github run fails, or is cancelled at any step, or there is an unexpected response from github (eg user does not have permission to approve a deployment). -When the python script finishes, it logs the run url, the tag and summary of what happened. +Once the run reaches release_prod step, the Python script will exit. +The Python script will also exit if the GitHub run fails, or is cancelled at any step, or there is an unexpected response from GitHub (eg user does not have permission to approve a deployment). +When the Python script finishes, it logs the run URL, the tag and summary of what happened. Python logs go to the console, and to a timestamped file in the logs folder. When all runs of the python script have finished then the shell script exits showing a summary of failed and successful runs. -If a run fails on a step BEFORE the tag_release step, and the failure is transient (eg quality checks fails installing dependencies due to npm being down) then the whole release workflow can be rerun - either via this script or using the github website. +If a run fails on a step BEFORE the tag_release step, and the failure is transient (eg quality checks fails installing dependencies due to npm being down) then the whole release workflow can be rerun - either via this script or using the GitHub website. -If a run fails on a step AFTER the tag_release step, and the failure is transient (eg regression tests failure) then that failing step can just be re-run manually via the github website. +If a run fails on a step AFTER the tag_release step, and the failure is transient (eg regression tests failure) then that failing step can just be re-run manually via the GitHub website. If a run fails due to a code or cloudformation/cdk issue, then a new pull request should be created to fix this, merged to main, and a new release triggered. From 66ecdfa0200ec759a8313fca7b994199d40e2442 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 19 Feb 2026 08:56:00 +0000 Subject: [PATCH 18/18] use new tag --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 601f388..29bb180 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "args": { "DOCKER_GID": "${env:DOCKER_GID:}", "IMAGE_NAME": "node_24_python_3_14", - "IMAGE_VERSION": "pr-23-d823049", + "IMAGE_VERSION": "v1.0.6", "USER_UID": "${localEnv:USER_ID:}", "USER_GID": "${localEnv:GROUP_ID:}" },