diff --git a/.github/workflows/automated-deploy-stack.yml b/.github/workflows/automated-deploy-stack.yml index 6f34935d..bd8a5ae8 100644 --- a/.github/workflows/automated-deploy-stack.yml +++ b/.github/workflows/automated-deploy-stack.yml @@ -3,43 +3,14 @@ name: "Z-AUTOMATED: Deploy - Stack" on: push: branches: [main] - paths: - - "stacks/deductions/**" - - "stacks/deductions-cross-account/**" - - "stacks/deductions-dashboard/**" - - "stacks/mesh-forwarder/**" - - "stacks/pds-adaptor/**" - - "stacks/re-registration-service/**" + paths: - "stacks/suspension-service/**" - - "stacks/nems-event-processor/**" - - "stacks/ehr-repo/**" - - "stacks/ehr-repo-db-roles/**" - - "stacks/ehr-out-service/**" - - "stacks/ehr-transfer-service/**" - - "stacks/gp2gp-messenger/**" - - "stacks/mhs/**" - - ".github/workflows/automated-deploy-stack.yml" - - ".github/workflows/base-deploy-stack.yml" pull_request: branches: [main] paths: - - "stacks/deductions/**" - - "stacks/deductions-cross-account/**" - - "stacks/deductions-dashboard/**" - - "stacks/mesh-forwarder/**" - - "stacks/pds-adaptor/**" - - "stacks/re-registration-service/**" - "stacks/suspension-service/**" - - "stacks/nems-event-processor/**" - - "stacks/ehr-repo/**" - - "stacks/ehr-repo-db-roles/**" - - "stacks/ehr-out-service/**" - - "stacks/ehr-transfer-service/**" - - "stacks/gp2gp-messenger/**" - - "stacks/mhs/**" - - ".github/workflows/automated-deploy-stack.yml" - - ".github/workflows/base-deploy-stack.yml" + permissions: pull-requests: write @@ -47,195 +18,12 @@ permissions: contents: read # This is required for actions/checkout jobs: - get-changed-files: - runs-on: ubuntu-latest - outputs: - changed: ${{ steps.get-changed-files.outputs.changed }} - steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Get changed files - id: get-changed-files - env: - GITHUB_EVENT_NAME: ${{ github.event_name }} - run: | - if [ "$GITHUB_EVENT_NAME" == "push" ]; then - echo "DEBUG: Comparing ${{ github.event.before }} to ${{ github.event.after }}" - changed=$(git diff --name-only ${{ github.event.before }} ${{ github.event.after }} | tr '\n' ' ') - fi - if [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then - changed=$(git diff --name-only origin/main... | tr '\n' ' ') - fi - echo "DEBUG: Changed files: '$changed'" - echo "changed=$changed" >> $GITHUB_OUTPUT - - deductions-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/mesh-forwarder') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: Deductions Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files] - with: - stack: deductions - backend_key_alias: deductions-infra-dev - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - lambdas_to_build: true - secrets: inherit - - deductions-cross-account: - if: always() && (needs.deductions-ci.result == 'success' || needs.deductions-ci.result == 'skipped') && (contains(needs.get-changed-files.outputs.changed, 'stacks/deductions-cross-account') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml')) - name: deductions-cross-account - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci] - with: - stack: deductions-cross-account - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - backend_key_alias: deductions-infra-cross-account-dev - secrets: inherit - - deductions-dashboard-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/deductions-dashboard') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: Deductions Dashboard Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci] - with: - stack: deductions-dashboard - backend_key_alias: deductions-infra-dashboard-dev - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - mesh-forwarder-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/mesh-forwarder') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: MESH Forwarder Infrastructure - needs: [get-changed-files, deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: mesh-forwarder - ecr_alias: deductions/mesh-forwarder - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - pds-adaptor-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/pds-adaptor') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: PDS Adaptor Infrastructure - needs: [get-changed-files, deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: pds-adaptor - ecr_alias: deductions/pds-adaptor - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - re-registration-service-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/re-registration-service') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: Re-Registration Service Infrastructure - needs: [get-changed-files, deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: re-registration-service - ecr_alias: repo/re-registration-service - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - suspension-service-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/suspension-service') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: Suspension Service Infrastructure - needs: [get-changed-files, deductions-ci, deductions-cross-account] + name: End of Transfer Infrastructure - Destroy plan uses: ./.github/workflows/deploy-stack.yml with: stack: suspension-service ecr_alias: repo/suspension-service environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - nems-event-processor-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/nems-event-processor') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: NEMS Event Processor Infrastructure - needs: [get-changed-files, deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: nems-event-processor - ecr_alias: deductions/nems-event-processor - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - ehr-repo-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/ehr-repo') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: EHR Repo Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci, deductions-cross-account] - with: - stack: ehr-repo - ecr_alias: deductions/ehr-repo - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - ehr-repo-db-roles-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/ehr-repo') || contains(needs.get-changed-files.outputs.changed, 'stacks/ehr-repo-db-roles') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: EHR Repo DB Roles Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci, ehr-repo-ci] - with: - stack: ehr-repo-db-roles - environment: dev - backend_key_alias: ehr-repo-dev-db-roles - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - ehr-out-service-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/ehr-out-service') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: EHR Out Service Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci, deductions-cross-account] - with: - stack: ehr-out-service - ecr_alias: deductions/ehr-out-service - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - ehr-transfer-service-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/ehr-transfer-service') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: EHR Transfer Service Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci, deductions-cross-account] - with: - stack: ehr-transfer-service - ecr_alias: deductions/ehr-transfer-service - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - gp2gp-messenger-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/gp2gp-messenger') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: GP2GP Messenger Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci, deductions-cross-account] - with: - stack: gp2gp-messenger - ecr_alias: deductions/gp2gp-messenger - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} - secrets: inherit - - mhs-ci: - if: contains(needs.get-changed-files.outputs.changed, 'stacks/mhs') || contains(needs.get-changed-files.outputs.changed, '.github/workflows/automated-deploy-stack.yml') - name: MHS Infrastructure CI - uses: ./.github/workflows/deploy-stack.yml - needs: [get-changed-files, deductions-ci, deductions-cross-account] - with: - stack: mhs - backend_key_alias: mhs-dev-repo - environment: dev - is_deployment: ${{ github.ref == 'refs/heads/main' }} + is_deployment: false secrets: inherit diff --git a/.github/workflows/deploy-full.yml b/.github/workflows/deploy-full.yml deleted file mode 100644 index aa4f51e8..00000000 --- a/.github/workflows/deploy-full.yml +++ /dev/null @@ -1,169 +0,0 @@ -name: Deploy - Full - -run-name: "${{ inputs.environment }} | Terraform Apply? = ${{ inputs.is_deployment }}" - -on: - workflow_dispatch: - inputs: - environment: - default: "dev" - description: "Environment" - required: true - type: choice - options: - - dev - - pre-prod - - prod - is_deployment: - default: false - type: boolean - description: "Terraform Apply?" - -permissions: - pull-requests: write - id-token: write # This is required for requesting the JWT - contents: read # This is required for actions/checkout - -jobs: - deductions-ci: - name: Deploy Deductions Infrastructure - uses: ./.github/workflows/deploy-stack.yml - with: - stack: deductions - backend_key_alias: deductions-infra-${{ inputs.environment }} - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - lambdas_to_build: true - secrets: inherit - - deductions-cross-account: - name: Deploy Deductions Cross Account Infrastructure - uses: ./.github/workflows/deploy-stack.yml - with: - stack: deductions-cross-account - backend_key_alias: deductions-infra-cross-account-${{ inputs.environment }} - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - lambdas_to_build: true - secrets: inherit - - deductions-dashboard-ci: - name: Deploy Deductions Dashboard Infrastructure - uses: ./.github/workflows/deploy-stack.yml - needs: [deductions-ci] - with: - stack: deductions-dashboard - backend_key_alias: deductions-infra-dashboard-${{ inputs.environment }} - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - mesh-forwarder-ci: - name: Deploy MESH Forwarder Infrastructure - needs: [deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: mesh-forwarder - ecr_alias: deductions/mesh-forwarder - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - pds-adaptor-ci: - name: Deploy PDS Adaptor Infrastructure - needs: [deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: pds-adaptor - ecr_alias: deductions/pds-adaptor - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - re-registration-service-ci: - name: Deploy Re-Registration Service Infrastructure - needs: [deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: re-registration-service - ecr_alias: repo/re-registration-service - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - suspension-service-ci: - name: Deploy Suspension Service Infrastructure - needs: [deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: suspension-service - ecr_alias: repo/suspension-service - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - nems-event-processor-ci: - name: Deploy NEMS Event Processor Infrastructure - needs: [deductions-ci, deductions-cross-account] - uses: ./.github/workflows/deploy-stack.yml - with: - stack: nems-event-processor - ecr_alias: deductions/nems-event-processor - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - ehr-repo-ci: - name: EHR Repo Infrastructure - uses: ./.github/workflows/deploy-stack.yml - needs: [deductions-ci, deductions-cross-account] - with: - stack: ehr-repo - ecr_alias: deductions/ehr-repo - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - ehr-out-service-ci: - name: Deploy EHR Out Service Infrastructure - uses: ./.github/workflows/deploy-stack.yml - needs: [deductions-ci, deductions-cross-account] - with: - stack: ehr-out-service - ecr_alias: deductions/ehr-out-service - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - ehr-transfer-service-ci: - name: Deploy EHR Transfer Service Infrastructure - uses: ./.github/workflows/deploy-stack.yml - needs: [deductions-ci, deductions-cross-account] - with: - stack: ehr-transfer-service - ecr_alias: deductions/ehr-transfer-service - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - gp2gp-messenger-ci: - name: Deploy GP2GP Messenger Infrastructure - uses: ./.github/workflows/deploy-stack.yml - needs: [deductions-ci, deductions-cross-account] - with: - stack: gp2gp-messenger - ecr_alias: deductions/gp2gp-messenger - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit - - mhs-ci: - name: Deploy MHS Infrastructure - uses: ./.github/workflows/deploy-stack.yml - needs: [deductions-ci, deductions-cross-account] - with: - stack: mhs - backend_key_alias: mhs-${{ inputs.environment }}-repo - environment: ${{ inputs.environment }} - is_deployment: ${{ inputs.is_deployment }} - secrets: inherit diff --git a/.github/workflows/deploy-stack.yml b/.github/workflows/deploy-stack.yml index c61cfc46..210136d6 100644 --- a/.github/workflows/deploy-stack.yml +++ b/.github/workflows/deploy-stack.yml @@ -1,4 +1,4 @@ -name: Deploy - Stack +name: Destroy end-of-transfer-service infrastructure run-name: "${{ inputs.environment }} | ${{ inputs.stack }} | Terraform Apply? = ${{ inputs.is_deployment }}" @@ -19,27 +19,11 @@ on: required: true type: choice options: - - ehr-repo - - ehr-repo-db-roles - - ehr-out-service - - ehr-transfer-service - - re-registration-service - suspension-service - - nems-event-processor - - deductions - - deductions-backup - - deductions-cross-account - - deductions-dashboard - - mhs - - gp2gp-messenger - - pds-adapter - - mesh-forwarder - - base-infra is_deployment: default: false type: boolean description: "Terraform Apply?" - workflow_call: inputs: environment: @@ -69,79 +53,16 @@ on: default: false description: "Do we need to build any lambdas before deploying?" type: boolean - permissions: pull-requests: write id-token: write # This is required for requesting the JWT contents: read # This is required for actions/checkout jobs: - promote-images-from-source-ecr-account: - environment: ${{ inputs.environment }} - name: Promote images from source to current environment - if: ${{ inputs.is_deployment && inputs.environment != 'dev' && inputs.ecr_alias }} - runs-on: ubuntu-latest - steps: - - name: Checkout - id: checkout - uses: actions/checkout@v6 - - - name: Configure AWS Credentials - id: creds - uses: aws-actions/configure-aws-credentials@v8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 - with: - role-to-assume: ${{ secrets.IAM_ROLE }} - aws-region: ${{ vars.AWS_REGION }} - role-skip-session-tagging: true - mask-aws-account-id: true - - # Needs the AmazonEC2ContainerRegistryPowerUser role - - name: Login to ECR - uses: aws-actions/amazon-ecr-login@v2 - with: - registries: "${{ steps.creds.outputs.aws-account-id }},${{ secrets.AWS_SOURCE_ECR_ACCOUNT_ID }}" - - - name: Get Most Recent ECR Image SHA Tag From Lower Environment - id: get-image-tag - env: - ECR_ALIAS: ${{ inputs.ecr_alias }} - run: | - LATEST_IMAGE_SHA=$(aws ecr describe-images \ - --registry-id ${{ secrets.AWS_SOURCE_ECR_ACCOUNT_ID }} \ - --repository-name $ECR_ALIAS \ - --output json | - jq -r ' - .imageDetails - | map(select(.imageTags != null and (.imageTags|length>0))) - | sort_by(.imagePushedAt) - | last - | .imageTags[] - | select(test("^[0-9a-f]{40}$")) - ' | head -n 1) - - echo "All image tags for $ECR_ALIAS: $LATEST_IMAGE_SHA" - echo "image_sha=$LATEST_IMAGE_SHA" >> $GITHUB_OUTPUT - - - name: ECR Copy - id: ecr-copy - env: - IMAGE_SHA: "${{ steps.get-image-tag.outputs.image_sha }}" - run: | - source_repo=${{ secrets.AWS_SOURCE_ECR_ACCOUNT_ID }}.dkr.ecr.eu-west-2.amazonaws.com/${{ inputs.ecr_alias }} - destination_repo=${{ steps.creds.outputs.aws-account-id }}.dkr.ecr.eu-west-2.amazonaws.com/${{ inputs.ecr_alias }} - - docker pull $source_repo:$IMAGE_SHA - docker tag $source_repo:$IMAGE_SHA $destination_repo:$IMAGE_SHA - docker tag $source_repo:$IMAGE_SHA $destination_repo:${{ github.ref_name }} - docker push $destination_repo:$IMAGE_SHA - docker push $destination_repo:${{ github.ref_name }} - - deploy_stack: - environment: ${{ !inputs.ci_account && inputs.environment || 'ci_account' }} + destroy_stack: + environment: ${{ inputs.environment}} env: - GITHUB_ENV: ${{ !inputs.ci_account && inputs.environment || 'ci_account' }} + GITHUB_ENV: ${{ inputs.environment }} runs-on: ubuntu-latest - needs: [promote-images-from-source-ecr-account] - if: always() && (needs.promote-images-from-source-ecr-account.result == 'skipped' || needs.promote-images-from-source-ecr-account.result == 'success') defaults: run: working-directory: ./stacks/${{ inputs.stack }}/terraform @@ -151,7 +72,7 @@ jobs: with: repository: NHSDigital/orphaned-record-continuity-infrastructure fetch-depth: 0 - ref: ${{ github.ref }} + ref: PRM-726 - name: Configure AWS Credentials (ReadWrite) uses: aws-actions/configure-aws-credentials@8df5847569e6427dd6c4fb1cf565c83acfa8afa7 # v6.0.0 @@ -162,65 +83,17 @@ jobs: mask-aws-account-id: true role-skip-session-tagging: true - - name: Setup Terraform variables for Deductions Cross Account - if: inputs.stack == 'deductions-cross-account' - env: - DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} - DOCKERHUB_ACCESS_TOKEN: ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} - PROMOTION_IAM_ARN: ${{ secrets.PROMOTION_IAM_ARN }} - run: | - cat > pipeline-deductions-cross-account.auto.tfvars <0))) - | sort_by(.imagePushedAt) - | last - | .imageTags[] - | select(test("^[0-9a-f]{40}$")) - ' | head -n 1) - - echo "Found tag: $LATEST_IMAGE_SHA" - - echo "Most recent image tag for $ECR_ALIAS: $LATEST_IMAGE_SHA" - echo "image_tag=$LATEST_IMAGE_SHA" >> $GITHUB_OUTPUT - - name: Setup Terraform variables id: vars env: ECR_ALIAS: ${{ inputs.ecr_alias }} - IMAGE_TAG: ${{ steps.get-image-tag.outputs.image_tag || '' }} + IMAGE_TAG: 51-4e60390b run: | - COMMON_ACCOUNT_ID=$(aws ssm get-parameter --name /repo/ci/user-input/external/aws-account-id --with-decryption | jq -r .Parameter.Value) - cat > pipeline.auto.tfvars <> pipeline.auto.tfvars fi - - name: Build lambdas with additional requirements - if: inputs.lambdas_to_build - run: | - cd ../../../ - ./tasks build_lambdas - - name: Setup Terraform uses: hashicorp/setup-terraform@v3 with: @@ -233,25 +106,12 @@ jobs: - name: Terraform Init id: init + env: + KEY: end-of-transfer-service-${{ inputs.environment }} run: | - # compute backend key: prefer alias, otherwise replace '-dev-' with the chosen environment - STACK="${{ inputs.stack }}" - ENV="${{ inputs.environment }}" - ALIAS="${{ inputs.backend_key_alias || '' }}" - - if [ -n "$ALIAS" ]; then - KEY="$ALIAS" - else - if [[ "$STACK" == *"-dev-"* ]]; then - KEY="${STACK/-dev-/-$ENV-}" - else - KEY="${STACK}-$ENV" - fi - fi - echo "Using backend key: $KEY/terraform.tfstate" terraform init -no-color \ - -backend-config="key=${KEY}/terraform.tfstate" \ + -backend-config="key=$KEY/terraform.tfstate" \ -backend-config="bucket=${{ secrets.TF_BACKEND_BUCKET }}" \ -backend-config="dynamodb_table=${{ secrets.TF_BACKEND_TABLE }}" shell: bash @@ -263,7 +123,7 @@ jobs: - name: Terraform Plan id: plan run: | - terraform plan -no-color -input=false -var-file="${{ vars.AWS_ENVIRONMENT }}.tfvars" -out "${{ vars.AWS_ENVIRONMENT }}.tfplan" + terraform plan -var task_image_tag=$IMAGE_TAG -var-file="configs/end-of-transfer-service/end-of-transfer-service.tfvars" -var-file="configs/end-of-transfer-service/${{ vars.AWS_ENVIRONMENT }}.tfvars" -out="${{ vars.AWS_ENVIRONMENT }}.tfplan" -destroy terraform show -no-color ${{ vars.AWS_ENVIRONMENT }}.tfplan > ${{ vars.AWS_ENVIRONMENT }}.tfplan.txt echo "summary=$(grep -E 'Plan: [0-9]+ to add, [0-9]+ to change, [0-9]+ to destroy\.|No changes\. Your infrastructure matches the configuration\.' ${{ vars.AWS_ENVIRONMENT }}.tfplan.txt | sed 's/.*No changes\. Your infrastructure matches the configuration/Plan: no changes/g' | sed 's/.*Plan: //g' | sed 's/\..*//g')" >> $GITHUB_OUTPUT shell: bash diff --git a/stacks/suspension-service/terraform/configs/end-of-transfer-service/dev.tfvars b/stacks/suspension-service/terraform/configs/end-of-transfer-service/dev.tfvars new file mode 100644 index 00000000..68f581cb --- /dev/null +++ b/stacks/suspension-service/terraform/configs/end-of-transfer-service/dev.tfvars @@ -0,0 +1,10 @@ +environment = "dev" + +synthetic_patient_prefix = "96937" +process_only_synthetic_patients = false + +scale_up_expression = "( (MINUTE(m1)>=0 )),10, 0" +enable_scale_action = false + +ecs_desired_count = 0 +repo_process_only_safe_listed_ods_codes = true \ No newline at end of file diff --git a/stacks/suspension-service/terraform/configs/end-of-transfer-service/end-of-transfer-service.tfvars b/stacks/suspension-service/terraform/configs/end-of-transfer-service/end-of-transfer-service.tfvars new file mode 100644 index 00000000..89cd5168 --- /dev/null +++ b/stacks/suspension-service/terraform/configs/end-of-transfer-service/end-of-transfer-service.tfvars @@ -0,0 +1,6 @@ +component_name = "end-of-transfer-service" +repo_name = "end-of-transfer-service" +metric_namespace = "EndOfTransferService" + +can_update_managing_organisation_to_repo = false +is_end_of_transfer_service = true diff --git a/stacks/suspension-service/terraform/configs/end-of-transfer-service/pre-prod.tfvars b/stacks/suspension-service/terraform/configs/end-of-transfer-service/pre-prod.tfvars new file mode 100644 index 00000000..ea6addf1 --- /dev/null +++ b/stacks/suspension-service/terraform/configs/end-of-transfer-service/pre-prod.tfvars @@ -0,0 +1,6 @@ +environment = "pre-prod" + +synthetic_patient_prefix = "96936" +process_only_synthetic_patients = true + +ecs_desired_count = 0 \ No newline at end of file diff --git a/stacks/suspension-service/terraform/configs/end-of-transfer-service/prod.tfvars b/stacks/suspension-service/terraform/configs/end-of-transfer-service/prod.tfvars new file mode 100644 index 00000000..fcba9bce --- /dev/null +++ b/stacks/suspension-service/terraform/configs/end-of-transfer-service/prod.tfvars @@ -0,0 +1,6 @@ +environment = "prod" + +synthetic_patient_prefix = "999" +process_only_synthetic_patients = true + +ecs_desired_count = 0 \ No newline at end of file diff --git a/stacks/suspension-service/terraform/dev.tfvars b/stacks/suspension-service/terraform/dev.tfvars deleted file mode 100644 index b5968e04..00000000 --- a/stacks/suspension-service/terraform/dev.tfvars +++ /dev/null @@ -1,8 +0,0 @@ -environment = "dev" -synthetic_patient_prefix = "96937" -component_name = "suspension-service" -repo_name = "suspension-service" -metric_namespace = "SuspensionService" -can_update_managing_organisation_to_repo = true -repo_process_only_safe_listed_ods_codes = true -process_only_synthetic_patients = false \ No newline at end of file diff --git a/stacks/suspension-service/terraform/iam.tf b/stacks/suspension-service/terraform/iam.tf index 2344c417..cf51dd76 100644 --- a/stacks/suspension-service/terraform/iam.tf +++ b/stacks/suspension-service/terraform/iam.tf @@ -1,17 +1,17 @@ locals { account_id = data.aws_caller_identity.current.account_id - sns_base_arns = [ - aws_sns_topic.not_suspended.arn, - aws_sns_topic.mof_updated.arn, - aws_sns_topic.mof_not_updated.arn, - aws_sns_topic.invalid_suspension.arn, - aws_sns_topic.invalid_suspension_audit_topic.arn, - aws_sns_topic.deceased_patient.arn, - aws_sns_topic.event_out_of_order.arn, - aws_sns_topic.active_suspensions.arn, - ] - sns_for_suspension_service = module.suspension-service.repo_incoming_sns_topic - sns_arns = concat(local.sns_base_arns, [local.sns_for_suspension_service]) + sns_base_arns = { + not_suspended = aws_sns_topic.not_suspended.arn + mof_updated = aws_sns_topic.mof_updated.arn + mof_not_updated = aws_sns_topic.mof_not_updated.arn + invalid_suspension = aws_sns_topic.invalid_suspension.arn + invalid_suspension_audit = aws_sns_topic.invalid_suspension_audit_topic.arn + deceased_patient = aws_sns_topic.deceased_patient.arn + event_out_of_order = aws_sns_topic.event_out_of_order.arn + active_suspensions = aws_sns_topic.active_suspensions.arn + end_of_transfer = module.end-of-transfer-service[0].end_of_transfer_sns_topic + } + sns_arns = values(local.sns_base_arns) } data "aws_iam_policy_document" "ecs-assume-role-policy" { diff --git a/stacks/suspension-service/terraform/main.tf b/stacks/suspension-service/terraform/main.tf index de1e9d18..9fe969b8 100644 --- a/stacks/suspension-service/terraform/main.tf +++ b/stacks/suspension-service/terraform/main.tf @@ -11,6 +11,17 @@ terraform { } } +module "end-of-transfer-service" { + count = 1 + source = "./modules/end-of-transfer-service/" + environment = var.environment + + component_name = var.component_name + metric_namespace = var.metric_namespace + repo_name = var.repo_name + ecs_desired_count = var.ecs_desired_count +} + module "suspension-service" { source = "./modules/suspension-service/" environment = var.environment diff --git a/stacks/suspension-service/terraform/modules/end-of-transfer-service/cloudwatch.tf b/stacks/suspension-service/terraform/modules/end-of-transfer-service/cloudwatch.tf new file mode 100644 index 00000000..af7edee6 --- /dev/null +++ b/stacks/suspension-service/terraform/modules/end-of-transfer-service/cloudwatch.tf @@ -0,0 +1,24 @@ +locals { + sqs_namespace = "AWS/SQS" +} + +resource "aws_cloudwatch_metric_alarm" "transfer_complete_queue_age_of_message" { + alarm_name = "${var.environment}-${var.component_name}-transfer-complete-approx-age-of-oldest-message" + comparison_operator = "GreaterThanThreshold" + threshold = "1800" + evaluation_periods = "1" + metric_name = "ApproximateAgeOfOldestMessage" + namespace = local.sqs_namespace + alarm_description = "This alarm triggers when messages on the transfer complete queue is not polled by end of transfer service in last 30 mins" + statistic = "Maximum" + period = "300" + dimensions = { + QueueName = aws_sqs_queue.transfer_complete.name + } + alarm_actions = [data.aws_sns_topic.alarm_notifications.arn] + ok_actions = [data.aws_sns_topic.alarm_notifications.arn] +} + +data "aws_sns_topic" "alarm_notifications" { + name = "${var.environment}-alarm-notifications-sns-topic" +} \ No newline at end of file diff --git a/stacks/suspension-service/terraform/modules/end-of-transfer-service/data.tf b/stacks/suspension-service/terraform/modules/end-of-transfer-service/data.tf new file mode 100644 index 00000000..06565d02 --- /dev/null +++ b/stacks/suspension-service/terraform/modules/end-of-transfer-service/data.tf @@ -0,0 +1,6 @@ +data "aws_ssm_parameter" "transfer_complete_kms_key" { + name = "/repo/${var.environment}/output/ehr-transfer-service/transfer-complete-encryption-kms-key" +} +data "aws_ssm_parameter" "transfer_complete_topic_arn" { + name = "/repo/${var.environment}/output/ehr-transfer-service/transfer-complete-sns-topic-arn" +} \ No newline at end of file diff --git a/stacks/suspension-service/terraform/modules/end-of-transfer-service/iam.tf b/stacks/suspension-service/terraform/modules/end-of-transfer-service/iam.tf new file mode 100644 index 00000000..4ee14a83 --- /dev/null +++ b/stacks/suspension-service/terraform/modules/end-of-transfer-service/iam.tf @@ -0,0 +1,35 @@ +data "aws_iam_policy_document" "transfer_complete_policy_doc" { + statement { + effect = "Allow" + + actions = [ + "sqs:SendMessage" + ] + + principals { + identifiers = ["sns.amazonaws.com"] + type = "Service" + } + + resources = [ + aws_sqs_queue.transfer_complete.arn, + aws_sqs_queue.transfer_complete_observability.arn + ] + + condition { + test = "ArnEquals" + values = [data.aws_ssm_parameter.transfer_complete_topic_arn.value] + variable = "aws:SourceArn" + } + } +} + +resource "aws_sqs_queue_policy" "transfer_complete_observability" { + queue_url = aws_sqs_queue.transfer_complete_observability.id + policy = data.aws_iam_policy_document.transfer_complete_policy_doc.json +} + +resource "aws_sqs_queue_policy" "transfer_complete" { + queue_url = aws_sqs_queue.transfer_complete.id + policy = data.aws_iam_policy_document.transfer_complete_policy_doc.json +} diff --git a/stacks/suspension-service/terraform/modules/end-of-transfer-service/output.tf b/stacks/suspension-service/terraform/modules/end-of-transfer-service/output.tf new file mode 100644 index 00000000..e921bb3b --- /dev/null +++ b/stacks/suspension-service/terraform/modules/end-of-transfer-service/output.tf @@ -0,0 +1,11 @@ +output "end_of_transfer_sns_topic" { + value = nonsensitive(data.aws_ssm_parameter.transfer_complete_topic_arn.value) +} + +output "transfer_complete_queue_name" { + value = aws_sqs_queue.transfer_complete.name +} + +output "transfer_complete_queue_arn" { + value = aws_sqs_queue.transfer_complete.arn +} \ No newline at end of file diff --git a/stacks/suspension-service/terraform/modules/end-of-transfer-service/queue.tf b/stacks/suspension-service/terraform/modules/end-of-transfer-service/queue.tf new file mode 100644 index 00000000..e57cfdc9 --- /dev/null +++ b/stacks/suspension-service/terraform/modules/end-of-transfer-service/queue.tf @@ -0,0 +1,46 @@ +locals { + transfer_complete_queue_name = "${var.environment}-${var.component_name}-transfer-complete" + transfer_complete_observability_queue_name = "${var.environment}-${var.component_name}-transfer-complete-observability" +} + +resource "aws_sqs_queue" "transfer_complete" { + name = local.transfer_complete_queue_name + message_retention_seconds = 259200 + kms_master_key_id = data.aws_ssm_parameter.transfer_complete_kms_key.value + receive_wait_time_seconds = 20 + visibility_timeout_seconds = 240 + + tags = { + Name = local.transfer_complete_queue_name + CreatedBy = var.repo_name + Environment = var.environment + } +} + +resource "aws_sqs_queue" "transfer_complete_observability" { + name = local.transfer_complete_observability_queue_name + message_retention_seconds = 259200 + kms_master_key_id = data.aws_ssm_parameter.transfer_complete_kms_key.value + receive_wait_time_seconds = 20 + visibility_timeout_seconds = 240 + + tags = { + Name = local.transfer_complete_observability_queue_name + CreatedBy = var.repo_name + Environment = var.environment + } +} + +resource "aws_sns_topic_subscription" "transfer_complete_topic" { + protocol = "sqs" + raw_message_delivery = true + topic_arn = data.aws_ssm_parameter.transfer_complete_topic_arn.value + endpoint = aws_sqs_queue.transfer_complete.arn +} + +resource "aws_sns_topic_subscription" "transfer_complete_observability_topic" { + protocol = "sqs" + raw_message_delivery = true + topic_arn = data.aws_ssm_parameter.transfer_complete_topic_arn.value + endpoint = aws_sqs_queue.transfer_complete_observability.arn +} \ No newline at end of file diff --git a/stacks/suspension-service/terraform/modules/end-of-transfer-service/variables.tf b/stacks/suspension-service/terraform/modules/end-of-transfer-service/variables.tf new file mode 100644 index 00000000..2e15cb96 --- /dev/null +++ b/stacks/suspension-service/terraform/modules/end-of-transfer-service/variables.tf @@ -0,0 +1,18 @@ +variable "region" { + type = string + default = "eu-west-2" +} + +variable "repo_name" { + type = string +} + +variable "environment" {} + +variable "component_name" {} + +variable "metric_namespace" {} + +variable "ecs_desired_count" { + default = 0 +} \ No newline at end of file diff --git a/stacks/suspension-service/terraform/sns-topic.tf b/stacks/suspension-service/terraform/sns-topic.tf index c95b0ef7..27ae303c 100644 --- a/stacks/suspension-service/terraform/sns-topic.tf +++ b/stacks/suspension-service/terraform/sns-topic.tf @@ -99,7 +99,7 @@ data "aws_sns_topic" "alarm_notifications" { } resource "aws_sns_topic_policy" "deny_http" { - for_each = toset(local.sns_arns) + for_each = local.sns_base_arns arn = each.value diff --git a/stacks/suspension-service/terraform/tasks b/stacks/suspension-service/terraform/tasks new file mode 100755 index 00000000..63a9f8f4 --- /dev/null +++ b/stacks/suspension-service/terraform/tasks @@ -0,0 +1,274 @@ +#!/usr/bin/env bash + +set -Eeo pipefail + +########################### +# Local Config Parameters # +########################### + +AWS_DEFAULT_REGION=eu-west-2 +IMAGE_REPO_NAME=repo/suspension-service +AWS_HELPERS_VERSION=0.2.27 + +#################################### +# Instance (Environment) Variables # +#################################### + +function check_env { + if [[ -z "${NHS_ENVIRONMENT}" ]]; then + echo "Must set NHS_ENVIRONMENT" + exit 1 + fi + + if [[ -z "${NHS_SERVICE}" ]]; then + echo "Must set NHS_SERVICE" + exit 1 + fi +} + +function set_image_tag() { + if [[ -z "${GO_DEPENDENCY_LABEL_APP}" ]]; then + export IMAGE_TAG=${GO_PIPELINE_LABEL:-$(git rev-parse HEAD | cut -c 1-8)} + else + export IMAGE_TAG=${GO_DEPENDENCY_LABEL_APP} + fi +} + +function get_aws_account_id { + AWS_ACCOUNT_ID=$(dojo -c Dojofile-infra "aws sts get-caller-identity | jq -r .Account") +} + +function configure_envs { + export AWS_REGION=${AWS_DEFAULT_REGION} + export LOCALSTACK_URL="http://localstack:4566" +} + +function configure_sonar_environment_variable { + export SONAR_TOKEN=$(_get_aws_ssm_secret "/repo/dev/output/suspensions-service/sonar_token") +} + +####################### +# Terraform Functions # +####################### + +function tf_init { + check_env + cd terraform + + terraform init -reconfigure \ + -backend-config key="${NHS_SERVICE}-${NHS_ENVIRONMENT}/terraform.tfstate" \ + -backend-config bucket="prm-deductions-${NHS_ENVIRONMENT}-terraform-state" \ + -backend-config dynamodb_table="prm-deductions-${NHS_ENVIRONMENT}-terraform-table" \ + -backend-config region=${AWS_DEFAULT_REGION} +} + +function tf_plan { + operation=$1 + + export IMAGE_TAG="51-4e60390b" + + terraform plan -var task_image_tag=$IMAGE_TAG -var-file="configs/end-of-transfer-service/end-of-transfer-service.tfvars" -var-file="configs/end-of-transfer-service/dev.tfvars" -out="end-of-transfer-service.tfplan" -destroy +} + +function tf_apply { + tf_init + terraform get # modules + terraform apply ${NHS_SERVICE}.tfplan + terraform output -json > tf-out.json +} + +############################ +# Docker Related Functions # +############################ + +function docker_login { + echo Logging in to Amazon ECR... + eval $(dojo -c Dojofile-infra "aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION") +} + +function configure_docker_repository_uri { + docker_login + get_aws_account_id + export REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/$IMAGE_REPO_NAME +} + +function build_docker_image() { + echo Build started on $(date) + set_image_tag + echo Building the Docker image... + docker build --build-arg UTILS_VERSION=$AWS_HELPERS_VERSION -t $REPOSITORY_URI:latest -t $REPOSITORY_URI:$IMAGE_TAG . + echo Build completed on `date` +} + +function configure_local_envs { + export LOG_LEVEL=debug + export NHS_ENVIRONMENT=local +} + +function die_localstack_die { + echo docker processes running: $(docker ps) + + DOCKER_CONTAINERS_ON_PORT=$(docker ps --filter publish=4566 -q) + + echo docker processes running on expected localstack port: $DOCKER_CONTAINERS_ON_PORT + + if [ -z "$DOCKER_CONTAINERS_ON_PORT" ]; then + echo no localstack nonsense going on, cool + else + echo trying to kill localstack... + docker stop $DOCKER_CONTAINERS_ON_PORT + fi +} + +########### +## TASKS ## +########### + +command="$1" +case "${command}" in + fetch_utils) + fetch_redaction_utils + ;; + die_localstack_die) + die_localstack_die + ;; + build_docker) + configure_envs + dojo "./tasks _build" + configure_docker_repository_uri + fetch_redaction_utils + build_docker_image + echo "Pushing the Docker image... $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG" + docker push $REPOSITORY_URI:$IMAGE_TAG + ;; + _build) + rm -rf build/ + gradle assemble + ;; + build) + dojo "./tasks _build" + ;; + _test_unit) + gradle test + ;; + test_unit) + dojo "./tasks _test_unit" + ;; + _test_integration) + gradle --info integration + ;; + test_integration) + configure_envs + die_localstack_die + dojo -c Dojofile-itest "./tasks _test_integration" + ;; + dojo_integration) + configure_envs + dojo -c Dojofile-itest "./tasks _test_integration" + ;; + _test_coverage) + gradle jacocoTestCoverageVerification + ;; + test_coverage) + configure_envs + die_localstack_die + dojo -c Dojofile-itest "./tasks _test_coverage" + ;; + _code_quality) + gradle check -x test -x integration + ;; + code_quality) + die_localstack_die + dojo -c Dojofile-itest "./tasks _code_quality" + ;; + _test_all) + gradle clean test integration jacocoTestCoverageVerification check + ;; + test_all) + configure_envs + die_localstack_die + dojo -c Dojofile-itest "./tasks _test_all" + ;; + run_local) + configure_envs + ./gradlew bootRun + ;; + run_localstack_local) + docker-compose -f docker-compose.localstack-local.yaml up -d + ;; + _tf) + export NHS_SERVICE=suspension-service + + tf_init + bash + ;; + tf) + check_env + dojo -c Dojofile-infra "./tasks _tf" + ;; + _tf_plan_suspension) + export NHS_SERVICE=suspension-service + _assume_environment_role $NHS_ENVIRONMENT + tf_plan "$2" + ;; + tf_plan_suspension) + export NHS_SERVICE=suspension-service + check_env + dojo -c Dojofile-infra "./tasks _tf_plan $2" + ;; + _tf_plan_transfer) + export NHS_SERVICE=end-of-transfer-service + tf_plan "$2" + ;; + tf_plan_transfer) + export NHS_SERVICE=end-of-transfer-service + export NHS_ENVIRONMENT=dev + ./tasks _tf_plan $2 + ;; + _tf_plan) + tf_plan "$2" + ;; + tf_plan) + check_env + dojo -c Dojofile-infra "./tasks _tf_plan $2" + ;; + _tf_apply) + _assume_environment_role $NHS_ENVIRONMENT + tf_apply + ;; + tf_apply) + check_env + dojo -c Dojofile-infra "./tasks _tf_apply" + ;; + promote_docker_image) + check_env + set_image_tag + promote_docker_image "$IMAGE_REPO_NAME:$IMAGE_TAG" "$NHS_ENVIRONMENT" + ;; + _wait_ecs) + _assume_environment_role $NHS_ENVIRONMENT + aws ecs wait services-stable \ + --region $AWS_DEFAULT_REGION \ + --cluster $NHS_ENVIRONMENT-${NHS_SERVICE}-ecs-cluster \ + --service $NHS_ENVIRONMENT-${NHS_SERVICE} + ;; + wait_ecs) + check_env + dojo -c Dojofile-infra "./tasks _wait_ecs" + ;; + _run_sonar) + ./gradlew build sonar --info + ;; + run_sonar) + die_localstack_die + _assume_environment_role $NHS_ENVIRONMENT + configure_envs + configure_sonar_environment_variable + dojo -c Dojofile-itest "./tasks _run_sonar" + ;; + *) + echo "Invalid command: '${command}'" + exit 1 + ;; +esac +set +e