diff --git a/.github/workflows/data-replication-pipeline.yml b/.github/workflows/data-replication-pipeline.yml index df1fc271ad..3137c91959 100644 --- a/.github/workflows/data-replication-pipeline.yml +++ b/.github/workflows/data-replication-pipeline.yml @@ -1,5 +1,5 @@ -name: Data replication pipeline -run-name: ${{ inputs.deployment_type }} for data replication resources for ${{ inputs.environment }} +name: Deploy data replication +run-name: Deploy data replication to ${{ inputs.environment }} on: workflow_dispatch: @@ -15,227 +15,152 @@ on: - qa - sandbox-alpha - sandbox-beta - deployment_type: - description: Deployment type - required: true - type: choice - options: - - Deployment with DB recreation - - Application only deployment - image_tag: - description: Docker image tag to deploy + image-tag: + description: Docker image tag to deploy (if not provided, will build from git-ref-to-deploy) required: false type: string - db_snapshot_arn: - description: ARN of the DB snapshot to use (optional) + git-ref-to-deploy: + description: Git ref (branch/tag/SHA) to deploy required: false type: string - egress_cidr: - description: CIDR blocks to allow egress traffic. - type: string - required: true - default: "[]" - take_db_snapshot: - description: Take a new DB snapshot before creating the environment - type: boolean - default: false + +permissions: {} env: aws_role: ${{ inputs.environment == 'production' && 'arn:aws:iam::820242920762:role/GithubDeployDataReplicationInfrastructure' || 'arn:aws:iam::393416225559:role/GithubDeployDataReplicationInfrastructure' }} - db_snapshot_role: ${{ inputs.environment == 'production' - && 'arn:aws:iam::820242920762:role/DatabaseSnapshotRole' - || 'arn:aws:iam::393416225559:role/DatabaseSnapshotRole' }} - -defaults: - run: - working-directory: terraform/data_replication + aws_account_id: ${{ inputs.environment == 'production' && '820242920762' || '393416225559' }} concurrency: group: deploy-data-replica-${{ inputs.environment }} jobs: - prepare-db-replica: - if: ${{ inputs.deployment_type == 'Deployment with DB recreation' }} - name: Prepare data replica + validate-inputs: runs-on: ubuntu-latest - permissions: - id-token: write + permissions: {} steps: - - name: Checkout code - uses: actions/checkout@v5 - - name: Assume DB Snapshot role - if: inputs.take_db_snapshot - uses: aws-actions/configure-aws-credentials@v5 - with: - role-to-assume: ${{ env.db_snapshot_role }} - aws-region: eu-west-2 - - name: Take DB snapshot - if: inputs.take_db_snapshot - run: | - set -e - snapshot_identifier=snapshot-for-data-replication-$(date +"%Y-%m-%d-%H-%M-%S") - aws rds create-db-cluster-snapshot --db-cluster-identifier mavis-${{ inputs.environment }} --db-cluster-snapshot-identifier $snapshot_identifier - echo "Waiting for snapshot to be available. This can take a while." - aws rds wait db-cluster-snapshot-available --db-cluster-snapshot-identifier $snapshot_identifier - echo "New snapshot is now available" - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v5 - with: - role-to-assume: ${{ env.aws_role }} - aws-region: eu-west-2 - - name: Get latest snapshot - id: get-latest-snapshot + - name: Validate inputs run: | - set -e - if [ -z "${{ inputs.db_snapshot_arn }}" ]; then - echo "No snapshot ARN provided, fetching the latest snapshot" - SNAPSHOT_ARN=$(aws rds describe-db-cluster-snapshots \ - --query "DBClusterSnapshots[?DBClusterIdentifier=='mavis-${{ inputs.environment }}'].[DBClusterSnapshotArn, SnapshotCreateTime]" \ - --output text | sort -k2 -r | head -n 1 | cut -f1) - - if [ -z "$SNAPSHOT_ARN" ]; then - echo "No snapshots found for mavis-${{ inputs.environment }}" - exit 1 - fi - else - echo "Using provided snapshot ARN: ${{ inputs.db_snapshot_arn }}" - SNAPSHOT_ARN="${{ inputs.db_snapshot_arn }}" + if [[ "${{ inputs.environment }}" == "preview" || "${{ inputs.environment }}" == "production" ]]; then + if [[ -z "${{ inputs.git-ref-to-deploy }}" && -z "${{ inputs.image-tag }}" ]]; then + echo "Error: Either git-ref-to-deploy or image-tag is required for preview and production environment." + exit 1 + fi fi - echo "Using snapshot ARN: $SNAPSHOT_ARN" - echo "SNAPSHOT_ARN=$SNAPSHOT_ARN" >> $GITHUB_OUTPUT - - name: Install terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.13.3 - outputs: - SNAPSHOT_ARN: ${{ steps.get-latest-snapshot.outputs.SNAPSHOT_ARN }} - prepare-webapp: - name: Prepare webapp + determine-git-sha: runs-on: ubuntu-latest - permissions: - id-token: write + permissions: {} + needs: validate-inputs + outputs: + git-sha: ${{ steps.get-git-sha.outputs.git-sha }} steps: - name: Checkout code uses: actions/checkout@v5 - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v5 with: - role-to-assume: ${{ env.aws_role }} - aws-region: eu-west-2 - - name: ECR login - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - name: Get docker image digest - id: get-docker-image-digest - run: | - set -e - DOCKER_IMAGE="${{ steps.login-ecr.outputs.registry }}/mavis/webapp:${{ inputs.image_tag || github.sha }}" - docker pull "$DOCKER_IMAGE" - DOCKER_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$DOCKER_IMAGE") - DIGEST="${DOCKER_DIGEST#*@}" - echo "DIGEST=$DIGEST" >> $GITHUB_OUTPUT - outputs: - DOCKER_DIGEST: ${{ steps.get-docker-image-digest.outputs.DIGEST }} + ref: ${{ inputs.git-ref-to-deploy || github.sha }} + - name: Get git sha + id: get-git-sha + run: echo "git-sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + build-and-push-image: + if: ${{ !inputs.image-tag }} + permissions: + id-token: write + needs: determine-git-sha + uses: ./.github/workflows/build-and-push-image.yml + with: + git-sha: ${{ needs.determine-git-sha.outputs.git-sha }} - plan: - name: Terraform plan + prepare-deployment: + name: Prepare deployment runs-on: ubuntu-latest - needs: - - prepare-db-replica - - prepare-webapp - if: ${{ !cancelled() && - (needs.prepare-db-replica.result == 'success' || needs.prepare-db-replica.result == 'skipped') && - needs.prepare-webapp.result == 'success' }} - env: - SNAPSHOT_ARN: ${{ needs.prepare-db-replica.outputs.SNAPSHOT_ARN }} - DB_SECRET_ARN: ${{ needs.prepare-db-replica.outputs.DB_SECRET_ARN || 'arn:aws:secretsmanager:eu-west-2:000000000000:secret:placeholder' }} - DOCKER_DIGEST: ${{ needs.prepare-webapp.outputs.DOCKER_DIGEST }} - REPLACE_DB_CLUSTER: ${{ inputs.deployment_type == 'Deployment with DB recreation' }} + needs: [determine-git-sha, build-and-push-image] + if: ${{ always() && (needs.build-and-push-image.result == 'success' || needs.build-and-push-image.result == 'skipped') }} permissions: id-token: write steps: - name: Checkout code uses: actions/checkout@v5 + with: + ref: ${{ inputs.git-ref-to-deploy || github.sha }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: role-to-assume: ${{ env.aws_role }} aws-region: eu-west-2 - - name: Install terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.13.3 - - name: Get db secret arn - id: get-db-secret-arn - working-directory: terraform/app + - name: Get image digest + id: get-image-digest run: | - terraform init -backend-config="env/${{ inputs.environment }}-backend.hcl" -upgrade - DB_SECRET_ARN=$(terraform output --raw db_secret_arn) - echo "DB_SECRET_ARN=$DB_SECRET_ARN" >> $GITHUB_OUTPUT - - name: Terraform Plan - id: plan + digest=$(aws ecr describe-images \ + --repository-name mavis/webapp \ + --image-ids imageTag=${{ inputs.image-tag || needs.determine-git-sha.outputs.git-sha }} \ + --query 'imageDetails[0].imageDigest' \ + --output text) + echo "digest=$digest" >> $GITHUB_OUTPUT + - name: Parse environment variables + id: parse-environment-variables run: | - set -eo pipefail - terraform init -backend-config="env/${{ inputs.environment }}-backend.hcl" -upgrade - - CIDR_BLOCKS='${{ inputs.egress_cidr }}' - PLAN_ARGS=( - "plan" - "-var=image_digest=${{ env.DOCKER_DIGEST }}" - "-var=db_secret_arn=${{ steps.get-db-secret-arn.outputs.DB_SECRET_ARN }}" - "-var=imported_snapshot=${{ env.SNAPSHOT_ARN }}" - "-var-file=env/${{ inputs.environment }}.tfvars" - "-var=allowed_egress_cidr_blocks=$CIDR_BLOCKS" - "-out=${{ runner.temp }}/tfplan" - ) - - if [ "${{ env.REPLACE_DB_CLUSTER }}" = "true" ]; then - PLAN_ARGS+=("-replace" "aws_rds_cluster.cluster") - fi - terraform "${PLAN_ARGS[@]}" | tee ${{ runner.temp }}/tf_stdout - - name: Upload artifact + { + echo 'parsed_env_vars<> "$GITHUB_OUTPUT" + - name: Populate web task definition + id: create-task-definition + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition-family: "mavis-data-replication-task-definition-${{ inputs.environment }}-template" + container-name: "application" + image: "${{ env.aws_account_id }}.dkr.ecr.eu-west-2.amazonaws.com/mavis/webapp@${{ steps.get-image-digest.outputs.digest }}" + environment-variables: ${{ steps.parse-environment-variables.outputs.parsed_env_vars }} + - name: Rename task definition file + run: mv ${{ steps.create-task-definition.outputs.task-definition }} ${{ runner.temp }}/data-replication-task-definition.json + - name: Upload artifact for data-replication task definition uses: actions/upload-artifact@v4 with: - name: tfplan_infrastructure-${{ inputs.environment }} - path: ${{ runner.temp }}/tfplan + name: ${{ inputs.environment }}-data-replication-task-definition + path: ${{ runner.temp }}/data-replication-task-definition.json - apply: - name: Terraform apply + approve-deployments: + name: Wait for approval if required runs-on: ubuntu-latest - needs: plan - if: ${{ !cancelled() && needs.plan.result == 'success' }} + needs: prepare-deployment environment: ${{ inputs.environment }} + steps: + - run: echo "Proceeding with deployment to ${{ inputs.environment }} environment" + + deploy-data-replication: + name: Deploy data-replication service + runs-on: ubuntu-latest + needs: [prepare-deployment, approve-deployments] permissions: id-token: write steps: - - name: Checkout code - uses: actions/checkout@v5 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: role-to-assume: ${{ env.aws_role }} aws-region: eu-west-2 - - name: Download artifact + - name: Download data-replication task definition artifact uses: actions/download-artifact@v5 with: - name: tfplan_infrastructure-${{ inputs.environment }} path: ${{ runner.temp }} - - name: Install terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.13.3 - - name: Apply the changes + name: ${{ inputs.environment }}-data-replication-task-definition + - name: Change family of task definition run: | - set -e - terraform init -backend-config="env/${{ inputs.environment }}-backend.hcl" -upgrade - terraform apply ${{ runner.temp }}/tfplan - - name: Deploy db-access-service - run: | - task_definition_arn=$(terraform output -raw task_definition_arn) - aws ecs update-service \ - --cluster mavis-${{ inputs.environment }}-data-replication \ - --service mavis-${{ inputs.environment }}-data-replication \ - --task-definition $task_definition_arn + file_path="${{ runner.temp }}/data-replication-task-definition.json" + family_name="mavis-data-replication-task-definition-${{ inputs.environment }}" + echo "$(jq --arg f "$family_name" '.family = $f' "$file_path")" > "$file_path" + - name: Deploy data-replication service + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 + with: + task-definition: ${{ runner.temp }}/data-replication-task-definition.json + cluster: mavis-${{ inputs.environment }}-data-replication + service: mavis-${{ inputs.environment }}-data-replication + force-new-deployment: true + wait-for-service-stability: true diff --git a/.github/workflows/deploy-application.yml b/.github/workflows/deploy-application.yml index 5defcd928f..26693dd738 100644 --- a/.github/workflows/deploy-application.yml +++ b/.github/workflows/deploy-application.yml @@ -16,7 +16,7 @@ on: - production - sandbox-alpha - sandbox-beta - server_types: + server-types: description: Server types to deploy required: true type: choice @@ -25,18 +25,30 @@ on: - web - sidekiq default: all + git-ref-to-deploy: + description: The git commit SHA to deploy. + required: false + type: string + image-tag: + description: Docker image tag to deploy (if not provided, will build from git-ref-to-deploy) + required: false + type: string workflow_call: inputs: environment: required: true type: string - server_types: + server-types: required: true type: string - git_sha_to_deploy: + git-ref-to-deploy: description: The git commit SHA to deploy. required: true type: string + image-tag: + description: Docker image tag to deploy + required: false + type: string permissions: {} @@ -45,124 +57,165 @@ concurrency: env: aws-role: ${{ inputs.environment == 'production' - && 'arn:aws:iam::820242920762:role/GithubDeployMavisAndInfrastructure' - || 'arn:aws:iam::393416225559:role/GithubDeployMavisAndInfrastructure' }} + && 'arn:aws:iam::820242920762:role/GithubDeployECSService' + || 'arn:aws:iam::393416225559:role/GithubDeployECSService' }} + aws-account-id: ${{ inputs.environment == 'production' && '820242920762' || '393416225559' }} + cluster-name: mavis-${{ inputs.environment }} + app-version: ${{ inputs.git-ref-to-deploy == '' && 'unknown' || inputs.git-ref-to-deploy }} jobs: + determine-git-sha: + runs-on: ubuntu-latest + permissions: {} + outputs: + git-sha: ${{ steps.get-git-sha.outputs.git-sha }} + steps: + - name: Checkout code + uses: actions/checkout@v5 + with: + ref: ${{ inputs.git-ref-to-deploy || github.sha }} + - name: Get git sha + id: get-git-sha + run: echo "git-sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + build-and-push-image: + if: ${{ !inputs.image-tag }} + permissions: + id-token: write + needs: determine-git-sha + uses: ./.github/workflows/build-and-push-image.yml + with: + git-sha: ${{ needs.determine-git-sha.outputs.git-sha }} + prepare-deployment: name: Prepare deployment runs-on: ubuntu-latest - environment: ${{ inputs.environment }} + needs: [determine-git-sha, build-and-push-image] + if: ${{ always() && (needs.build-and-push-image.result == 'success' || needs.build-and-push-image.result == 'skipped') }} permissions: id-token: write + strategy: + fail-fast: true + matrix: + service: ${{ inputs.server-types == 'all' && fromJSON('["web", "sidekiq"]') || fromJSON(format('["{0}"]', inputs.server-types)) }} steps: - name: Checkout code uses: actions/checkout@v5 + id: checkout-code with: - ref: ${{ inputs.git_sha_to_deploy || github.sha }} + ref: ${{ inputs.git-ref-to-deploy || github.sha }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: role-to-assume: ${{ env.aws-role }} aws-region: eu-west-2 - - name: Install terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: 1.13.3 - - name: Get terraform output - id: terraform-output - working-directory: terraform/app + - name: Get image digest + id: get-image-digest run: | - set -e - terraform init -backend-config=env/${{ inputs.environment }}-backend.hcl -reconfigure - terraform output -json | jq -r ' - "s3_bucket=" + .s3_bucket.value, - "s3_key=" + .s3_key.value, - "application=" + .codedeploy_application_name.value, - "application_group=" + .codedeploy_deployment_group_name.value, - "cluster_name=" + .ecs_variables.value.cluster_name, - "sidekiq_service=" + .ecs_variables.value.sidekiq.service_name, - "sidekiq_task_definition=" + .ecs_variables.value.sidekiq.task_definition.arn - ' > ${{ runner.temp }}/DEPLOYMENT_ENVS - - name: Upload Artifact + digest=$(aws ecr describe-images \ + --repository-name mavis/webapp \ + --image-ids imageTag=${{ inputs.image-tag || needs.determine-git-sha.outputs.git-sha }} \ + --query 'imageDetails[0].imageDigest' \ + --output text) + echo "digest=$digest" >> $GITHUB_OUTPUT + - name: Populate web task definition + id: create-task-definition + uses: aws-actions/amazon-ecs-render-task-definition@v1 + with: + task-definition-family: "mavis-${{ matrix.service }}-task-definition-${{ inputs.environment }}-template" + container-name: "application" + image: "${{ env.aws-account-id }}.dkr.ecr.eu-west-2.amazonaws.com/mavis/webapp@${{ steps.get-image-digest.outputs.digest }}" + environment-variables: | + APP_VERSION=app-version + - name: Rename task definition file + run: mv ${{ steps.create-task-definition.outputs.task-definition }} ${{ runner.temp }}/${{ matrix.service }}-task-definition.json + - name: Upload artifact for ${{ matrix.service }} task definition uses: actions/upload-artifact@v4 with: - name: DEPLOYMENT_ENVS-${{ inputs.environment }} - path: ${{ runner.temp }}/DEPLOYMENT_ENVS + name: ${{ inputs.environment }}-${{ matrix.service }}-task-definition + path: ${{ runner.temp }}/${{ matrix.service }}-task-definition.json - create-web-deployment: - name: Create web deployment + approve-deployments: + name: Wait for approval if required runs-on: ubuntu-latest needs: prepare-deployment - if: inputs.server_types == 'web' || inputs.server_types == 'all' + environment: ${{ inputs.environment }} + steps: + - run: echo "Proceeding with deployment to ${{ inputs.environment }} environment" + + deploy-web: + name: Deploy web service + runs-on: ubuntu-latest + if: ${{ inputs.server-types == 'web' || inputs.server-types == 'all' }} + needs: [prepare-deployment, approve-deployments] permissions: id-token: write steps: - - name: Download artifact - uses: actions/download-artifact@v5 - with: - name: DEPLOYMENT_ENVS-${{ inputs.environment }} - path: ${{ runner.temp }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: role-to-assume: ${{ env.aws-role }} aws-region: eu-west-2 - - name: Trigger CodeDeploy deployment + - name: Checkout code + uses: actions/checkout@v5 + - name: Download web task definition artifact + uses: actions/download-artifact@v5 + with: + path: ${{ runner.temp }} + name: ${{ inputs.environment }}-web-task-definition + - name: Change family of task definition run: | - set -e - source ${{ runner.temp }}/DEPLOYMENT_ENVS - deployment_id=$(aws deploy create-deployment \ - --application-name "$application" --deployment-group-name "$application_group" \ - --s3-location bucket="$s3_bucket",key="$s3_key",bundleType=yaml | jq -r .deploymentId) - echo "Deployment started: $deployment_id" - echo "deployment_id=$deployment_id" >> $GITHUB_ENV - - name: Wait up to 30 minutes for deployment to complete + file_path="${{ runner.temp }}/web-task-definition.json" + family_name="mavis-web-task-definition-${{ inputs.environment }}" + echo "$(jq --arg f "$family_name" '.family = $f' "$file_path")" > "$file_path" + - name: Register web task definition + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 + with: + task-definition: ${{ runner.temp }}/web-task-definition.json + - name: Deploy web service with CodeDeploy + id: deploy-web-service + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 + with: + task-definition: ${{ runner.temp }}/web-task-definition.json + codedeploy-appspec: config/templates/appspec.yaml + cluster: ${{ env.cluster-name }} + service: mavis-${{ inputs.environment }}-web + codedeploy-application: mavis-${{ inputs.environment }} + codedeploy-deployment-group: blue-green-group-${{ inputs.environment }} + - name: Wait for deployment to complete run: | - set -e - aws deploy wait deployment-successful --deployment-id "$deployment_id" + echo "Waiting for CodeDeploy deployment ${{ steps.deploy-web-service.outputs.codedeploy-deployment-id }} to complete..." + aws deploy wait deployment-successful --deployment-id "${{ steps.deploy-web-service.outputs.codedeploy-deployment-id }}" echo "Deployment successful" - create-sidekiq-deployment: - name: Create sidekiq deployment + deploy-sidekiq: + name: Deploy sidekiq service runs-on: ubuntu-latest - needs: prepare-deployment - if: inputs.server_types == 'sidekiq' || inputs.server_types == 'all' + if: ${{ inputs.server-types == 'sidekiq' || inputs.server-types == 'all' }} + needs: [prepare-deployment, approve-deployments] permissions: id-token: write steps: - - name: Download Artifact - uses: actions/download-artifact@v5 - with: - name: DEPLOYMENT_ENVS-${{ inputs.environment }} - path: ${{ runner.temp }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: role-to-assume: ${{ env.aws-role }} aws-region: eu-west-2 - - name: Trigger ECS Deployment - run: | - set -e - source ${{ runner.temp }}/DEPLOYMENT_ENVS - DEPLOYMENT_ID=$(aws ecs update-service --cluster $cluster_name --service $sidekiq_service \ - --task-definition $sidekiq_task_definition --force-new-deployment \ - --query 'service.deployments[?rolloutState==`IN_PROGRESS`].[id][0]' --output text) - echo "Deployment started: $DEPLOYMENT_ID" - echo "deployment_id=$DEPLOYMENT_ID" >> $GITHUB_ENV - - name: Wait for deployment to complete + - name: Download sidekiq task definition artifact + uses: actions/download-artifact@v5 + with: + path: ${{ runner.temp }} + name: ${{ inputs.environment }}-sidekiq-task-definition + - name: Change family of task definition run: | - set -e - source ${{ runner.temp }}/DEPLOYMENT_ENVS - DEPLOYMENT_STATE=IN_PROGRESS - while [ "$DEPLOYMENT_STATE" == "IN_PROGRESS" ]; do - echo "Waiting for deployment to complete..." - sleep 30 - DEPLOYMENT_STATE="$(aws ecs describe-services --cluster $cluster_name --services $sidekiq_service \ - --query "services[0].deployments[?id == \`$deployment_id\`].[rolloutState][0]" --output text)" - done - if [ "$DEPLOYMENT_STATE" != "COMPLETED" ]; then - echo "Deployment failed with state: $DEPLOYMENT_STATE" - exit 1 - fi - echo "Deployment successful" + file_path="${{ runner.temp }}/sidekiq-task-definition.json" + family_name="mavis-sidekiq-task-definition-${{ inputs.environment }}" + echo "$(jq --arg f "$family_name" '.family = $f' "$file_path")" > "$file_path" + - name: Deploy sidekiq service + uses: aws-actions/amazon-ecs-deploy-task-definition@v2 + with: + task-definition: ${{ runner.temp }}/sidekiq-task-definition.json + cluster: ${{ env.cluster-name }} + service: mavis-${{ inputs.environment }}-sidekiq + force-new-deployment: true + wait-for-service-stability: true diff --git a/.github/workflows/deploy-infrastructure.yml b/.github/workflows/deploy-infrastructure.yml index 4b5068acc0..023c936381 100644 --- a/.github/workflows/deploy-infrastructure.yml +++ b/.github/workflows/deploy-infrastructure.yml @@ -8,12 +8,19 @@ on: description: Deployment environment required: true type: string - image_tag: - required: false + git-ref-to-deploy: + required: true type: string - git_ref_to_deploy: + workflow_dispatch: + inputs: + environment: + description: Deployment environment required: true type: string + git-ref-to-deploy: + description: The git commit SHA to deploy. + required: false + type: string permissions: {} @@ -21,12 +28,10 @@ concurrency: group: deploy-infrastructure-${{ inputs.environment }} env: - aws_role: ${{ inputs.environment == 'production' + aws-role: ${{ inputs.environment == 'production' && 'arn:aws:iam::820242920762:role/GithubDeployMavisAndInfrastructure' || 'arn:aws:iam::393416225559:role/GithubDeployMavisAndInfrastructure' }} - aws_account_id: ${{ inputs.environment == 'production' - && '820242920762' || '393416225559' }} - git_ref_to_deploy: ${{ inputs.git_ref_to_deploy || github.ref_name }} + git-ref-to-deploy: ${{ inputs.git-ref-to-deploy || github.ref_name }} defaults: run: @@ -42,31 +47,12 @@ jobs: - name: Checkout code uses: actions/checkout@v5 with: - ref: ${{ env.git_ref_to_deploy }} + ref: ${{ env.git-ref-to-deploy }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: - role-to-assume: ${{ env.aws_role }} + role-to-assume: ${{ env.aws-role }} aws-region: eu-west-2 - - name: Set image tag - run: | - IMAGE_TAG="${{ inputs.image_tag || github.sha }}" - echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV - - name: Login to ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - - name: Pull Docker image - run: | - set -e - DOCKER_IMAGE="${{ steps.login-ecr.outputs.registry }}/mavis/webapp:${IMAGE_TAG}" - docker pull "$DOCKER_IMAGE" - echo "DOCKER_IMAGE=$DOCKER_IMAGE" >> $GITHUB_ENV - - name: Extract image digest - run: | - set -e - DOCKER_DIGEST=$(docker inspect --format='{{index .RepoDigests 0}}' "$DOCKER_IMAGE") - DIGEST="${DOCKER_DIGEST#*@}" - echo "DIGEST=$DIGEST" >> $GITHUB_ENV - name: Install terraform uses: hashicorp/setup-terraform@v3 with: @@ -78,7 +64,7 @@ jobs: run: | set -e terraform init -backend-config="env/${{ inputs.environment }}-backend.hcl" -upgrade - terraform plan -var="image_digest=$DIGEST" -var="app_version=${{ env.git_ref_to_deploy }}" \ + terraform plan \ -var-file="env/${{ inputs.environment }}.tfvars" \ -out ${{ runner.temp }}/tfplan | tee ${{ runner.temp }}/tf_stdout TF_EXIT_CODE=${PIPESTATUS[0]} @@ -104,11 +90,11 @@ jobs: - name: Checkout code uses: actions/checkout@v5 with: - ref: ${{ env.git_ref_to_deploy }} + ref: ${{ env.git-ref-to-deploy }} - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: - role-to-assume: ${{ env.aws_role }} + role-to-assume: ${{ env.aws-role }} aws-region: eu-west-2 - name: Download artifact uses: actions/download-artifact@v5 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 81f5d5351d..86f63d9505 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,5 +1,5 @@ name: Deploy -run-name: Deploy ${{ inputs.git_ref_to_deploy || github.ref_name }} to ${{ inputs.environment }} +run-name: Deploy ${{ inputs.git-ref-to-deploy || github.ref_name }} to ${{ inputs.environment }} concurrency: group: deploy-${{ inputs.environment }} @@ -10,12 +10,12 @@ on: environment: required: true type: string - server_types: + server-types: required: true type: string workflow_dispatch: inputs: - git_ref_to_deploy: + git-ref-to-deploy: description: | # Use blank unicode character (U+2800) to force line-break Use code from: ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ @@ -33,7 +33,7 @@ on: - production - sandbox-alpha - sandbox-beta - server_types: + server-types: description: Server types to deploy required: true type: choice @@ -45,7 +45,7 @@ on: default: all env: - account_id: ${{ inputs.environment == 'production' && '820242920762' || '393416225559' }} + account-id: ${{ inputs.environment == 'production' && '820242920762' || '393416225559' }} jobs: validate-inputs: @@ -55,8 +55,8 @@ jobs: - name: Validate inputs run: | if [[ "${{ inputs.environment }}" == "preview" || "${{ inputs.environment }}" == "production" ]]; then - if [[ -z "${{ inputs.git_ref_to_deploy }}" ]]; then - echo "Error: git_ref_to_deploy is required for preview and production environments." + if [[ -z "${{ inputs.git-ref-to-deploy }}" ]]; then + echo "Error: git-ref-to-deploy is required for preview and production environments." exit 1 fi fi @@ -70,7 +70,7 @@ jobs: - name: Checkout code uses: actions/checkout@v5 with: - ref: ${{ inputs.git_ref_to_deploy || github.sha }} + ref: ${{ inputs.git-ref-to-deploy || github.sha }} - name: Get git sha id: get-git-sha run: echo "git-sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -97,18 +97,18 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: - role-to-assume: arn:aws:iam::${{ env.account_id }}:role/GithubDeployMavisAndInfrastructure + role-to-assume: arn:aws:iam::${{ env.account-id }}:role/GithubDeployMavisAndInfrastructure aws-region: eu-west-2 - name: Compare permissions id: compare-permissions run: | source ./scripts/validate-github-actions-policy.sh - validate_policies arn:aws:iam::${{ env.account_id }}:policy/DeployMavisResources ./account/resources/iam_policy_DeployMavisResources.json + validate_policies arn:aws:iam::${{ env.account-id }}:policy/DeployMavisResources ./account/resources/iam_policy_DeployMavisResources.json exit $? update-permissions: runs-on: ubuntu-latest needs: validate-permissions - if: always() && (inputs.environment == 'production' || inputs.environment == 'preview') && needs.validate-permissions.result == 'failure' + if: ${{ !cancelled() && (inputs.environment == 'production' || inputs.environment == 'preview') && needs.validate-permissions.result == 'failure' }} environment: ${{ inputs.environment }} defaults: run: @@ -123,10 +123,10 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v5 with: - role-to-assume: arn:aws:iam::${{ env.account_id }}:role/GithubDeployMavisAndInfrastructure + role-to-assume: arn:aws:iam::${{ env.account-id }}:role/GithubDeployMavisAndInfrastructure aws-region: eu-west-2 - name: Update IAM policy - run: ./scripts/update-github-actions-policy.sh arn:aws:iam::${{ env.account_id }}:policy/DeployMavisResources ./account/resources/iam_policy_DeployMavisResources.json + run: ./scripts/update-github-actions-policy.sh arn:aws:iam::${{ env.account-id }}:policy/DeployMavisResources ./account/resources/iam_policy_DeployMavisResources.json deploy-infrastructure: permissions: id-token: write @@ -137,21 +137,20 @@ jobs: validate-permissions, update-permissions, ] - if: always() && + if: ${{ !cancelled() && ((inputs.environment != 'production' && inputs.environment != 'preview') || - needs.validate-permissions.result == 'success' || needs.update-permissions.result == 'success') + needs.validate-permissions.result == 'success' || needs.update-permissions.result == 'success') }} uses: ./.github/workflows/deploy-infrastructure.yml with: environment: ${{ inputs.environment }} - image_tag: ${{ needs.determine-git-sha.outputs.git-sha }} - git_ref_to_deploy: ${{ inputs.git_ref_to_deploy || github.ref_name }} + git-ref-to-deploy: ${{ inputs.git-ref-to-deploy || github.ref_name }} deploy-application: permissions: id-token: write needs: [deploy-infrastructure, determine-git-sha] - if: always() && inputs.server_types != 'none' && needs.deploy-infrastructure.result == 'success' + if: ${{ !cancelled() && inputs.server-types != 'none' && needs.deploy-infrastructure.result == 'success' }} uses: ./.github/workflows/deploy-application.yml with: environment: ${{ inputs.environment }} - server_types: ${{ inputs.server_types }} - git_sha_to_deploy: ${{ needs.determine-git-sha.outputs.git-sha }} + server-types: ${{ inputs.server-types }} + git-ref-to-deploy: ${{ inputs.git-ref-to-deploy || github.ref_name }} diff --git a/.github/workflows/refresh-data-replication.yml b/.github/workflows/refresh-data-replication.yml new file mode 100644 index 0000000000..3405d7ea9d --- /dev/null +++ b/.github/workflows/refresh-data-replication.yml @@ -0,0 +1,186 @@ +name: Refresh Data Replication +run-name: Refresh data replication resources & data for ${{ inputs.environment }} + +on: + workflow_dispatch: + inputs: + environment: + description: Deployment environment + required: true + type: choice + options: + - training + - production + - test + - qa + - sandbox-alpha + - sandbox-beta + db-snapshot-arn: + description: ARN of the DB snapshot to use (optional) + required: false + type: string + egress-cidr: + description: CIDR blocks to allow egress traffic. + type: string + required: true + default: "[]" + take-db-snapshot: + description: Take a new DB snapshot before creating the environment + type: boolean + default: false + +permissions: {} + +env: + aws_role: ${{ inputs.environment == 'production' + && 'arn:aws:iam::820242920762:role/GithubDeployDataReplicationInfrastructure' + || 'arn:aws:iam::393416225559:role/GithubDeployDataReplicationInfrastructure' }} + db_snapshot_role: ${{ inputs.environment == 'production' + && 'arn:aws:iam::820242920762:role/DatabaseSnapshotRole' + || 'arn:aws:iam::393416225559:role/DatabaseSnapshotRole' }} + +defaults: + run: + working-directory: terraform/data_replication + +concurrency: + group: deploy-data-replica-${{ inputs.environment }} + +jobs: + prepare-db-replica: + name: Prepare data replica + runs-on: ubuntu-latest + permissions: + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v5 + - name: Assume DB Snapshot role + if: inputs.take-db-snapshot + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ env.db_snapshot_role }} + aws-region: eu-west-2 + - name: Take DB snapshot + if: inputs.take-db-snapshot + run: | + set -e + snapshot_identifier=snapshot-for-data-replication-$(date +"%Y-%m-%d-%H-%M-%S") + aws rds create-db-cluster-snapshot --db-cluster-identifier mavis-${{ inputs.environment }} --db-cluster-snapshot-identifier $snapshot_identifier + echo "Waiting for snapshot to be available. This can take a while." + aws rds wait db-cluster-snapshot-available --db-cluster-snapshot-identifier $snapshot_identifier + echo "New snapshot is now available" + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ env.aws_role }} + aws-region: eu-west-2 + - name: Get latest snapshot + id: get-latest-snapshot + run: | + set -e + if [ -z "${{ inputs.db-snapshot-arn }}" ]; then + echo "No snapshot ARN provided, fetching the latest snapshot" + SNAPSHOT_ARN=$(aws rds describe-db-cluster-snapshots \ + --query "DBClusterSnapshots[?DBClusterIdentifier=='mavis-${{ inputs.environment }}'].[DBClusterSnapshotArn, SnapshotCreateTime]" \ + --output text | sort -k2 -r | head -n 1 | cut -f1) + + if [ -z "$SNAPSHOT_ARN" ]; then + echo "No snapshots found for mavis-${{ inputs.environment }}" + exit 1 + fi + else + echo "Using provided snapshot ARN: ${{ inputs.db-snapshot-arn }}" + SNAPSHOT_ARN="${{ inputs.db-snapshot-arn }}" + fi + echo "Using snapshot ARN: $SNAPSHOT_ARN" + echo "SNAPSHOT_ARN=$SNAPSHOT_ARN" >> $GITHUB_OUTPUT + - name: Install terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.11.4 + outputs: + SNAPSHOT_ARN: ${{ steps.get-latest-snapshot.outputs.SNAPSHOT_ARN }} + + plan: + name: Terraform plan + runs-on: ubuntu-latest + needs: + - prepare-db-replica + env: + SNAPSHOT_ARN: ${{ needs.prepare-db-replica.outputs.SNAPSHOT_ARN }} + DB_SECRET_ARN: ${{ needs.prepare-db-replica.outputs.DB_SECRET_ARN || 'arn:aws:secretsmanager:eu-west-2:000000000000:secret:placeholder' }} + permissions: + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v5 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ env.aws_role }} + aws-region: eu-west-2 + - name: Install terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.11.4 + - name: Get db secret arn + id: get-db-secret-arn + working-directory: terraform/app + run: | + terraform init -backend-config="env/${{ inputs.environment }}-backend.hcl" -upgrade + DB_SECRET_ARN=$(terraform output --raw db_secret_arn) + echo "DB_SECRET_ARN=$DB_SECRET_ARN" >> $GITHUB_OUTPUT + - name: Terraform Plan + id: plan + run: | + set -eo pipefail + terraform init -backend-config="env/${{ inputs.environment }}-backend.hcl" -upgrade + + CIDR_BLOCKS='${{ inputs.egress-cidr }}' + PLAN_ARGS=( + "plan" + "-var=db_secret_arn=${{ steps.get-db-secret-arn.outputs.DB_SECRET_ARN }}" + "-var=imported_snapshot=${{ env.SNAPSHOT_ARN }}" + "-var-file=env/${{ inputs.environment }}.tfvars" + "-var=allowed_egress_cidr_blocks=$CIDR_BLOCKS" + "-out=${{ runner.temp }}/tfplan" + "-replace" "aws_rds_cluster.cluster" + ) + + terraform "${PLAN_ARGS[@]}" | tee ${{ runner.temp }}/tf_stdout + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: tfplan_infrastructure-${{ inputs.environment }} + path: ${{ runner.temp }}/tfplan + + apply: + name: Terraform apply + runs-on: ubuntu-latest + needs: plan + environment: ${{ inputs.environment }} + permissions: + id-token: write + steps: + - name: Checkout code + uses: actions/checkout@v5 + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ env.aws_role }} + aws-region: eu-west-2 + - name: Download artifact + uses: actions/download-artifact@v5 + with: + name: tfplan_infrastructure-${{ inputs.environment }} + path: ${{ runner.temp }} + - name: Install terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.11.4 + - name: Apply the changes + run: | + set -e + terraform init -backend-config="env/${{ inputs.environment }}-backend.hcl" -upgrade + terraform apply ${{ runner.temp }}/tfplan diff --git a/bin/docker-entrypoint b/bin/docker-entrypoint index c9330c2273..85c12bb78d 100755 --- a/bin/docker-entrypoint +++ b/bin/docker-entrypoint @@ -6,6 +6,12 @@ if [ -z "${LD_PRELOAD+x}" ]; then export LD_PRELOAD fi +for var in $(env | cut -d= -f1); do + if [ "${!var}" = "CHANGE_ME" ]; then + unset "$var" + fi +done + # When starting the container then create or migrate existing database if [ "$1" == "./bin/docker-start" ]; then ./bin/rails db:prepare:ignore_concurrent_migration_exceptions diff --git a/config/templates/appspec.yaml b/config/templates/appspec.yaml new file mode 100644 index 0000000000..bb94bf7cad --- /dev/null +++ b/config/templates/appspec.yaml @@ -0,0 +1,16 @@ +# This is an appspec.yml template file for use with an Amazon ECS deployment in CodeDeploy. +# The lines in this template that start with the hashtag are +# comments that can be safely left in the file or +# ignored. +# For help completing this file, see the "AppSpec File Reference" in the +# "CodeDeploy User Guide" at +# https://docs.aws.amazon.com/codedeploy/latest/userguide/app-spec-ref.html +version: 1.0 +Resources: + - TargetService: + Type: AWS::ECS::Service + Properties: + TaskDefinition: "" + LoadBalancerInfo: + ContainerName: "application" + ContainerPort: "4000" diff --git a/script/requirements.txt b/script/requirements.txt new file mode 100644 index 0000000000..b6119c043c --- /dev/null +++ b/script/requirements.txt @@ -0,0 +1,2 @@ +boto3>=1.39.0 +PyYAML>=6.0 diff --git a/script/shell.sh b/script/shell.sh index 0a52d3046e..db9e512260 100755 --- a/script/shell.sh +++ b/script/shell.sh @@ -161,5 +161,5 @@ aws ecs execute-command --region "$region" \ --cluster "$cluster_name" \ --task "$task_id" \ --container "$container_name" \ - --command "/bin/bash" \ + --command "/rails/bin/docker-entrypoint /bin/bash" \ --interactive diff --git a/terraform/account/deployment_permissions.tf b/terraform/account/deployment_permissions.tf index e8f9441325..9ff3877780 100644 --- a/terraform/account/deployment_permissions.tf +++ b/terraform/account/deployment_permissions.tf @@ -3,7 +3,8 @@ resource "aws_iam_role" "mavis_deploy" { name = "GithubDeployMavisAndInfrastructure" description = "Role allowing terraform deployment from github workflows" assume_role_policy = templatefile("resources/iam_role_github_trust_policy_${var.environment}.json.tftpl", { - account_id = var.account_id + account_id = var.account_id + repository_list = ["repo:nhsuk/manage-vaccinations-in-schools:*"] }) } @@ -27,7 +28,8 @@ resource "aws_iam_role" "data_replication_deploy" { name = "GithubDeployDataReplicationInfrastructure" description = "Role to be assumed by github workflows dealing with the creation and destruction of the data-replication infrastructure." assume_role_policy = templatefile("resources/iam_role_github_trust_policy_${var.environment}.json.tftpl", { - account_id = var.account_id + account_id = var.account_id + repository_list = ["repo:nhsuk/manage-vaccinations-in-schools:*"] }) } @@ -46,6 +48,8 @@ resource "aws_iam_role_policy_attachment" "data_replication" { policy_arn = each.value } +################# DB Snapshot Policy ################ + resource "aws_iam_role" "data_replication_snapshot" { name = "DatabaseSnapshotRole" description = "Role to be assumed by the data replication workflow for taking on-demand DB snapshots" @@ -74,7 +78,8 @@ resource "aws_iam_role" "monitoring_deploy" { name = "GithubDeployMonitoring" description = "Role allowing terraform deployment of monitoring resources from github workflows" assume_role_policy = templatefile("resources/iam_role_github_trust_policy_${var.environment}.json.tftpl", { - account_id = var.account_id + account_id = var.account_id + repository_list = ["repo:nhsuk/manage-vaccinations-in-schools:*"] }) } @@ -104,3 +109,32 @@ resource "aws_iam_role_policy_attachment" "mavis_dms" { role = aws_iam_role.mavis_deploy.name policy_arn = aws_iam_policy.dms.arn } + +################ Deploy ECS Service ################ + +resource "aws_iam_role" "deploy_ecs_service" { + name = "GithubDeployECSService" + description = "Role allowing terraform deployment of ECS services from github workflows" + assume_role_policy = templatefile("resources/iam_role_github_trust_policy_${var.environment}.json.tftpl", { + account_id = var.account_id, + repository_list = [ + "repo:nhsuk/manage-vaccinations-in-schools:*", + "repo:NHSDigital/manage-vaccinations-in-schools-reporting:*" + ] + }) +} + +resource "aws_iam_policy" "deploy_ecs_service" { + name = "DeployECSServiceResources" + description = "Permissions for GithubDeployECSService role" + policy = file("resources/iam_policy_DeployECSServiceResources.json") + lifecycle { + ignore_changes = [description] + } +} + +resource "aws_iam_role_policy_attachment" "deploy_ecs_service" { + for_each = local.ecs_deploy_policies + role = aws_iam_role.deploy_ecs_service.name + policy_arn = each.value +} diff --git a/terraform/account/resources/iam_policy_DeployECSServiceResources.json b/terraform/account/resources/iam_policy_DeployECSServiceResources.json new file mode 100644 index 0000000000..3b59b05cf0 --- /dev/null +++ b/terraform/account/resources/iam_policy_DeployECSServiceResources.json @@ -0,0 +1,57 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ecs:UpdateService", + "ecs:DescribeServices", + "ecs:ListServices", + "ecs:RegisterTaskDefinition", + "ecs:DeregisterTaskDefinition", + "ecs:DescribeTaskDefinition", + "ecs:ListTaskDefinitions", + "ecs:DescribeClusters", + "ecs:DescribeTasks", + "ecs:DescribeTaskSets", + "ecs:StartTask", + "ecs:ListServiceDeployments", + "ecs:DescribeServiceDeployments", + "ecs:UntagResource", + "ecs:TagResource", + "ecs:ListClusters", + "ecs:ListContainerInstances", + "ecs:ListTaskDefinitionFamilies", + "ecs:ListTasks", + "ecr:PutImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload", + "codedeploy:BatchGetApplicationRevisions", + "codedeploy:BatchGetApplications", + "codedeploy:BatchGetDeploymentGroups", + "codedeploy:BatchGetDeployments", + "codedeploy:ContinueDeployment", + "codedeploy:CreateApplication", + "codedeploy:CreateDeployment", + "codedeploy:CreateDeploymentGroup", + "codedeploy:GetApplication", + "codedeploy:GetApplicationRevision", + "codedeploy:GetDeployment", + "codedeploy:GetDeploymentConfig", + "codedeploy:GetDeploymentGroup", + "codedeploy:GetDeploymentTarget", + "codedeploy:ListApplicationRevisions", + "codedeploy:ListApplications", + "codedeploy:ListDeploymentConfigs", + "codedeploy:ListDeploymentGroups", + "codedeploy:ListDeployments", + "codedeploy:ListDeploymentTargets", + "codedeploy:RegisterApplicationRevision", + "codedeploy:StopDeployment", + "iam:PassRole" + ], + "Resource": ["*"] + } + ] +} diff --git a/terraform/account/resources/iam_role_github_trust_policy_development.json.tftpl b/terraform/account/resources/iam_role_github_trust_policy_development.json.tftpl index c8b00eb1b5..d12422de37 100644 --- a/terraform/account/resources/iam_role_github_trust_policy_development.json.tftpl +++ b/terraform/account/resources/iam_role_github_trust_policy_development.json.tftpl @@ -12,7 +12,7 @@ "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { - "token.actions.githubusercontent.com:sub": "repo:nhsuk/manage-vaccinations-in-schools:*" + "token.actions.githubusercontent.com:sub": ${jsonencode(repository_list)} } } } diff --git a/terraform/account/variables.tf b/terraform/account/variables.tf index d7a1fa3f3c..f6a14fae64 100644 --- a/terraform/account/variables.tf +++ b/terraform/account/variables.tf @@ -32,4 +32,9 @@ locals { monitoring_policies = merge(local.base_policies, { monitoring_deploy = aws_iam_policy.monitoring_deploy.arn }) + + ecs_deploy_policies = { + ecr_read = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" + deploy_ecs_service = aws_iam_policy.deploy_ecs_service.arn + } } diff --git a/terraform/app/codedeploy.tf b/terraform/app/codedeploy.tf index 3a4190ef09..8e1b79ab71 100644 --- a/terraform/app/codedeploy.tf +++ b/terraform/app/codedeploy.tf @@ -51,77 +51,3 @@ resource "aws_codedeploy_deployment_group" "blue_green_deployment_group" { } } } - -resource "aws_s3_bucket" "code_deploy_bucket" { - bucket = var.appspec_bucket - force_destroy = true -} - - -data "aws_s3_bucket" "logs" { - bucket = var.access_logs_bucket -} - -resource "aws_s3_bucket_logging" "example" { - bucket = aws_s3_bucket.code_deploy_bucket.id - - target_bucket = data.aws_s3_bucket.logs.id - target_prefix = "codedeploy-log-${var.environment}/" -} - -resource "aws_s3_bucket_versioning" "code_deploy_bucket_versioning" { - bucket = aws_s3_bucket.code_deploy_bucket.id - versioning_configuration { - status = "Enabled" - } -} - -resource "aws_s3_bucket_policy" "block_http" { - bucket = aws_s3_bucket.code_deploy_bucket.id - policy = jsonencode({ - Version = "2012-10-17" - Id = "block-http-policy" - Statement = [ - { - Sid = "HTTPSOnly" - Effect = "Deny" - Principal = { - "AWS" : "*" - } - Action = "s3:*" - Resource = [ - aws_s3_bucket.code_deploy_bucket.arn, - "${aws_s3_bucket.code_deploy_bucket.arn}/*", - ] - Condition = { - Bool = { - "aws:SecureTransport" = "false" - } - } - }, - ] - }) -} - -resource "aws_s3_bucket_public_access_block" "s3_bucket_access" { - bucket = aws_s3_bucket.code_deploy_bucket.bucket - block_public_acls = true - block_public_policy = true - ignore_public_acls = true - restrict_public_buckets = true -} - -resource "aws_s3_object" "appspec_object" { - bucket = aws_s3_bucket.code_deploy_bucket.bucket - key = "appspec.yaml" - acl = "private" - content = templatefile("templates/appspec.yaml.tpl", { - task_definition_arn = module.web_service.task_definition.arn - container_name = module.web_service.task_definition.container_name - container_port = aws_lb_target_group.blue.port - }) - - tags = { - UseWithCodeDeploy = true - } -} diff --git a/terraform/app/db_migration_configuration.tf b/terraform/app/db_migration_configuration.tf deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/terraform/app/ecs.tf b/terraform/app/ecs.tf index cf6adceb9f..53e1873e18 100644 --- a/terraform/app/ecs.tf +++ b/terraform/app/ecs.tf @@ -22,12 +22,11 @@ resource "aws_ecs_cluster" "cluster" { module "web_service" { source = "./modules/ecs_service" task_config = { - environment = local.task_envs - secrets = local.task_secrets + environment = local.task_envs["CORE"] + secrets = local.task_secrets["CORE"] cpu = 1024 memory = 3072 - docker_image = "${var.account_id}.dkr.ecr.eu-west-2.amazonaws.com/${var.docker_image}@${var.image_digest}" - execution_role_arn = aws_iam_role.ecs_task_execution_role.arn + execution_role_arn = aws_iam_role.ecs_task_execution_role["CORE"].arn task_role_arn = aws_iam_role.ecs_task_role.arn log_group_name = aws_cloudwatch_log_group.ecs_log_group.name region = var.region @@ -61,12 +60,11 @@ module "web_service" { module "sidekiq_service" { source = "./modules/ecs_service" task_config = { - environment = local.task_envs - secrets = local.task_secrets + environment = local.task_envs["CORE"] + secrets = local.task_secrets["CORE"] cpu = 1024 memory = 2048 - docker_image = "${var.account_id}.dkr.ecr.eu-west-2.amazonaws.com/${var.docker_image}@${var.image_digest}" - execution_role_arn = aws_iam_role.ecs_task_execution_role.arn + execution_role_arn = aws_iam_role.ecs_task_execution_role["CORE"].arn task_role_arn = aws_iam_role.ecs_task_role.arn log_group_name = aws_cloudwatch_log_group.ecs_log_group.name region = var.region diff --git a/terraform/app/env/preview.tfvars b/terraform/app/env/preview.tfvars index 97df32fa3e..5f36d8227b 100644 --- a/terraform/app/env/preview.tfvars +++ b/terraform/app/env/preview.tfvars @@ -1,26 +1,18 @@ environment = "preview" dns_certificate_arn = null -docker_image = "mavis/webapp" resource_name = { rds_security_group = "mavis-preview-AddonsStack-1PD6PKSN106RK-dbDBClusterSecurityGroup-7cmoQwi6uv8e" loadbalancer = "mavis-preview-pub-lb" lb_security_group = "mavis-preview-PublicHTTPLoadBalancerSecurityGroup-qfHAKWH39OY3" cloudwatch_vpc_log_group = "mavis-preview-FlowLogs" } -rails_env = "staging" rails_master_key_path = "/copilot/mavis/secrets/STAGING_RAILS_MASTER_KEY" -enable_splunk = false -enable_cis2 = false -enable_pds_enqueue_bulk_updates = false - http_hosts = { MAVIS__HOST = "preview.mavistesting.com" MAVIS__GIVE_OR_REFUSE_CONSENT_HOST = "preview.mavistesting.com" } -appspec_bucket = "nhse-mavis-appspec-bucket-preview" - valkey_node_type = "cache.t4g.micro" valkey_log_retention_days = 3 valkey_failover_enabled = false diff --git a/terraform/app/env/production.tfvars b/terraform/app/env/production.tfvars index b571f99b77..b7e57fb6b1 100644 --- a/terraform/app/env/production.tfvars +++ b/terraform/app/env/production.tfvars @@ -1,13 +1,11 @@ environment = "production" dns_certificate_arn = ["arn:aws:acm:eu-west-2:820242920762:certificate/dd00edc0-b305-45bd-83aa-7c7f298b0a68"] -docker_image = "mavis/webapp" resource_name = { rds_security_group = "mavis-production-AddonsStack-H6B1986BQ928-dbDBClusterSecurityGroup-dEt2cEtcHBMo" loadbalancer = "mavis-production-pub-lb" lb_security_group = "mavis-production-PublicHTTPLoadBalancerSecurityGroup-G7umbZTkvkwK" cloudwatch_vpc_log_group = "mavis-production-FlowLogs" } -rails_env = "production" rails_master_key_path = "/copilot/mavis/production/secrets/RAILS_MASTER_KEY" academic_year_number_of_preparation_days = 31 @@ -18,7 +16,6 @@ http_hosts = { MAVIS__GIVE_OR_REFUSE_CONSENT_HOST = "www.give-or-refuse-consent-for-vaccinations.nhs.uk" } -appspec_bucket = "nhse-mavis-appspec-bucket-production" account_id = 820242920762 vpc_log_retention_days = 14 ecs_log_retention_days = 30 diff --git a/terraform/app/env/qa.tfvars b/terraform/app/env/qa.tfvars index d3e4ee6c6b..e00ced3288 100644 --- a/terraform/app/env/qa.tfvars +++ b/terraform/app/env/qa.tfvars @@ -1,21 +1,13 @@ environment = "qa" dns_certificate_arn = ["arn:aws:acm:eu-west-2:393416225559:certificate/dafb0f10-ee18-45e2-8971-28d4ab434375"] -docker_image = "mavis/webapp" resource_name = { rds_security_group = "mavis-qa-AddonsStack-Z0L4GX5EUV3I-dbDBClusterSecurityGroup-vd2Avaw4JIgr" loadbalancer = "mavis-qa-pub-lb" lb_security_group = "mavis-qa-PublicHTTPLoadBalancerSecurityGroup-ml4lZT5ey5ih" cloudwatch_vpc_log_group = "mavis-qa-FlowLogs" } -rails_env = "staging" rails_master_key_path = "/copilot/mavis/secrets/STAGING_RAILS_MASTER_KEY" -enable_cis2 = false -enable_pds_enqueue_bulk_updates = false - -# Normally this is 31, but this gives us 2 weeks of additional testing. -academic_year_number_of_preparation_days = 45 - http_hosts = { MAVIS__HOST = "qa.mavistesting.com" MAVIS__GIVE_OR_REFUSE_CONSENT_HOST = "qa.mavistesting.com" diff --git a/terraform/app/env/sandbox-alpha.tfvars b/terraform/app/env/sandbox-alpha.tfvars index b07127fc38..82d4961c06 100644 --- a/terraform/app/env/sandbox-alpha.tfvars +++ b/terraform/app/env/sandbox-alpha.tfvars @@ -12,11 +12,6 @@ http_hosts = { MAVIS__GIVE_OR_REFUSE_CONSENT_HOST = "sandbox-alpha.mavistesting.com" } -enable_splunk = false -enable_cis2 = false -enable_pds_enqueue_bulk_updates = false - -appspec_bucket = "nhse-mavis-appspec-bucket-sandbox-alpha" minimum_web_replicas = 1 maximum_web_replicas = 2 minimum_sidekiq_replicas = 1 @@ -25,4 +20,3 @@ maximum_sidekiq_replicas = 2 valkey_node_type = "cache.t4g.micro" valkey_log_retention_days = 3 valkey_failover_enabled = false -sidekiq_replicas = 1 diff --git a/terraform/app/env/sandbox-beta.tfvars b/terraform/app/env/sandbox-beta.tfvars index 0daa27a468..db9a9e22de 100644 --- a/terraform/app/env/sandbox-beta.tfvars +++ b/terraform/app/env/sandbox-beta.tfvars @@ -12,11 +12,6 @@ http_hosts = { MAVIS__GIVE_OR_REFUSE_CONSENT_HOST = "sandbox-beta.mavistesting.com" } -enable_splunk = false -enable_cis2 = false -enable_pds_enqueue_bulk_updates = false - -appspec_bucket = "nhse-mavis-appspec-bucket-sandbox-beta" minimum_web_replicas = 1 maximum_web_replicas = 2 minimum_sidekiq_replicas = 1 @@ -26,4 +21,3 @@ maximum_sidekiq_replicas = 2 valkey_node_type = "cache.t4g.micro" valkey_log_retention_days = 3 valkey_failover_enabled = false -sidekiq_replicas = 1 diff --git a/terraform/app/env/test.tfvars b/terraform/app/env/test.tfvars index 1eaae3eb9a..87ad14007f 100644 --- a/terraform/app/env/test.tfvars +++ b/terraform/app/env/test.tfvars @@ -1,13 +1,11 @@ environment = "test" dns_certificate_arn = ["arn:aws:acm:eu-west-2:393416225559:certificate/7e80f006-e9d8-488f-b950-d97f3cc41e4f"] -docker_image = "mavis/webapp" resource_name = { rds_security_group = "mavis-test-AddonsStack-GB8Z9LQVO8OF-dbDBClusterSecurityGroup-1KSO3O1CL4NI5" loadbalancer = "mavis--Publi-W19xy2QLULZ4" lb_security_group = "mavis-test-PublicHTTPLoadBalancerSecurityGroup-15LE48D6JYPML" cloudwatch_vpc_log_group = "mavis-test-FlowLogs" } -rails_env = "staging" rails_master_key_path = "/copilot/mavis/secrets/STAGING_RAILS_MASTER_KEY" # Normally this is 31, but this gives us 2 weeks of additional testing. @@ -17,7 +15,6 @@ http_hosts = { MAVIS__HOST = "test.mavistesting.com" MAVIS__GIVE_OR_REFUSE_CONSENT_HOST = "test.mavistesting.com" } -appspec_bucket = "nhse-mavis-appspec-bucket-test" valkey_node_type = "cache.t4g.micro" valkey_log_retention_days = 3 diff --git a/terraform/app/env/training.tfvars b/terraform/app/env/training.tfvars index 0aa8968a36..6d98882531 100644 --- a/terraform/app/env/training.tfvars +++ b/terraform/app/env/training.tfvars @@ -3,25 +3,18 @@ dns_certificate_arn = [ "arn:aws:acm:eu-west-2:393416225559:certificate/368edbcb-37c5-4146-9087-ff011bef5e05", "arn:aws:acm:eu-west-2:393416225559:certificate/e93e3912-eee4-4f6e-826d-c628bff58527", ] -docker_image = "mavis/webapp" resource_name = { rds_security_group = "mavis-training-AddonsStack-1JZSXP7P84221-dbDBClusterSecurityGroup-A5NL1GFJ83LX" loadbalancer = "mavis--Publi-w1wzc4E2jrl6" lb_security_group = "mavis-training-PublicHTTPLoadBalancerSecurityGroup-L8GOGS04ARYI" cloudwatch_vpc_log_group = "mavis-training-FlowLogs" } -rails_env = "staging" rails_master_key_path = "/copilot/mavis/secrets/STAGING_RAILS_MASTER_KEY" -enable_splunk = false -enable_cis2 = false -enable_pds_enqueue_bulk_updates = false - http_hosts = { MAVIS__HOST = "training.manage-vaccinations-in-schools.nhs.uk" MAVIS__GIVE_OR_REFUSE_CONSENT_HOST = "training.give-or-refuse-consent-for-vaccinations.nhs.uk" } -appspec_bucket = "nhse-mavis-appspec-bucket-training" valkey_node_type = "cache.t4g.micro" valkey_log_retention_days = 3 diff --git a/terraform/app/iam.tf b/terraform/app/iam.tf index f9a42c11f9..91aa350d97 100644 --- a/terraform/app/iam.tf +++ b/terraform/app/iam.tf @@ -1,7 +1,8 @@ ################################# IAM Policies ################################# resource "aws_iam_policy" "ecs_secret_access_policy" { - name = "ecs-secret-access-policy-${var.environment}" - policy = data.aws_iam_policy_document.ecs_secrets_access.json + for_each = local.applications_accessing_secrets_or_parameters + name = "ecs-secret-access-policy-${var.environment}-${each.key}" + policy = data.aws_iam_policy_document.ecs_secrets_access[each.key].json } resource "aws_iam_policy" "shell_access_policy" { @@ -23,7 +24,8 @@ resource "aws_iam_policy" "vpc_flowlogs" { resource "aws_iam_role" "ecs_task_execution_role" { - name = "ecsTaskExecutionRole-${var.environment}" + for_each = local.parameter_store_variables + name = "ecsTaskExecutionRole-${var.environment}-${each.key}" assume_role_policy = templatefile("templates/iam_assume_role.json.tpl", { service_name = "ecs-tasks.amazonaws.com" }) } @@ -47,12 +49,14 @@ resource "aws_iam_role" "vpc_flowlogs" { ################################# IAM Role/Policy Attachments ################################# resource "aws_iam_role_policy_attachment" "ecs_secret_access" { - role = aws_iam_role.ecs_task_execution_role.name - policy_arn = aws_iam_policy.ecs_secret_access_policy.arn + for_each = local.applications_accessing_secrets_or_parameters + role = aws_iam_role.ecs_task_execution_role[each.key].name + policy_arn = aws_iam_policy.ecs_secret_access_policy[each.key].arn } resource "aws_iam_role_policy_attachment" "ecs_ecr_and_log_permissions" { - role = aws_iam_role.ecs_task_execution_role.name + for_each = local.parameter_store_variables + role = aws_iam_role.ecs_task_execution_role[each.key].name policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" } diff --git a/terraform/app/iam_policy_documents.tf b/terraform/app/iam_policy_documents.tf index 622073eb80..1e5e0b71f4 100644 --- a/terraform/app/iam_policy_documents.tf +++ b/terraform/app/iam_policy_documents.tf @@ -19,18 +19,16 @@ data "aws_iam_policy_document" "codedeploy" { "elasticloadbalancing:DescribeRules", "elasticloadbalancing:ModifyRule" ] - resources = ["*"] #TODO: Restrict permissions to only Mavis-specifc resources - effect = "Allow" - } - statement { - actions = ["s3:GetObject", "s3:GetObjectVersion"] - resources = ["arn:aws:s3:::*"] + resources = ["*"] effect = "Allow" } statement { - actions = ["iam:PassRole"] - resources = [aws_iam_role.ecs_task_role.arn, aws_iam_role.ecs_task_execution_role.arn] - effect = "Allow" + actions = ["iam:PassRole"] + resources = concat( + [aws_iam_role.ecs_task_role.arn], + [for role in aws_iam_role.ecs_task_execution_role : role.arn] + ) + effect = "Allow" } } @@ -48,21 +46,24 @@ data "aws_iam_policy_document" "shell_access" { } data "aws_iam_policy_document" "ecs_secrets_access" { - statement { - sid = "railsKeySid" - actions = ["ssm:GetParameters"] - resources = concat([ - "arn:aws:ssm:${var.region}:${var.account_id}:parameter${var.rails_master_key_path}" - ], local.parameter_store_arns) - effect = "Allow" + for_each = local.applications_accessing_secrets_or_parameters + dynamic "statement" { + for_each = length(local.parameter_values[each.key]) == 0 ? [] : [1] + content { + sid = "ssmParameterStoreAccessSid" + actions = ["ssm:GetParameters"] + resources = [for kv_pair in local.parameter_values[each.key] : kv_pair["valueFrom"]] + effect = "Allow" + } } - statement { - sid = "dbSecretSid" - actions = ["secretsmanager:GetSecretValue"] - resources = [ - aws_rds_cluster.core.master_user_secret[0].secret_arn - ] - effect = "Allow" + dynamic "statement" { + for_each = length(local.secret_values[each.key]) == 0 ? [] : [1] + content { + sid = "dbSecretSid" + actions = ["secretsmanager:GetSecretValue"] + resources = [for kv_pair in local.secret_values[each.key] : kv_pair["valueFrom"]] + effect = "Allow" + } } } diff --git a/terraform/app/modules/ecs_service/main.tf b/terraform/app/modules/ecs_service/main.tf index 1f220e7f72..28d3641b71 100644 --- a/terraform/app/modules/ecs_service/main.tf +++ b/terraform/app/modules/ecs_service/main.tf @@ -70,7 +70,7 @@ resource "aws_ecs_service" "this" { } resource "aws_ecs_task_definition" "this" { - family = "mavis-${local.server_type_name}-task-definition-${var.environment}" + family = "mavis-${local.server_type_name}-task-definition-${var.environment}-template" requires_compatibilities = ["FARGATE"] network_mode = "awsvpc" cpu = var.task_config.cpu @@ -80,7 +80,7 @@ resource "aws_ecs_task_definition" "this" { container_definitions = jsonencode([ { name = var.container_name - image = var.task_config.docker_image + image = "CHANGE_ME" essential = true readonlyRootFileSystem = true portMappings = [ diff --git a/terraform/app/modules/ecs_service/outputs.tf b/terraform/app/modules/ecs_service/outputs.tf index c91f926f47..efd1121ef8 100644 --- a/terraform/app/modules/ecs_service/outputs.tf +++ b/terraform/app/modules/ecs_service/outputs.tf @@ -10,12 +10,3 @@ output "service" { } description = "Essential attributes of the ECS service" } - -output "task_definition" { - value = { - arn = aws_ecs_task_definition.this.arn - family = aws_ecs_task_definition.this.family - container_name = var.container_name - } - description = "Essential attributes of the ECS task definition" -} diff --git a/terraform/app/modules/ecs_service/variables.tf b/terraform/app/modules/ecs_service/variables.tf index 785da0c6c3..ad11890c72 100644 --- a/terraform/app/modules/ecs_service/variables.tf +++ b/terraform/app/modules/ecs_service/variables.tf @@ -58,7 +58,6 @@ variable "task_config" { })) cpu = number memory = number - docker_image = string execution_role_arn = string task_role_arn = string log_group_name = string diff --git a/terraform/app/outputs.tf b/terraform/app/outputs.tf index ba58c5f4de..beb54847a3 100644 --- a/terraform/app/outputs.tf +++ b/terraform/app/outputs.tf @@ -1,18 +1,3 @@ -output "s3_uri" { - description = "S3 uri for appspec.yaml needed for CodeDeploy" - value = "s3://${aws_s3_bucket.code_deploy_bucket.bucket}/${aws_s3_object.appspec_object.key}" -} - -output "s3_bucket" { - description = "The name of the S3 bucket that stores the appspec.yaml for CodeDeploy" - value = aws_s3_bucket.code_deploy_bucket.bucket -} - -output "s3_key" { - description = "The key of the S3 CodeDeploy appspec object" - value = aws_s3_object.appspec_object.key -} - output "codedeploy_application_name" { description = "The name of the CodeDeploy application" value = aws_codedeploy_app.mavis.name @@ -23,17 +8,6 @@ output "codedeploy_deployment_group_name" { value = aws_codedeploy_deployment_group.blue_green_deployment_group.deployment_group_name } -output "ecs_variables" { - value = { - cluster_name = aws_ecs_cluster.cluster.name - sidekiq = { - service_name = module.sidekiq_service.service.name - task_definition = module.sidekiq_service.task_definition - } - } - description = "Essential attributes of the ECS services" -} - output "db_secret_arn" { description = "The ARN of the secret containing the DB credentials." value = aws_rds_cluster.core.master_user_secret[0].secret_arn diff --git a/terraform/app/ssm_parameters.tf b/terraform/app/ssm_parameters.tf index fcd4e83708..58082cc06c 100644 --- a/terraform/app/ssm_parameters.tf +++ b/terraform/app/ssm_parameters.tf @@ -1,7 +1,21 @@ -resource "aws_ssm_parameter" "environment_config" { - for_each = local.parameter_store_variables - name = "/${var.environment}/env/${each.key}" +resource "aws_ssm_parameter" "core_environment_overwrites" { + for_each = local.parameter_store_variables["CORE"] + name = "/${var.environment}/env/core/${each.key}" type = "String" + value = each.value - value = each.value + lifecycle { + ignore_changes = all + } +} + +resource "aws_ssm_parameter" "reporting_environment_overwrites" { + for_each = local.parameter_store_variables["REPORTING"] + name = "/${var.environment}/env/reporting/${each.key}" + type = "String" + value = each.value + + lifecycle { + ignore_changes = all + } } diff --git a/terraform/app/variables.tf b/terraform/app/variables.tf index 73c6dbbc73..190cb76b17 100644 --- a/terraform/app/variables.tf +++ b/terraform/app/variables.tf @@ -23,12 +23,6 @@ variable "access_logs_bucket" { description = "Name of the S3 bucket which stores access logs for various resources" } -variable "appspec_bucket" { - type = string - description = "Name of the S3 bucket which stores appspec files" - nullable = false -} - variable "account_id" { type = string default = "393416225559" @@ -102,16 +96,6 @@ variable "vpc_log_retention_days" { ########## Task definition configuration ########## -variable "rails_env" { - type = string - default = "staging" - description = "The rails environment configuration to use for the mavis application" - nullable = false - validation { - condition = contains(["staging", "production"], var.rails_env) - error_message = "Incorrect rails environment, allowed values are: {staging, production}" - } -} variable "rails_master_key_path" { type = string @@ -120,61 +104,6 @@ variable "rails_master_key_path" { nullable = false } -variable "docker_image" { - type = string - default = "mavis/webapp" - description = "The docker image name for the essential container in the task definition" - nullable = false -} - -variable "image_digest" { - type = string - description = "The docker image digest for the essential container in the task definition." - nullable = false -} - -variable "enable_cis2" { - type = bool - default = true - description = "Boolean toggle to determine whether the CIS2 feature should be enabled." - nullable = false -} - -variable "enable_pds_enqueue_bulk_updates" { - type = bool - default = true - description = "Whether PDS jobs that update patients in bulk should execute or not. This is disabled in non-production environments to avoid making unnecessary requests to PDS." - nullable = false -} - -variable "academic_year_today_override" { - type = string - default = "nil" - description = "A date that can be used to override today's date when calculating the current academic year." - nullable = false -} - -variable "academic_year_number_of_preparation_days" { - type = number - default = 31 - description = "How many days before the start of the academic year to start the preparation period." - nullable = false -} - -variable "pds_rate_limit_per_second" { - type = number - default = 5 - description = "The rate limit when communicating with PDS." - nullable = false -} - -variable "enable_splunk" { - type = bool - default = true - description = "Boolean toggle to determine whether the Splunk feature should be enabled." - nullable = false -} - variable "enable_enhanced_db_monitoring" { type = bool default = false @@ -182,27 +111,52 @@ variable "enable_enhanced_db_monitoring" { nullable = false } -variable "app_version" { - type = string - description = "The version identifier for the MAVIS application deployment" - default = "Unknown" - nullable = false -} - locals { is_production = var.environment == "production" parameter_store_variables = tomap({ - MAVIS__ACADEMIC_YEAR_TODAY_OVERRIDE = var.academic_year_today_override - MAVIS__ACADEMIC_YEAR_NUMBER_OF_PREPARATION_DAYS = var.academic_year_number_of_preparation_days - MAVIS__PDS__ENQUEUE_BULK_UPDATES = var.enable_pds_enqueue_bulk_updates ? "true" : "false" - MAVIS__PDS__RATE_LIMIT_PER_SECOND = var.pds_rate_limit_per_second - SIDEKIQ_CONCURRENCY = 5 + CORE = local.is_production ? {} : tomap({ + MAVIS__ACADEMIC_YEAR_TODAY_OVERRIDE = "CHANGE_ME" + MAVIS__ACADEMIC_YEAR_NUMBER_OF_PREPARATION_DAYS = "CHANGE_ME" + MAVIS__PDS__ENQUEUE_BULK_UPDATES = "CHANGE_ME" + MAVIS__PDS__RATE_LIMIT_PER_SECOND = "CHANGE_ME" + SIDEKIQ_CONCURRENCY = "CHANGE_ME" + }) + REPORTING = local.is_production ? {} : tomap({ + }) }) - parameter_store_config_list = [for key, value in local.parameter_store_variables : { - name = key - valueFrom = aws_ssm_parameter.environment_config[key].arn - }] - parameter_store_arns = [for key, value in local.parameter_store_variables : aws_ssm_parameter.environment_config[key].arn] + applications_accessing_secrets_or_parameters = toset([ + for key, value in local.parameter_store_variables : key if length(local.task_secrets[key]) > 0 + ]) + secret_values = tomap( + { + CORE = [{ + name = "DB_CREDENTIALS" + valueFrom = aws_rds_cluster.core.master_user_secret[0].secret_arn + }] + REPORTING = [] + } + ) + + parameter_values = tomap( + { + CORE = concat( + [for key, value in aws_ssm_parameter.core_environment_overwrites : + { + name = key + valueFrom = "arn:aws:ssm:${var.region}:${var.account_id}:parameter${value.name}" + } + ], + [{ + name = "RAILS_MASTER_KEY" + valueFrom = "arn:aws:ssm:${var.region}:${var.account_id}:parameter${var.rails_master_key_path}" + }], + ) + REPORTING = [for key, value in aws_ssm_parameter.reporting_environment_overwrites : { + name = key + valueFrom = "arn:aws:ssm:${var.region}:${var.account_id}:parameter${value.name}" + }] + } + ) sandbox_envs = ( startswith(var.environment, "sandbox") ? [ @@ -213,63 +167,49 @@ locals { ] : [] ) - task_envs = concat([ - { - name = "DB_HOST" - value = aws_rds_cluster.core.endpoint - }, - { - name = "DB_NAME" - value = aws_rds_cluster.core.database_name - }, - { - name = "RAILS_ENV" - value = var.rails_env - }, - { - name = "SENTRY_ENVIRONMENT" - value = var.environment - }, - { - name = "MAVIS__HOST" - value = var.http_hosts.MAVIS__HOST - }, - { - name = "MAVIS__GIVE_OR_REFUSE_CONSENT_HOST" - value = var.http_hosts.MAVIS__GIVE_OR_REFUSE_CONSENT_HOST - }, - { - name = "MAVIS__CIS2__ENABLED" - value = var.enable_cis2 ? "true" : "false" - }, - { - name = "MAVIS__SPLUNK__ENABLED" - value = var.enable_splunk ? "true" : "false" - }, - { - name = "APP_VERSION" - value = var.app_version - }, - { - name = "SIDEKIQ_REDIS_URL" - value = "rediss://${aws_elasticache_replication_group.valkey.primary_endpoint_address}:${var.valkey_port}" - }, - { - name = "REDIS_CACHE_URL" - value = "rediss://${aws_elasticache_serverless_cache.rails_cache.endpoint[0].address}:${aws_elasticache_serverless_cache.rails_cache.endpoint[0].port}" - }, - ], local.sandbox_envs) + task_envs = { + CORE = concat([ + { + name = "DB_HOST" + value = aws_rds_cluster.core.endpoint + }, + { + name = "DB_NAME" + value = aws_rds_cluster.core.database_name + }, + { + name = "MAVIS__HOST" + value = var.http_hosts.MAVIS__HOST + }, + { + name = "MAVIS__GIVE_OR_REFUSE_CONSENT_HOST" + value = var.http_hosts.MAVIS__GIVE_OR_REFUSE_CONSENT_HOST + }, + { + name = "SIDEKIQ_REDIS_URL" + value = "rediss://${aws_elasticache_replication_group.valkey.primary_endpoint_address}:${var.valkey_port}" + }, + { + name = "REDIS_CACHE_URL" + value = "rediss://${aws_elasticache_serverless_cache.rails_cache.endpoint[0].address}:${aws_elasticache_serverless_cache.rails_cache.endpoint[0].port}" + }, + { + name = "RAILS_ENV" + value = var.environment == "production" ? "production" : "staging" + }, + { + name = "SENTRY_ENVIRONMENT" + value = var.environment + }, + ], local.sandbox_envs, + ) + REPORTING = [] + } - task_secrets = concat([ - { - name = "DB_CREDENTIALS" - valueFrom = aws_rds_cluster.core.master_user_secret[0].secret_arn - }, - { - name = "RAILS_MASTER_KEY" - valueFrom = var.rails_master_key_path - } - ], local.parameter_store_config_list) + task_secrets = { + CORE = concat(local.secret_values["CORE"], local.parameter_values["CORE"]) + REPORTING = concat(local.secret_values["REPORTING"], local.parameter_values["REPORTING"]) + } } ########## RDS configuration ########## diff --git a/terraform/data_replication/ecs.tf b/terraform/data_replication/ecs.tf index 8b6cd87b4c..9e1cb8bbc1 100644 --- a/terraform/data_replication/ecs.tf +++ b/terraform/data_replication/ecs.tf @@ -33,7 +33,6 @@ module "db_access_service" { secrets = local.task_secrets cpu = 1024 memory = 2048 - docker_image = "${var.account_id}.dkr.ecr.eu-west-2.amazonaws.com/${var.docker_image}@${var.image_digest}" execution_role_arn = aws_iam_role.ecs_task_execution_role.arn task_role_arn = aws_iam_role.ecs_task_role.arn log_group_name = aws_cloudwatch_log_group.ecs_log_group.name diff --git a/terraform/data_replication/outputs.tf b/terraform/data_replication/outputs.tf index 6d67f77cfd..e69de29bb2 100644 --- a/terraform/data_replication/outputs.tf +++ b/terraform/data_replication/outputs.tf @@ -1,4 +0,0 @@ -output "task_definition_arn" { - description = "The task definition arn of the db access service" - value = module.db_access_service.task_definition.arn -} diff --git a/terraform/data_replication/variables.tf b/terraform/data_replication/variables.tf index 7bdea5566e..c5ec1e13f3 100644 --- a/terraform/data_replication/variables.tf +++ b/terraform/data_replication/variables.tf @@ -50,30 +50,6 @@ variable "account_id" { nullable = false } -variable "docker_image" { - type = string - default = "mavis/webapp" - description = "The docker image name for the essential container in the task definition" - nullable = false -} - -variable "image_digest" { - type = string - description = "The docker image digest for the essential container in the task definition." - nullable = false -} - -variable "rails_env" { - type = string - default = "staging" - description = "The rails environment configuration to use for the mavis application" - nullable = false - validation { - condition = contains(["staging", "production"], var.rails_env) - error_message = "Incorrect rails environment, allowed values are: {staging, production}" - } -} - variable "rails_master_key_path" { type = string default = "/mavis/staging/credentials/RAILS_MASTER_KEY" @@ -94,26 +70,6 @@ locals { { name = "DB_NAME" value = aws_rds_cluster.cluster.database_name - }, - { - name = "RAILS_ENV" - value = var.rails_env - }, - { - name = "SENTRY_ENVIRONMENT" - value = var.environment - }, - { - name = "MAVIS__CIS2__ENABLED" - value = "false" - }, - { - name = "MAVIS__SPLUNK__ENABLED" - value = "false" - }, - { - name = "MAVIS__PDS__ENQUEUE_BULK_UPDATES" - value = "false" } ] task_secrets = [