From 43000b5851724134357d61ba48638ee123f80098 Mon Sep 17 00:00:00 2001 From: nhsd-rebecca-flynn <241736882+nhsd-rebecca-flynn@users.noreply.github.com> Date: Wed, 22 Apr 2026 10:46:17 +0100 Subject: [PATCH 1/4] [CDAPI-153]: Add PR number to OAS (#116) --- .github/actions/proxy/deploy-proxy/action.yaml | 12 ++++++++++++ .github/workflows/preview-env.yaml | 2 ++ 2 files changed, 14 insertions(+) diff --git a/.github/actions/proxy/deploy-proxy/action.yaml b/.github/actions/proxy/deploy-proxy/action.yaml index 9daf583..06a66c8 100644 --- a/.github/actions/proxy/deploy-proxy/action.yaml +++ b/.github/actions/proxy/deploy-proxy/action.yaml @@ -26,6 +26,10 @@ inputs: proxygen-environment: description: 'Proxygen environment' required: true + pr-number: + description: 'Pull request number. When provided, appends "(PR-)" to the API title in the proxy specification to distinguish PR-based proxies in the developer portal.' + required: false + default: "" runs: using: composite @@ -48,6 +52,14 @@ runs: yq eval '.x-nhsd-apim.target.url = env(TARGET_URL) | .x-nhsd-apim.target.security.secret = env(MTLS_SECRET_NAME)' -i /tmp/proxy-specification.yaml + - name: Append PR number to API title + if: ${{ inputs.pr-number != '' }} + env: + PR_NUMBER: "${{ inputs.pr-number }}" + shell: bash + run: | + yq eval '.info.title = .info.title + " (PR-" + env(PR_NUMBER) + ")"' -i /tmp/proxy-specification.yaml + - name: Deploy API proxy env: ENVIRONMENT: "${{ inputs.proxygen-environment }}" diff --git a/.github/workflows/preview-env.yaml b/.github/workflows/preview-env.yaml index ca21e57..f1638f0 100644 --- a/.github/workflows/preview-env.yaml +++ b/.github/workflows/preview-env.yaml @@ -594,6 +594,7 @@ jobs: proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }} proxygen-api-name: ${{ env.PROXYGEN_API_NAME }} proxygen-environment: ${{ env.PROXY_ENV}} + pr-number: ${{ github.event.pull_request.number }} - name: Deploy int preview API proxy if: github.event.action != 'closed' && github.event.pull_request.user.login != 'dependabot[bot]' @@ -607,6 +608,7 @@ jobs: proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }} proxygen-api-name: ${{ env.PROXYGEN_API_NAME }} proxygen-environment: ${{ env.PROXY_ENV}} + pr-number: "${{ github.event.pull_request.number }}i" - name: Tear down preview API proxy if: github.event.action == 'closed' From 34296035f5911db8aac7a086e2b7e01065bf75ba Mon Sep 17 00:00:00 2001 From: nhsd-rebecca-flynn <241736882+nhsd-rebecca-flynn@users.noreply.github.com> Date: Thu, 23 Apr 2026 12:54:44 +0100 Subject: [PATCH 2/4] [CDAPI-90]: Publish Spec Workflow and Action (#124) --- .../actions/proxy/publish-proxy/action.yaml | 40 ++++++++++++ .github/workflows/publish-specification.yaml | 63 +++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 .github/actions/proxy/publish-proxy/action.yaml create mode 100644 .github/workflows/publish-specification.yaml diff --git a/.github/actions/proxy/publish-proxy/action.yaml b/.github/actions/proxy/publish-proxy/action.yaml new file mode 100644 index 0000000..825c62f --- /dev/null +++ b/.github/actions/proxy/publish-proxy/action.yaml @@ -0,0 +1,40 @@ +name: Publish API Proxy Specification +description: Publish the Specification to the relevant catalogue + +inputs: + proxygen-key-secret: + description: 'Proxygen private key secret' + required: true + proxygen-key-id: + description: 'Proxygen key ID' + required: true + proxygen-client-id: + description: 'Proxygen client ID' + required: true + proxygen-api-name: + description: 'Proxygen API name' + required: true + catalogue: + description: 'Catalogue to which the specification will be published' + required: false + default: "uat" + +runs: + using: composite + steps: + - name: Configure Proxygen + uses: ./.github/actions/proxy/configure-proxygen + with: + proxygen-key-secret: ${{ inputs.proxygen-key-secret }} + proxygen-key-id: ${{ inputs.proxygen-key-id }} + proxygen-client-id: ${{ inputs.proxygen-client-id }} + proxygen-api-name: ${{ inputs.proxygen-api-name }} + + - name: Publish specification + env: + CATALOGUE: ${{ inputs.catalogue }} + shell: bash + run: | + FLAGS="--no-confirm" + [[ "$CATALOGUE" != "prod" ]] && FLAGS="--uat $FLAGS" + proxygen spec publish pathology-api/openapi.yaml $FLAGS diff --git a/.github/workflows/publish-specification.yaml b/.github/workflows/publish-specification.yaml new file mode 100644 index 0000000..fe5a78a --- /dev/null +++ b/.github/workflows/publish-specification.yaml @@ -0,0 +1,63 @@ +name: Publish the Specification + +on: + release: + types: [published] + workflow_dispatch: + inputs: + catalogue: + description: 'Catalogue' + required: true + default: "uat" + type: choice + options: + - "uat" + - "prod" + +permissions: + id-token: write + contents: read + +env: + AWS_REGION: eu-west-2 + PYTHON_VERSION: 3.14 + PROXYGEN_KEY_ID: ${{ vars.PROD_PROXYGEN_KEY_ID }} + PROXYGEN_CLIENT_ID: ${{ vars.PROD_PROXYGEN_CLIENT_ID }} + PROXYGEN_API_NAME: ${{ vars.PROXYGEN_API_NAME }} + +jobs: + publish-spec: + name: "Publish specification" + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + + - name: Select AWS role inputs + id: role-select + env: + AWS_ROLE_ARN: ${{ secrets.AWS_ROLE_ARN_INT }} + run: echo "aws_role=$AWS_ROLE_ARN" >> "$GITHUB_OUTPUT" + + - name: Configure AWS credentials (OIDC) + uses: aws-actions/configure-aws-credentials@5e19c1aa7acd2e02c5110eaf77eacd29039cca28 + with: + role-to-assume: ${{ steps.role-select.outputs.aws_role }} + aws-region: ${{ env.AWS_REGION }} + + - name: Get proxygen machine user details + id: proxygen-machine-user + uses: aws-actions/aws-secretsmanager-get-secrets@2cb1a461cbd4865ac4299648312e4704c646cd53 + with: + secret-ids: /cds/pathology/int/proxygen/prod-proxygen-key-secret + name-transformation: lowercase + + - name: Publish Specification + uses: ./.github/actions/proxy/publish-proxy + with: + proxygen-key-secret: ${{ env._cds_pathology_int_proxygen_prod_proxygen_key_secret }} + proxygen-key-id: ${{ env.PROXYGEN_KEY_ID }} + proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }} + proxygen-api-name: ${{ env.PROXYGEN_API_NAME }} + catalogue: ${{ inputs.catalogue || 'prod' }} From 63e7a78372fcb309bb9e826c1d7289d1ba701171 Mon Sep 17 00:00:00 2001 From: neil-sproston Date: Thu, 23 Apr 2026 14:29:25 +0100 Subject: [PATCH 3/4] [CDAPI-119] Implement workflow for release creation and deployment to integration --- .github/workflows/integration-env.yaml | 149 ++++++++++++++----------- .github/workflows/preview-env.yaml | 2 +- 2 files changed, 83 insertions(+), 68 deletions(-) diff --git a/.github/workflows/integration-env.yaml b/.github/workflows/integration-env.yaml index 354b221..e0f7d09 100644 --- a/.github/workflows/integration-env.yaml +++ b/.github/workflows/integration-env.yaml @@ -2,9 +2,15 @@ name: Integration Environment on: pull_request: - branches: [integration] - types: [closed] + branches: [ integration ] + types: [ closed ] workflow_dispatch: + inputs: + create-release: + description: "Create release" + required: true + default: false + type: boolean env: AWS_REGION: eu-west-2 @@ -12,13 +18,14 @@ env: PYTHON_VERSION: 3.14 LAMBDA_RUNTIME: python3.14 LAMBDA_HANDLER: lambda_handler.handler - MTLS_SECRET_NAME: ${{ vars.PREVIEW_ENV_MTLS_SECRET_NAME }} + MTLS_SECRET_NAME: ${{ vars.INT_MTLS_SECRET_NAME }} PROXYGEN_KEY_ID: ${{ vars.PREVIEW_ENV_PROXYGEN_KEY_ID }} PROXYGEN_CLIENT_ID: ${{ vars.PREVIEW_ENV_PROXYGEN_CLIENT_ID }} PROXYGEN_API_NAME: ${{ vars.PROXYGEN_API_NAME }} - BASE_URL: "https://internal-dev.api.service.nhs.uk/${{ vars.PROXYGEN_API_NAME }}-integration" + BASE_URL: "https://int.api.service.nhs.uk/pathology-laboratory-reporting" ENV: "remote" - HOST: "internal-dev.api.service.nhs.uk" + HOST: "int.api.service.nhs.uk" + PROXY_ENV: "int" jobs: integration-environment: @@ -103,7 +110,7 @@ jobs: echo "int_url=$URL" >> "$GITHUB_OUTPUT" # ---------- Handle application with int ---------- - - name: Create or update preview Lambda with int + - name: Create or update preview Lambda in int env: MOCK_URL: ${{ steps.names.outputs.int_url }} TOKEN_EXPIRY_THRESHOLD: ${{ secrets.APIM_TOKEN_EXPIRY_THRESHOLD }} @@ -114,6 +121,9 @@ jobs: API_MTLS_KEY: ${{ secrets.API_MTLS_KEY }} APIM_KEY_ID: ${{ secrets.APIM_KEY_ID }} CLIENT_REQUEST_TIMEOUT: ${{ secrets.CLIENT_REQUEST_TIMEOUT }} + APIM_TOKEN_URL: ${{ vars.INT_APIM_TOKEN_URL }} + MNS_EVENT_URL: ${{ vars.INT_MNS_EVENT_URL }} + PDM_BUNDLE_URL: ${{ vars.INT_PDM_BUNDLE_URL }} run: | cd pathology-api/target/ FN="${{ steps.names.outputs.function_name }}" @@ -123,7 +133,10 @@ jobs: API_KEY="${APIM_APIKEY:-/cds/pathology/int/apim/api-key}" MTLS_CERT="${API_MTLS_CERT:-/cds/pathology/int/mtls/client1-key-public}" MTLS_KEY="${API_MTLS_KEY:-/cds/pathology/int/mtls/client1-key-secret}" - KEY_ID="${APIM_KEY_ID:-DEV-1}" + KEY_ID="${APIM_KEY_ID:-INT-1}" + APIM_TOKEN_URL="${APIM_TOKEN_URL}" + MNS_EVENT_URL="${MNS_EVENT_URL}" + PDM_BUNDLE_URL="${PDM_BUNDLE_URL}" CLIENT_TIMEOUT="${CLIENT_REQUEST_TIMEOUT:-10s}" echo "Deploying preview function: $FN" wait_for_lambda_ready() { @@ -154,9 +167,9 @@ jobs: APIM_MTLS_CERT_NAME=$MTLS_CERT, \ APIM_MTLS_KEY_NAME=$MTLS_KEY, \ APIM_KEY_ID=$KEY_ID, \ - APIM_TOKEN_URL=$MOCK_URL/apim/oauth2/token, \ - PDM_BUNDLE_URL=$MOCK_URL/apim/check_auth, \ - MNS_EVENT_URL=$MOCK_URL/mns, \ + APIM_TOKEN_URL=$APIM_TOKEN_URL, \ + MNS_EVENT_URL=$MNS_EVENT_URL, \ + PDM_BUNDLE_URL=$PDM_BUNDLE_URL, \ CLIENT_TIMEOUT=$CLIENT_TIMEOUT, \ JWKS_SECRET_NAME=$JWKS_SECRET}" || true wait_for_lambda_ready @@ -177,16 +190,16 @@ jobs: APIM_KEY_ID=$KEY_ID, \ APIM_MTLS_CERT_NAME=$MTLS_CERT, \ APIM_MTLS_KEY_NAME=$MTLS_KEY, \ - APIM_TOKEN_URL=$MOCK_URL/apim/oauth2/token, \ - PDM_BUNDLE_URL=$MOCK_URL/apim/check_auth, \ - MNS_EVENT_URL=$MOCK_URL/mns, \ + APIM_TOKEN_URL=$APIM_TOKEN_URL, \ + MNS_EVENT_URL=$MNS_EVENT_URL, \ + PDM_BUNDLE_URL=$PDM_BUNDLE_URL, \ CLIENT_TIMEOUT=$CLIENT_TIMEOUT, \ JWKS_SECRET_NAME=$JWKS_SECRET}" \ --publish wait_for_lambda_ready fi - - name: Output function name with mock + - name: Output function name for int run: | echo "function = ${{ steps.names.outputs.function_name }}" echo "url = ${{ steps.names.outputs.int_url }}" @@ -258,16 +271,17 @@ jobs: secret-ids: /cds/pathology/int/proxygen/proxygen-key-secret name-transformation: lowercase - - name: Deploy preview API proxy + - name: Deploy integration API proxy uses: ./.github/actions/proxy/deploy-proxy with: mtls-secret-name: ${{ env.MTLS_SECRET_NAME }} target-url: ${{ steps.names.outputs.int_url }} - proxy-base-path: "${{ env.PROXYGEN_API_NAME }}-integration" + proxy-base-path: ${{ env.PROXYGEN_API_NAME }} proxygen-key-secret: ${{ env._cds_pathology_int_proxygen_proxygen_key_secret }} proxygen-key-id: ${{ env.PROXYGEN_KEY_ID }} proxygen-client-id: ${{ env.PROXYGEN_CLIENT_ID }} proxygen-api-name: ${{ env.PROXYGEN_API_NAME }} + proxygen-environment: ${{ env.PROXY_ENV }} - name: Retrieve Apigee Token id: apigee-token @@ -293,87 +307,88 @@ jobs: # ---------- Test suites ---------- - name: "Run unit tests" + continue-on-error: true uses: ./.github/actions/run-test-suite with: test-type: unit env: local - name: "Run contract tests" + continue-on-error: true uses: ./.github/actions/run-test-suite with: test-type: contract apigee-access-token: ${{ steps.apigee-token.outputs.apigee-access-token }} - name: "Run schema validation tests" + continue-on-error: true uses: ./.github/actions/run-test-suite with: test-type: schema apigee-access-token: ${{ steps.apigee-token.outputs.apigee-access-token }} - name: "Run integration tests" + continue-on-error: true uses: ./.github/actions/run-test-suite with: test-type: integration apigee-access-token: ${{ steps.apigee-token.outputs.apigee-access-token }} - name: "Run acceptance tests" + continue-on-error: true uses: ./.github/actions/run-test-suite with: test-type: acceptance apigee-access-token: ${{ steps.apigee-token.outputs.apigee-access-token }} + # ---------- Perform vuln scan and notify ---------- + # - name: Filesystem vuln scan + # - name: SBOM generation - # ---------- Coverage & reporting ---------- - - name: "Download all test coverage artefacts" - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - path: pathology-api/test-artefacts/ - merge-multiple: false - - name: "Download mock test coverage artefacts" - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - path: mocks/test-artefacts/ - merge-multiple: false - - name: "Merge coverage data" - run: make test-coverage - - name: "Rename coverage XML with unique name" - run: | - cd pathology-api/test-artefacts - mv coverage-merged.xml "${{ steps.create-name.outputs.artefact-name }}.xml" - cd ../.. - cd mocks/test-artefacts - mv coverage-merged.xml ${{ steps.create-name.outputs.artefact-name }}-mocks.xml - - name: "Upload combined coverage report" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: ${{ steps.create-name.outputs.artefact-name }} - path: pathology-api/test-artefacts - retention-days: 30 - - name: "Upload mocks coverage report" - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 - with: - name: ${{ steps.create-name.outputs.artefact-name }}-mocks - path: mocks/test-artefacts - retention-days: 30 - - name: "Download merged coverage report" - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + release-build: + name: "Build release on GIT" + runs-on: ubuntu-latest + needs: integration-environment + if: > + (github.event_name == 'pull_request' && github.event.pull_request.merged) || (github.event_name == 'workflow_dispatch' && inputs.create-release) + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd with: - name: ${{ steps.create-name.outputs.artefact-name }} - path: coverage-reports/ - - name: "Download mock coverage report" - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + fetch-depth: 0 + ref: integration + + - name: Set up Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 with: - name: ${{ steps.create-name.outputs.artefact-name }}-mocks - path: coverage-reports/ - - name: "SonarCloud Scan" - uses: SonarSource/sonarqube-scan-action@299e4b793aaa83bf2aba7c9c14bedbb485688ec4 #7.1.0 - env: - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + python-version: "${{ env.PYTHON_VERSION }}" + + - name: Setup Python project + uses: ./.github/actions/setup-python-project with: - args: > - -Dsonar.organization=${{ vars.SONAR_ORGANISATION_KEY }} - -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} - -Dsonar.python.coverage.reportPaths=coverage-reports/${{ steps.create-name.outputs.artefact-name }}.xml,coverage-reports/${{ steps.create-name.outputs.artefact-name }}-mocks.xml + python-version: ${{ env.PYTHON_VERSION }} - # ---------- Perform vuln scan and notify ---------- - # - name: Filesystem vuln scan - # - name: SBOM generation + - name: Package artifacts + run: | + make build + + - name: Compute tag + id: version + run: | + BASE_VERSION="$(poetry --directory pathology-api version --short)" + TAG="v${BASE_VERSION}-${GITHUB_RUN_NUMBER}" + echo "Computed tag: $TAG" + echo "base_version=$BASE_VERSION" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Create GitHub release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${{ steps.version.outputs.tag }}" \ + --title "Release ${{ steps.version.outputs.tag }}" \ + --target integration \ + --draft \ + --generate-notes \ + --fail-on-no-commits \ + pathology-api/target/artifact.zip || true diff --git a/.github/workflows/preview-env.yaml b/.github/workflows/preview-env.yaml index f1638f0..3574eba 100644 --- a/.github/workflows/preview-env.yaml +++ b/.github/workflows/preview-env.yaml @@ -37,6 +37,7 @@ jobs: pr-preview: name: "PR preview management" runs-on: ubuntu-latest + if: github.head_ref != 'main' && github.head_ref != 'integration' outputs: function_name: ${{ steps.names.outputs.function_name }} preview_url: ${{ steps.names.outputs.preview_url }} @@ -841,7 +842,6 @@ jobs: issue_number: issueNumber, body: lines.join('\n'), }); - # ---------- Perform vuln scan and notify ---------- # - name: Filesystem vuln scan # - name: SBOM generation From 2592800840a9f0defec9e415d43195d49e63a77a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 13:41:18 +0000 Subject: [PATCH 4/4] Bump aws-actions/configure-aws-credentials Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 5e19c1aa7acd2e02c5110eaf77eacd29039cca28 to bc9489585819302995bb108bbd899b7975f40303. - [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases) - [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md) - [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/5e19c1aa7acd2e02c5110eaf77eacd29039cca28...bc9489585819302995bb108bbd899b7975f40303) --- updated-dependencies: - dependency-name: aws-actions/configure-aws-credentials dependency-version: bc9489585819302995bb108bbd899b7975f40303 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/integration-env.yaml | 2 +- .github/workflows/preview-env.yaml | 2 +- .github/workflows/publish-specification.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration-env.yaml b/.github/workflows/integration-env.yaml index e0f7d09..9cdf853 100644 --- a/.github/workflows/integration-env.yaml +++ b/.github/workflows/integration-env.yaml @@ -75,7 +75,7 @@ jobs: echo "lambda_role=$LAMBDA_ROLE_ARN" >> "$GITHUB_OUTPUT" - name: Configure AWS credentials (OIDC) - uses: aws-actions/configure-aws-credentials@5e19c1aa7acd2e02c5110eaf77eacd29039cca28 + uses: aws-actions/configure-aws-credentials@bc9489585819302995bb108bbd899b7975f40303 with: role-to-assume: ${{ steps.role-select.outputs.aws_role }} aws-region: ${{ env.AWS_REGION }} diff --git a/.github/workflows/preview-env.yaml b/.github/workflows/preview-env.yaml index 3574eba..bc200c3 100644 --- a/.github/workflows/preview-env.yaml +++ b/.github/workflows/preview-env.yaml @@ -80,7 +80,7 @@ jobs: fi - name: Configure AWS credentials (OIDC) - uses: aws-actions/configure-aws-credentials@5e19c1aa7acd2e02c5110eaf77eacd29039cca28 + uses: aws-actions/configure-aws-credentials@bc9489585819302995bb108bbd899b7975f40303 with: role-to-assume: ${{ steps.role-select.outputs.aws_role }} aws-region: ${{ env.AWS_REGION }} diff --git a/.github/workflows/publish-specification.yaml b/.github/workflows/publish-specification.yaml index fe5a78a..52858ff 100644 --- a/.github/workflows/publish-specification.yaml +++ b/.github/workflows/publish-specification.yaml @@ -41,7 +41,7 @@ jobs: run: echo "aws_role=$AWS_ROLE_ARN" >> "$GITHUB_OUTPUT" - name: Configure AWS credentials (OIDC) - uses: aws-actions/configure-aws-credentials@5e19c1aa7acd2e02c5110eaf77eacd29039cca28 + uses: aws-actions/configure-aws-credentials@bc9489585819302995bb108bbd899b7975f40303 with: role-to-assume: ${{ steps.role-select.outputs.aws_role }} aws-region: ${{ env.AWS_REGION }}