From 4388af2d360bda5417016e9ecf1045a384cb8e09 Mon Sep 17 00:00:00 2001 From: Francisco Videira Date: Mon, 13 Apr 2026 14:26:37 +0000 Subject: [PATCH] first pass --- .github/workflows/cicd-3-deploy.yaml | 280 +++++++++++++++++++-------- 1 file changed, 197 insertions(+), 83 deletions(-) diff --git a/.github/workflows/cicd-3-deploy.yaml b/.github/workflows/cicd-3-deploy.yaml index 6a46ed13..f581ddf1 100644 --- a/.github/workflows/cicd-3-deploy.yaml +++ b/.github/workflows/cicd-3-deploy.yaml @@ -3,112 +3,226 @@ name: "2. CD - Deploy" on: workflow_dispatch: inputs: - include_prereleases: + backend_account_group: + description: "Target backend account group" type: choice - description: "Include pre-releases" - default: "true" + required: true + default: dev options: - - "true" - - "false" - version: - type: string - default: latest - description: "Install specific version" - -run-name: "Include prerelease: ${{ inputs.include_prereleases }} Version: ${{ inputs.version }} by @${{ github.actor }}" + - dev + - nonprod + - prod + backend_environment: + description: "Target backend environment" + type: string + required: true + default: main + apim_environment: + description: "Target APIM environment" + type: choice + required: true + default: internal-dev + options: + - internal-dev + - int + - prod + source_type: + description: "Deployment source type" + type: choice + required: true + default: release + options: + - release + - branch + - pr + source_value: + description: "Release tag, branch name, or PR number" + type: string + required: true + +run-name: >- + Deploy backend=${{ inputs.backend_account_group }}/${{ inputs.backend_environment }} + apim=${{ inputs.apim_environment }} + source=${{ inputs.source_type }}:${{ inputs.source_value }} by @${{ github.actor }} + permissions: - contents: read - pages: write id-token: write + contents: read + packages: read jobs: - metadata: - name: "Set CI/CD metadata" + validate: + name: Validate deployment request runs-on: ubuntu-latest - timeout-minutes: 1 + timeout-minutes: 5 outputs: - build_datetime: ${{ steps.variables.outputs.build_datetime }} - build_timestamp: ${{ steps.variables.outputs.build_timestamp }} - build_epoch: ${{ steps.variables.outputs.build_epoch }} - nodejs_version: ${{ steps.variables.outputs.nodejs_version }} - python_version: ${{ steps.variables.outputs.python_version }} - terraform_version: ${{ steps.variables.outputs.terraform_version }} - version: ${{ steps.variables.outputs.version }} - # tag: ${{ steps.variables.outputs.tag }} + release_version: ${{ steps.validate.outputs.release_version }} + is_release: ${{ steps.validate.outputs.is_release }} + build_artifact_version: ${{ steps.validate.outputs.build_artifact_version }} + target_account_group: ${{ steps.validate.outputs.target_account_group }} + target_environment: ${{ steps.validate.outputs.target_environment }} + apim_environment: ${{ steps.validate.outputs.apim_environment }} steps: - - name: "Checkout code" - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - - name: "Set CI/CD variables" - id: variables - run: | - datetime=$(date -u +'%Y-%m-%dT%H:%M:%S%z') - echo "build_datetime=$datetime" >> $GITHUB_OUTPUT - echo "build_timestamp=$(date --date=$datetime -u +'%Y%m%d%H%M%S')" >> $GITHUB_OUTPUT - echo "build_epoch=$(date --date=$datetime -u +'%s')" >> $GITHUB_OUTPUT - echo "nodejs_version=$(grep "^nodejs\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT - echo "python_version=$(grep "^python\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT - echo "terraform_version=$(grep "^terraform\s" .tool-versions | cut -f2 -d' ')" >> $GITHUB_OUTPUT - echo "version=$(head -n 1 .version 2> /dev/null || echo unknown)" >> $GITHUB_OUTPUT - # echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT - - name: "List variables" - run: | - export BUILD_DATETIME="${{ steps.variables.outputs.build_datetime }}" - export BUILD_TIMESTAMP="${{ steps.variables.outputs.build_timestamp }}" - export BUILD_EPOCH="${{ steps.variables.outputs.build_epoch }}" - export NODEJS_VERSION="${{ steps.variables.outputs.nodejs_version }}" - export PYTHON_VERSION="${{ steps.variables.outputs.python_version }}" - export TERRAFORM_VERSION="${{ steps.variables.outputs.terraform_version }}" - export VERSION="${{ steps.variables.outputs.version }}" - # export TAG="${{ steps.variables.outputs.tag }}" - make list-variables - - deploy-jekyll: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: metadata - steps: - - name: "Checkout code" - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 - - name: "Get version" - id: get-asset-version + - name: Validate inputs and resolve source + id: validate shell: bash env: GH_TOKEN: ${{ github.token }} run: | - if [[ ${{inputs.include_prereleases}} == true ]]; then - json=$(gh release list --json tagName --limit 1 --exclude-drafts) - else - json=$(gh release list --json tagName --limit 1 --exclude-drafts --exclude-pre-releases) + set -euo pipefail + + backend_account_group="${{ inputs.backend_account_group }}" + backend_environment="${{ inputs.backend_environment }}" + apim_environment="${{ inputs.apim_environment }}" + source_type="${{ inputs.source_type }}" + source_value="${{ inputs.source_value }}" + + if [[ -z "$source_value" ]]; then + echo "[ERROR] source_value cannot be empty." + exit 1 + fi + + if [[ "$backend_account_group" == "prod" && "$apim_environment" != "prod" ]]; then + echo "[ERROR] PROD backend and PROD APIM can only be deployed together." + exit 1 + fi + + if [[ "$apim_environment" == "prod" && "$backend_account_group" != "prod" ]]; then + echo "[ERROR] PROD backend and PROD APIM can only be deployed together." + exit 1 fi - echo $json + is_release="false" + release_version="$source_value" - release_version=$(echo $json | (jq -r '.[0].tagName')) - if [[ $release_version == null ]]; then exit 1; else echo $release_version; fi + if [[ "$source_type" == "release" ]]; then + if [[ ! "$source_value" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+([-.+][0-9A-Za-z.-]+)?$ ]]; then + echo "[ERROR] Release tags must be semantic versions, for example v1.2.3." + exit 1 + fi - if [[ ${{inputs.version}} == latest ]]; then - echo release_version=$(echo $release_version) >> $GITHUB_OUTPUT + gh release view "$source_value" --repo "$GITHUB_REPOSITORY" >/dev/null + + oas_asset="api-oas-specification-${apim_environment}-${source_value}.zip" + gh release view "$source_value" --repo "$GITHUB_REPOSITORY" --json assets \ + --jq '.assets[].name' | grep -x "$oas_asset" >/dev/null + + is_release="true" + elif [[ "$source_type" == "branch" ]]; then + if [[ "$backend_account_group" != "dev" ]]; then + echo "[ERROR] Branch deployments are only allowed for dev backend deployments." + exit 1 + fi + + branch_matches=$(gh api "repos/${GITHUB_REPOSITORY}/git/matching-refs/heads/${source_value}" --jq 'length') + if [[ "$branch_matches" -eq 0 ]]; then + echo "[ERROR] Branch '$source_value' not found in repository." + exit 1 + fi + elif [[ "$source_type" == "pr" ]]; then + if [[ "$backend_account_group" != "dev" ]]; then + echo "[ERROR] PR deployments are only allowed for dev backend deployments." + exit 1 + fi + + if [[ ! "$source_value" =~ ^[0-9]+$ ]]; then + echo "[ERROR] PR source_value must be a numeric PR number." + exit 1 + fi + + release_version=$(gh pr view "$source_value" --repo "$GITHUB_REPOSITORY" --json headRefName --jq '.headRefName') + if [[ -z "$release_version" || "$release_version" == "null" ]]; then + echo "[ERROR] PR #$source_value was not found." + exit 1 + fi else - echo release_version=$(echo ${{inputs.version}}) >> $GITHUB_OUTPUT + echo "[ERROR] Unsupported source type '$source_type'." + exit 1 fi - - name: "Get release version" - id: download-asset - shell: bash + if [[ "$backend_account_group" == "nonprod" || "$backend_account_group" == "prod" ]]; then + if [[ "$is_release" != "true" ]]; then + echo "[ERROR] Only tagged releases can be deployed to NONPROD and PROD backends." + exit 1 + fi + fi + + case "$backend_account_group" in + dev) + target_account_group="nhs-notify-supplier-api-dev" + ;; + nonprod) + target_account_group="nhs-notify-supplier-api-nonprod" + ;; + prod) + target_account_group="nhs-notify-supplier-api-prod" + ;; + *) + echo "[ERROR] Unsupported backend account group '$backend_account_group'." + exit 1 + ;; + esac + + build_artifact_version="" + if [[ "$is_release" != "true" ]]; then + build_artifact_version="manual" + fi + + echo "release_version=$release_version" >> "$GITHUB_OUTPUT" + echo "is_release=$is_release" >> "$GITHUB_OUTPUT" + echo "build_artifact_version=$build_artifact_version" >> "$GITHUB_OUTPUT" + echo "target_account_group=$target_account_group" >> "$GITHUB_OUTPUT" + echo "target_environment=$backend_environment" >> "$GITHUB_OUTPUT" + echo "apim_environment=$apim_environment" >> "$GITHUB_OUTPUT" + + deploy-backend: + name: Deploy backend + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: validate + steps: + - name: Checkout repository + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5 + + - name: Deploy backend environment env: - GH_TOKEN: ${{ github.token }} + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }} run: | - gh release download ${{steps.get-asset-version.outputs.release_version}} -p jekyll-docs-*.tar --output artifact.tar + bash .github/scripts/dispatch_internal_repo_workflow.sh \ + --releaseVersion "${{ needs.validate.outputs.release_version }}" \ + --targetWorkflow "dispatch-deploy-static-notify-supplier-api-env.yaml" \ + --targetEnvironment "${{ needs.validate.outputs.target_environment }}" \ + --targetAccountGroup "${{ needs.validate.outputs.target_account_group }}" \ + --targetComponent "api" \ + --terraformAction "apply" - - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7 + deploy-proxy: + name: Deploy proxy + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: [validate, deploy-backend] + steps: + - name: Build OAS spec for non-release source + if: ${{ needs.validate.outputs.is_release != 'true' }} + uses: ./.github/actions/build-oas-spec with: - name: jekyll-docs-${{steps.get-asset-version.outputs.release_version}} - path: artifact.tar + version: ${{ needs.validate.outputs.build_artifact_version }} + apimEnv: ${{ needs.validate.outputs.apim_environment }} + nodejs_version: "22.22.0" + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 + - name: Deploy proxy + env: + PROXYGEN_API_NAME: nhs-notify-supplier + APP_CLIENT_ID: ${{ secrets.APP_CLIENT_ID }} + APP_PEM_FILE: ${{ secrets.APP_PEM_FILE }} + uses: ./.github/actions/build-proxies with: - artifact_name: jekyll-docs-${{steps.get-asset-version.outputs.release_version}} + targetComponent: api + environment: ${{ needs.validate.outputs.target_environment }} + apimEnv: ${{ needs.validate.outputs.apim_environment }} + runId: "${{ github.run_id }}" + releaseVersion: ${{ needs.validate.outputs.release_version }} + isRelease: ${{ needs.validate.outputs.is_release }} + version: ${{ needs.validate.outputs.build_artifact_version }}