diff --git a/.aws/deploy/task-definition.json b/.aws/deploy/task-definition.json new file mode 100644 index 000000000..24e86499e --- /dev/null +++ b/.aws/deploy/task-definition.json @@ -0,0 +1,182 @@ +{ + "containerDefinitions": [ + { + "name": "backend", + "portMappings": [ + { + "containerPort": 3000, + "hostPort": 3000, + "protocol": "tcp" + } + ], + "essential": true, + "environment": [ + { + "name": "DD_GIT_COMMIT_SHA", + "value": "" + }, + { + "name": "DD_GIT_REPOSITORY_URL", + "value": "github.com/opengovsg/isomer" + }, + { "name": "DD_DBM_PROPAGATION_MODE", "value": "full" } + ], + "linuxParameters": { + "initProcessEnabled": true + }, + "volumesFrom": [], + "secrets": [ + { + "name": "CLOUDMERSIVE_API_KEY", + "valueFrom": "STAGING_CLOUDMERSIVE_API_KEY" + }, + { + "name": "NEXT_PUBLIC_ENABLE_STORAGE", + "valueFrom": "NEXT_PUBLIC_ENABLE_STORAGE" + }, + { + "name": "NEXT_PUBLIC_ENABLE_SGID", + "valueFrom": "NEXT_PUBLIC_ENABLE_SGID" + }, + { "name": "NEXT_PUBLIC_APP_URL", "valueFrom": "NEXT_PUBLIC_APP_URL" }, + { "name": "NEXT_PUBLIC_APP_NAME", "valueFrom": "NEXT_PUBLIC_APP_NAME" }, + { + "name": "NEXT_PUBLIC_APP_VERSION", + "valueFrom": "NEXT_PUBLIC_APP_VERSION" + }, + { "name": "SGID_CLIENT_ID", "valueFrom": "SGID_CLIENT_ID" }, + { "name": "SGID_CLIENT_SECRET", "valueFrom": "SGID_CLIENT_SECRET" }, + { "name": "SGID_REDIRECT_URI", "valueFrom": "SGID_REDIRECT_URI" }, + { "name": "SGID_PRIVATE_KEY", "valueFrom": "SGID_PRIVATE_KEY" }, + { "name": "S3_REGION", "valueFrom": "S3_REGION" }, + { + "name": "S3_PUBLIC_ASSETS_DOMAIN_NAME", + "valueFrom": "S3_PUBLIC_ASSETS_DOMAIN_NAME" + }, + { + "name": "S3_UNSAFE_ASSETS_DOMAIN_NAME", + "valueFrom": "S3_UNSAFE_ASSETS_DOMAIN_NAME" + }, + { + "name": "S3_PUBLIC_ASSETS_BUCKET_NAME", + "valueFrom": "S3_PUBLIC_ASSETS_BUCKET_NAME" + }, + { + "name": "S3_UNSAFE_ASSETS_BUCKET_NAME", + "valueFrom": "S3_UNSAFE_ASSETS_BUCKET_NAME" + }, + { "name": "DATABASE_URL", "valueFrom": "DATABASE_URL" }, + { "name": "NODE_ENV", "valueFrom": "NODE_ENV" }, + { "name": "OTP_EXPIRY", "valueFrom": "OTP_EXPIRY" }, + { "name": "POSTMAN_API_KEY", "valueFrom": "POSTMAN_API_KEY" }, + { "name": "SENDGRID_API_KEY", "valueFrom": "SENDGRID_API_KEY" }, + { + "name": "SENDGRID_FROM_ADDRESS", + "valueFrom": "SENDGRID_FROM_ADDRESS" + }, + { "name": "SESSION_SECRET", "valueFrom": "SESSION_SECRET" } + ], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "/aws/ecs/studio/var/log/web.stdout.log", + "awslogs-region": "ap-southeast-1", + "awslogs-stream-prefix": "ecs" + } + } + }, + { + "name": "dd-agent", + "image": "public.ecr.aws/datadog/agent:latest", + "portMappings": [ + { + "containerPort": 8126, + "hostPort": 8126, + "protocol": "tcp" + } + ], + "essential": true, + "environment": [ + { + "name": "TZ", + "value": "Asia/Singapore" + }, + { + "name": "DD_APM_NON_LOCAL_TRAFFIC", + "value": "true" + }, + { + "name": "ECS_FARGATE", + "value": "true" + }, + { + "name": "DD_APM_ENABLED", + "value": "true" + }, + { + "name": "DD_SITE", + "value": "datadoghq.com" + }, + { + "name": "DD_ENV", + "value": "staging" + }, + { + "name": "DD_SERVICE", + "value": "isomer" + }, + { + "name": "DD_TAGS", + "value": "team:isomer,service:isomer-next" + }, + { + "name": "DD_AGENT_MAJOR_VERSION", + "value": "7" + }, + { + "name": "DD_LOGS_INJECTION", + "value": "true" + }, + { + "name": "DD_TRACE_STARTUP_LOGS", + "value": "true" + }, + { + "name": "DD_API_KEY", + "value": "" + } + ], + "dockerLabels": { + "com.datadoghq.tags.env": "", + "com.datadoghq.tags.service": "isomer-next", + "com.datadoghq.tags.version": "7", + "com.datadoghq.ad.check_names": "[\"postgres\"]", + "com.datadoghq.ad.init_configs": "[{}]", + "com.datadoghq.ad.instances": "[{\"dbm\": true, \"host\": \"\", \"port\": 5432, \"username\": \"datadog\", \"password\": \"\"}]" + }, + "mountPoints": [], + "volumesFrom": [], + "secrets": [], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "isomer-infra-staging/ecs//dd-agent", + "awslogs-region": "ap-southeast-1", + "awslogs-stream-prefix": "ecs" + } + } + } + ], + "family": "isomer-infra", + "networkMode": "awsvpc", + "volumes": [], + "placementConstraints": [], + "runtimePlatform": { + "operatingSystemFamily": "LINUX" + }, + "requiresCompatibilities": ["FARGATE"], + "taskRoleArn": "arn:aws:iam:::role/isomer-next-infra-ecs-task-role", + "executionRoleArn": "arn:aws:iam:::role/isomer-next-infra-ecs-task-exec-role", + "cpu": "", + "memory": "" +} diff --git a/.github/workflows/aws_deploy.yml b/.github/workflows/aws_deploy.yml index ec7d2504f..f80c140ee 100644 --- a/.github/workflows/aws_deploy.yml +++ b/.github/workflows/aws_deploy.yml @@ -4,65 +4,64 @@ on: workflow_call: inputs: environment: - description: 'Deployment environment' + description: "Deployment environment" required: true type: string shortEnv: - description: 'Deployment environment, the short version (one of [dev, prod, stg, test, uat, vapt])' + description: "Deployment environment, the short version (one of [dev, prod, stg, test, uat, vapt])" required: true type: string aws-account-id: - description: 'AWS account ID to use' + description: "AWS account ID to use" required: true type: string aws-region: - description: 'AWS region to use' + description: "AWS region to use" required: true - default: 'ap-southeast-1' + default: "ap-southeast-1" type: string cicd-role: - description: 'AWS IAM role to assume by GitHub action runner' + description: "AWS IAM role to assume by GitHub action runner" required: true type: string ecr-repository: - description: 'ECR repository to push image to' + description: "ECR repository to push image to" required: true type: string ecs-cluster-name: - description: 'ECS cluster to deploy to' - required: true - type: string - ecs-task-definition: - description: 'ECS task definition to use' + description: "ECS cluster to deploy to" required: true type: string ecs-task-definition-path: - description: 'ECS task definition path' - default: 'ecs-task-definition.json' + description: "ECS task definition path" + default: "ecs-task-definition.json" type: string ecs-service-name: - description: 'ECS service to deploy to' + description: "ECS service to deploy to" required: true type: string ecs-container-name: - description: 'Name of container in ECS task definition' + description: "Name of container in ECS task definition" required: true type: string codedeploy-application: - description: 'CodeDeploy application to use' + description: "CodeDeploy application to use" required: true type: string codedeploy-appspec-path: - description: 'CodeDeploy appspec.json/yml file path' - default: 'appspec.json' + description: "CodeDeploy appspec.json/yml file path" + default: "appspec.json" type: string codedeploy-deployment-group: - description: 'CodeDeploy deployment group to use' + description: "CodeDeploy deployment group to use" required: true type: string secrets: - DATADOG_API_KEY: - description: 'Datadog API key for uploading sourcemaps' + DD_API_KEY: + description: "Datadog API key for uploading sourcemaps" + required: false + RDS_READER_ENDPOINT: + description: "RDS reader endpoint for database connection" required: false permissions: @@ -70,11 +69,9 @@ permissions: contents: read jobs: - deploy staging: - name: Deploy staging + build: + name: Build and push image to ECR runs-on: ubuntu-latest - env: - DATADOG_API_KEY: ${{ secrets.DATADOG_API_KEY }} steps: - name: Checkout source code uses: actions/checkout@v3 @@ -98,7 +95,7 @@ jobs: id: login-ecr uses: aws-actions/amazon-ecr-login@v1 with: - mask-password: 'true' + mask-password: "true" - name: Build and push image to ECR uses: docker/build-push-action@v3 @@ -115,6 +112,19 @@ jobs: build-args: | ENV=${{env.ENV}} + deploy: + name: Deploy image to ECS + runs-on: ubuntu-latest + needs: build + env: + DD_API_KEY: ${{ secrets.DD_API_KEY }} + steps: + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v1 + with: + mask-password: "true" + - name: Replace AWS_ACCOUNT_ID/ENV in task definition file id: replace-variables run: | @@ -123,6 +133,15 @@ jobs: sed -i 's//${{ inputs.shortEnv }}/g' ${{ inputs.ecs-task-definition-path }} sed -i 's//${{ inputs.environment == 'production' && 1024 || 512 }}/g' ${{ inputs.ecs-task-definition-path }} sed -i 's//${{ inputs.environment == 'production' && 2048 || 1024 }}/g' ${{ inputs.ecs-task-definition-path }} + sed -i 's//${{ secrets.RDS_READER_ENDPOINT }}/g' ${{ inputs.task-definition-path }} + sed -i 's//${{ github.sha }}/g' ${{ inputs.task-definition-path }} + + - name: Replace variables in appspec + run: | + sed -i 's//${{ secrets.AWS_ACCOUNT_ID }}/g' .aws/deploy/appspec.json + sed -i 's//${{ inputs.env}}/g' .aws/deploy/appspec.json + sed -i 's//${{ inputs.ecs-container-name }}/g' .aws/deploy/appspec.json + sed -i 's//${{ inputs.ecs-container-port }}/g' .aws/deploy/appspec.json - name: Fill in the new image ID in the Amazon ECS task definition id: task-def diff --git a/.github/workflows/deploy_vapt.yml b/.github/workflows/deploy_vapt.yml new file mode 100644 index 000000000..c78a8bd48 --- /dev/null +++ b/.github/workflows/deploy_vapt.yml @@ -0,0 +1,38 @@ +name: Deploy to staging + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - vapt + +# NOTE: This is actually using our federated isomer-staging account +jobs: + deploy staging: + name: Deploy app to vapt + uses: ./.github/workflows/aws_deploy.yml + # NOTE: deploy in `staging` env to set env specific secrets + environment: staging + with: + aws-region: "ap-southeast-1" + aws-account-id: 058264420411 + cicd-role: "arn:aws:iam::058264420411:role/isomer-next-infra-github-oidc-role-aaefdfd" + ecr-repository: "isomer-next-infra-stg-ecr" + ecs-cluster-name: "isomer-next-infra-ecs" + ecs-service-name: "isomer-next-infra-ecs-service" + ecs-container-name: "studio" + ecs-container-port: "3000" + environment: "staging" + shortEnv: "stg" + codedeploy-appspec-path: .aws/deploy/appspec.json + ecs-task-definition-path: ".aws/deploy/task-definition.json" + codedeploy-application-name: "isomer-next-infra-ecs-app" + codedeploy-deployment-group: "isomer-next-infra-ecs-dg" + path-to-dockerfile: "Dockerfile" + + secrets: + DD_API_KEY: ${{ secrets.DD_API_KEY_GITHUB_ACTIONS }} + RDS_READER_ENDPOINT: ${{ secrets.RDS_READER_ENDPOINT }}