From 015221bfa0f22b95bdec3b3f4df45b8a571e1985 Mon Sep 17 00:00:00 2001 From: "eps-create-pull-request[bot]" <270920461+eps-create-pull-request[bot]@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:45:08 +0000 Subject: [PATCH 1/3] Update devcontainer image version to v1.2.0 --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 29bb1804..d19c9683 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.6", + "IMAGE_VERSION": "v1.2.0", "USER_UID": "${localEnv:USER_ID:}", "USER_GID": "${localEnv:GROUP_ID:}" }, From ebf883a25f843678fa95e7e92c1ce2395964b4e7 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Fri, 27 Mar 2026 12:56:47 +0000 Subject: [PATCH 2/3] remove .tool-version --- .tool-versions | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions deleted file mode 100644 index d3c826ac..00000000 --- a/.tool-versions +++ /dev/null @@ -1,5 +0,0 @@ -nodejs 24.13.0 -actionlint 1.7.10 -shellcheck 0.11.0 -python 3.14.3 -poetry 2.3.2 From 59556ce4a0e712fbc04e7207bd30c0a95fe00f5f Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Fri, 27 Mar 2026 12:57:42 +0000 Subject: [PATCH 3/3] remove all trace of .tool-versions --- .github/workflows/quality-checks.yml | 711 --------------------------- .github/workflows/tag-release.yml | 419 ---------------- 2 files changed, 1130 deletions(-) delete mode 100644 .github/workflows/quality-checks.yml delete mode 100644 .github/workflows/tag-release.yml diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml deleted file mode 100644 index 54d52b20..00000000 --- a/.github/workflows/quality-checks.yml +++ /dev/null @@ -1,711 +0,0 @@ -name: Quality Checks - -on: - workflow_call: - secrets: - SONAR_TOKEN: - required: false - inputs: - install_java: - type: boolean - description: "If true, the action will install java into the runner, separately from ASDF." - default: false - required: false - run_sonar: - type: boolean - description: Toggle to run sonar code analyis on this repository. - default: true - required: false - asdfVersion: - type: string - required: true - reinstall_poetry: - type: boolean - description: Toggle to reinstall poetry on top of python version installed by asdf. - default: 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 - -jobs: - quality_checks: - runs-on: ubuntu-22.04 - steps: - - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 - if: ${{ inputs.install_java }} - with: - java-version: "21" - distribution: "corretto" - - - &checkout - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - ref: ${{ env.BRANCH_NAME }} - fetch-depth: 0 - - # Must be done before anything installs, or it will check dependencies for secrets too. - - name: Ensure .gitallowed exists, for secret scanning - run: | - if [ ! -f ".gitallowed" ]; then - echo "Creating empty .gitallowed file" - touch .gitallowed - fi - echo "./nhsd-rules-deny.txt:10" >> .gitallowed - echo "Allowing the following regex patterns:" - cat .gitallowed - - - name: Install git-secrets - run: | - sudo apt-get update - sudo apt-get install -y git curl - git clone https://github.com/awslabs/git-secrets.git /tmp/git-secrets - cd /tmp/git-secrets - sudo make install - - - name: Download regex patterns - run: | - curl -L https://raw.githubusercontent.com/NHSDigital/software-engineering-quality-framework/main/tools/nhsd-git-secrets/nhsd-rules-deny.txt -o nhsd-rules-deny.txt - - - name: Configure git-secrets - run: | - git-secrets --register-aws - git-secrets --add-provider -- cat nhsd-rules-deny.txt - - - name: Run secrets scan - run: | - git-secrets --scan-history . - - # using git commit sha for version of action to ensure we have stable version - - &install_asdf - name: Install asdf - uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ inputs.asdfVersion }} - - - &cache_asdf - name: Cache asdf - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb - with: - path: ~/.asdf - key: ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }}-${{ inputs.asdfVersion }} - - - &install_asdf_deps - name: Install asdf dependencies in .tool-versions - uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ inputs.asdfVersion }} - env: - PYTHON_CONFIGURE_OPTS: --enable-shared - - - &reinstall_poetry - name: Reinstall poetry - if: ${{ inputs.reinstall_poetry }} - run: | - poetry_tool_version=$(cat .tool-versions | grep poetry) - poetry_version=${poetry_tool_version//"poetry "} - asdf uninstall poetry "$poetry_version" - asdf install poetry - - - &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: 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 - touch trivy.yaml - - name: Update trivy config to include dev dependencies - uses: mikefarah/yq@0f4fb8d35ec1a939d78dd6862f494d19ec589f19 - with: - cmd: yq -i '.pkg.include-dev-deps = true' 'trivy.yaml' - - name: convert python dependencies to requirements.txt - if: ${{ steps.check_languages.outputs.uses_poetry == 'true' }} - run: | - POETRY_VERSION=$(poetry --version | awk '{print $3}') - - if [[ "$(printf '%s\n' "2.0.0" "$POETRY_VERSION" "3.0.0" | sort -V | head -n1)" == "2.0.0" ]] \ - && [[ "$(printf '%s\n' "$POETRY_VERSION" "3.0.0" | sort -V | head -n1)" == "$POETRY_VERSION" ]]; then - echo "Poetry version $POETRY_VERSION is >=2.0.0 and <3.0.0 - installing plugin-export" - poetry self add poetry-plugin-export - else - echo "Poetry version $POETRY_VERSION is outside the required range so not installing plugin-export" - fi - poetry export -f requirements.txt --with dev --without-hashes --output=requirements.txt - - name: download go dependencies - if: ${{ steps.check_languages.outputs.uses_go == 'true' }} - run: | - cd src - go mod vendor - - name: Check licenses - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 - with: - scan-type: "fs" - scan-ref: "." - severity: "CRITICAL,HIGH" - scanners: "license" - format: "table" - output: "license_scan.txt" - exit-code: "1" - list-all-pkgs: "false" - trivy-config: trivy.yaml - env: - VIRTUAL_ENV: "./.venv/" - - name: remove requirements.txt - if: ${{ steps.check_languages.outputs.uses_poetry == 'true' }} - run: | - rm -f requirements.txt - - name: clean go dependencies - if: ${{ steps.check_languages.outputs.uses_go == 'true' }} - run: | - cd src - rm -rf vendor - - name: Show license scan output - if: always() - run: | - if [ -f license_scan.txt ]; then - cat license_scan.txt - fi - - name: Run code lint - run: make lint - - - name: Run ShellCheck - uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 - with: - ignore_paths: >- - *test* - .venv - node_modules - .git - - - name: Run unit tests - run: make test - - name: Generate SBOM - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 - with: - scan-type: "fs" - scan-ref: "." - scanners: "vuln" - format: "cyclonedx" - output: "sbom.cdx.json" - exit-code: "0" - trivy-config: trivy.yaml - - name: Upload sbom - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f - with: - name: sbom.cdx.json - path: sbom.cdx.json - - - name: Check python vulnerabilities - if: ${{ always() && steps.check_languages.outputs.uses_poetry == 'true'}} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 - with: - scan-type: "fs" - skip-files: "**/package-lock.json,**/go.mod,**/pom.xml" - scan-ref: "." - severity: "CRITICAL,HIGH" - scanners: "vuln" - format: "table" - output: "dependency_results_python.txt" - exit-code: "1" - trivy-config: trivy.yaml - - name: Check node vulnerabilities - if: ${{ always() && steps.check_languages.outputs.uses_node == 'true' }} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 - with: - scan-type: "fs" - skip-files: "**/poetry.lock,**/go.mod,**/pom.xml" - scan-ref: "." - severity: "CRITICAL,HIGH" - scanners: "vuln" - format: "table" - output: "dependency_results_node.txt" - exit-code: "1" - trivy-config: trivy.yaml - - name: Check go vulnerabilities - if: ${{ always() && steps.check_languages.outputs.uses_go == 'true' }} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 - with: - scan-type: "fs" - skip-files: "**/poetry.lock,**/package-lock.json,**/pom.xml" - scan-ref: "." - severity: "CRITICAL,HIGH" - scanners: "vuln" - format: "table" - output: "dependency_results_go.txt" - exit-code: "1" - - name: Check java vulnerabilities - if: ${{ always() && steps.check_languages.outputs.uses_java == 'true' }} - continue-on-error: ${{ github.actor == 'dependabot[bot]' }} - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 - with: - scan-type: "fs" - skip-files: "**/poetry.lock,**/package-lock.json,**/go.mod" - scan-ref: "." - severity: "CRITICAL,HIGH" - scanners: "vuln" - format: "table" - output: "dependency_results_java.txt" - exit-code: "1" - trivy-config: trivy.yaml - - name: Show vulnerability output - if: always() - run: | - if [ -f dependency_results_python.txt ]; then - cat dependency_results_python.txt - fi - if [ -f dependency_results_node.txt ]; then - cat dependency_results_node.txt - fi - if [ -f dependency_results_java.txt ]; then - cat dependency_results_java.txt - fi - if [ -f dependency_results_go.txt ]; then - cat 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: - outputs: - docker_images: ${{ steps.normalized_docker_images.outputs.images }} - runs-on: ubuntu-22.04 - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - ref: ${{ env.BRANCH_NAME }} - fetch-depth: 0 - - 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 - if: ${{ inputs.run_docker_scan == true }} - strategy: - matrix: - docker_image: ${{ fromJson(needs.get_docker_images_to_scan.outputs.docker_images) }} - steps: - - name: Checkout code - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - ref: ${{ env.BRANCH_NAME }} - fetch-depth: 0 - # using git commit sha for version of action to ensure we have stable version - - name: Install asdf - uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ inputs.asdfVersion }} - - - name: Cache asdf - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb - with: - path: | - ~/.asdf - key: ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }}-${{ inputs.asdfVersion }} - restore-keys: | - ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }}-${{ inputs.asdfVersion }} - - - name: Install asdf dependencies in .tool-versions - uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ inputs.asdfVersion }} - env: - PYTHON_CONFIGURE_OPTS: --enable-shared - - - name: Reinstall poetry - if: ${{ inputs.reinstall_poetry }} - run: | - poetry_tool_version=$(cat .tool-versions | grep poetry) - poetry_version=${poetry_tool_version//"poetry "} - asdf uninstall poetry "$poetry_version" - asdf install poetry - - - 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 - - - name: Cache npm dependencies - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb - with: - path: ./node_modules - key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} - restore-keys: | - ${{ runner.os }}-node- - - - name: make install - run: | - make install - - - name: Build docker images - if: ${{ inputs.run_docker_scan == true }} - run: | - make docker-build - - - name: Check docker vulnerabilities - uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 - with: - scan-type: "image" - image-ref: ${{ matrix.docker_image }} - severity: "CRITICAL,HIGH" - scanners: "vuln" - vuln-type: "os,library" - format: "table" - output: "dependency_results_docker.txt" - exit-code: "1" - trivy-config: trivy.yaml - - - name: Show docker vulnerability output - if: always() - run: | - echo "Scan output for ${{ matrix.docker_image }}" - if [ -f dependency_results_docker.txt ]; then - cat dependency_results_docker.txt - fi - - IaC-validation: - runs-on: ubuntu-22.04 - steps: - - *checkout - - *install_asdf - - *cache_asdf - - *install_asdf_deps - - *reinstall_poetry - - - 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: | - pip install cfn-lint - cfn-lint -I "cloudformation/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print }' - cfn-lint -I "SAMtemplates/**/*.y*ml" 2>&1 | awk '/Run scan/ { print } /^[EW][0-9]/ { print; getline; print }' - - - *cache_npm - - *setup_npmrc - - - name: make install NodeJS - if: steps.check_cdk.outputs.cdk_exists == 'true' - run: | - make install-node && make compile - - - name: Run cdk-synth - if: steps.check_cdk.outputs.cdk_exists == 'true' - run: | - make cdk-synth - - - name: Install AWS SAM CLI - if: steps.check_sam_templates.outputs.sam_exists == 'true' - run: | - pip install aws-sam-cli - - - name: Init cfn-guard - run: | - #!/usr/bin/env bash - set -eou pipefail - - rm -rf /tmp/ruleset - rm -rf cfn_guard_output - - wget -O /tmp/ruleset.zip https://github.com/aws-cloudformation/aws-guard-rules-registry/releases/download/1.0.2/ruleset-build-v1.0.2.zip >/dev/null 2>&1 - unzip /tmp/ruleset.zip -d /tmp/ruleset/ >/dev/null 2>&1 - - curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh | sh >/dev/null 2>&1 - - mkdir -p cfn_guard_output - - - name: Run cfn-guard script for sam templates - if: steps.check_sam_templates.outputs.sam_exists == 'true' - run: | - #!/usr/bin/env bash - set -eou pipefail - - declare -a rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar") - for ruleset in "${rulesets[@]}" - do - while IFS= read -r -d '' file - do - echo "checking SAM template $file with ruleset $ruleset" - mkdir -p "$(dirname cfn_guard_output/"$file")" - - # Transform the SAM template to CloudFormation and then run through cfn-guard - SAM_OUTPUT=$(sam validate -t "$file" --region eu-west-2 --debug 2>&1 | \ - grep -Pazo '(?s)AWSTemplateFormatVersion.*\n\/' | tr -d '\0') - echo "${SAM_OUTPUT::-1}" | ~/.guard/bin/cfn-guard validate \ - --rules "/tmp/ruleset/output/$ruleset.guard" \ - --show-summary fail \ - > "cfn_guard_output/${file}_${ruleset}.txt" - - done < <(find ./SAMtemplates -name '*.y*ml' -print0) - done - - - name: Run cfn-guard script for cloudformation templates - if: steps.check_cf_templates.outputs.cf_exists == 'true' - run: | - #!/usr/bin/env bash - - declare -a rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar") - for ruleset in "${rulesets[@]}" - do - echo "Checking all templates in cloudformation folder with ruleset $ruleset" - - ~/.guard/bin/cfn-guard validate \ - --data cloudformation \ - --rules "/tmp/ruleset/output/$ruleset.guard" \ - --show-summary fail \ - > "cfn_guard_output/cloudformation_$ruleset.txt" - done - - - name: Run cfn-guard script for cdk templates - if: steps.check_cdk.outputs.cdk_exists == 'true' - run: | - #!/usr/bin/env bash - - declare -a rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar") - for ruleset in "${rulesets[@]}" - do - echo "Checking all templates in cdk.out folder with ruleset $ruleset" - - ~/.guard/bin/cfn-guard validate \ - --data cdk.out \ - --rules "/tmp/ruleset/output/$ruleset.guard" \ - --show-summary fail \ - > "cfn_guard_output/cdk.out_$ruleset.txt" - done - - - name: Download terraform plans - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c - 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: | - #!/usr/bin/env bash - - declare -a rulesets=("ncsc" "ncsc-cafv3" "wa-Reliability-Pillar" "wa-Security-Pillar") - for ruleset in "${rulesets[@]}" - do - echo "Checking terraform plans with ruleset $ruleset" - - ~/.guard/bin/cfn-guard validate \ - --data terraform_plans \ - --rules "/tmp/ruleset/output/$ruleset.guard" \ - --show-summary fail \ - > "cfn_guard_output/terraform_plans_$ruleset.txt" - done - - - name: Show cfn-guard output - if: failure() - run: find cfn_guard_output -type f -print0 | xargs -0 cat - - - name: Upload cfn_guard_output - if: failure() - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f - with: - name: cfn_guard_output - path: cfn_guard_output diff --git a/.github/workflows/tag-release.yml b/.github/workflows/tag-release.yml deleted file mode 100644 index 442cd01d..00000000 --- a/.github/workflows/tag-release.yml +++ /dev/null @@ -1,419 +0,0 @@ -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 - asdfVersion: - type: string - required: false - default: "0.18.0" - 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 - pypi_publish: - description: "Whether to publish to PyPI" - required: false - type: boolean - default: false - 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" - PYPI_TOKEN: - required: false - description: "PyPI token to publish packages" -jobs: - install_semantic_release: - # Install asdf - # Use npm install -g to install semantic release and plugins globally - ## These are installed in the asdf npm version - # Bundle .asdf and upload as artifact - runs-on: ubuntu-22.04 - steps: - - name: Checkout semantic-release workflow - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - repository: NHSDigital/eps-common-workflows - sparse-checkout-cone-mode: false - sparse-checkout: | - package.json - package-lock.json - .tool-versions - release.config.cjs - releaseNotesTemplates/commit.hbs - - - name: Calculate asdf cache key - id: asdf_cache_key - run: | - base="${{ runner.os }}-asdf-${{ inputs.asdfVersion }}" - key="$base-${{ hashFiles('**/.tool-versions') }}" - - echo "base=$base" >> "$GITHUB_OUTPUT" - echo "key=$key" >> "$GITHUB_OUTPUT" - - - name: Calculate installed cache key - id: installed_cache_key - run: | - base="${{ steps.asdf_cache_key.outputs.key }}-npm" - key="$base-${{ hashFiles('**/package-lock.json') }}" - - echo "base=$base" >> "$GITHUB_OUTPUT" - echo "key=$key" >> "$GITHUB_OUTPUT" - - - name: Calculate artifact cache key - id: artifact_cache_key - run: | - base="${{ steps.installed_cache_key.outputs.key }}" - key="$base-artifact" - - echo "base=$base" >> "$GITHUB_OUTPUT" - echo "key=$key" >> "$GITHUB_OUTPUT" - - - name: Cache asdf artifact - id: asdf_artifact_cache - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb - with: - path: asdf.tar.gz - key: ${{ steps.artifact_cache_key.outputs.key }} - restore-keys: ${{ steps.artifact_cache_key.outputs.base }}* - - - name: Cache asdf with installed npm packages - if: ${{ steps.asdf_artifact_cache.outputs.cache-hit != 'true' }} - id: installed_cache - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb - with: - path: ~/.asdf - key: ${{ steps.installed_cache_key.outputs.key }} - restore-keys: ${{ steps.installed_cache_key.outputs.base }}* - - - name: Cache asdf - if: ${{ steps.installed_cache.outcome == 'success' && steps.installed_cache.outputs.cache-hit != 'true' }} - id: asdf_cache - uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb - with: - path: ~/.asdf - key: ${{ steps.asdf_cache_key.outputs.key }} - restore-keys: | - ${{ steps.asdf_cache_key.outputs.base }}* - - - name: Install asdf dependencies in .tool-versions - if: ${{ steps.asdf_cache.outputs.cache-hit == 'false' || steps.asdf_cache.outcome != 'skipped' }} - uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ inputs.asdfVersion }} - - - name: Install semantic release dependencies globally - if: ${{ steps.installed_cache.outputs.cache-hit == 'false' || steps.installed_cache.outcome != 'skipped' }} - run: | - dependencies="$(jq -r '.devDependencies | to_entries | map("\(.key)@\(.value)") | join(" ")' package.json)" - echo "Installing: $dependencies" - - # shellcheck disable=SC2086 - npm install --global $dependencies - - - name: Prepare asdf artifact - if: ${{ steps.asdf_artifact_cache.outputs.cache-hit != 'true' }} - run: | - tar -czf asdf.tar.gz -C "$HOME" .asdf - - - name: Upload asdf artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f - with: - name: asdf_artifact - path: asdf.tar.gz - overwrite: true - - - name: Prepare config artifact - run: | - mkdir -p config_artifact/releaseNotesTemplates - cp release.config.cjs config_artifact/ - cp releaseNotesTemplates/commit.hbs config_artifact/releaseNotesTemplates/ - - - name: Upload config artifact - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f - with: - name: config_artifact - path: config_artifact/ - overwrite: true - - tag_release: - needs: install_semantic_release - runs-on: ubuntu-22.04 - 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: Fetch asdf artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c - with: - name: asdf_artifact - - name: Install asdf - uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ inputs.asdfVersion }} - - - name: Extract asdf - run: tar -xzf asdf.tar.gz -C "$HOME" - - - name: Setup node from cache - run: | - # Get installed node version (trim whitespace with xargs) - NODE_VER=$(asdf list nodejs | head -n1 | xargs) - NODE_DIR="$HOME/.asdf/installs/nodejs/$NODE_VER" - - echo "Using Node $NODE_VER from cache at $NODE_DIR" - asdf set -u nodejs "$NODE_VER" - - # Prepend binaries to path - echo "$NODE_DIR/bin" >> "$GITHUB_PATH" - echo "$NODE_DIR/lib/node_modules/.bin" >> "$GITHUB_PATH" - - # # Set node path to resolve packages - # NODE_PATH="$NODE_DIR/lib/node_modules" - # echo "NODE_PATH=$NODE_PATH" >> $GITHUB_ENV - - - name: Clone calling repo - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd - with: - repository: ${{ github.repository }} - ref: ${{ github.sha }} - - - 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: Fetch semantic-release config - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c - with: - name: config_artifact - - - name: Cache asdf - if: ${{ inputs.publish_packages != '' || inputs.pypi_publish }} - uses: actions/cache@v5 - with: - path: | - ~/.asdf - key: ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }} - restore-keys: | - ${{ runner.os }}-asdf- - - - name: Install asdf dependencies in .tool-versions - if: ${{ inputs.publish_packages != '' || inputs.pypi_publish }} - uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47 - with: - asdf_version: ${{ inputs.asdfVersion }} - env: - PYTHON_CONFIGURE_OPTS: --enable-shared - - - 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@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c - 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 - npx semantic-release --dry-run --branches "${BRANCH_NAME}" --branch "${BRANCH_NAME}" --tag-format "${TAG_FORMAT}" > semantic-release-output.log - exit_code=0 - fi - # Dry run mode: use short git SHA and get next version for summary - VERSION_TAG=$(git rev-parse --short HEAD) - 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 - 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 [ -z "${NEXT_VERSION}" ] - 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 }} - PYPI_PUBLISH: ${{ inputs.pypi_publish }} - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - - - 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 }} - PYPI_PUBLISH: ${{ inputs.pypi_publish }} - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - 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"