diff --git a/.aws/task-definition.json b/.aws/task-definition.json deleted file mode 100644 index 72a9fc211..000000000 --- a/.aws/task-definition.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "taskDefinitionArn": "arn:aws:ecs:us-east-1:768512802988:task-definition/appointments-definition:24", - "containerDefinitions": [ - { - "name": "backend", - "image": "backend-latest", - "cpu": 0, - "portMappings": [ - { - "name": "backend-5000-tcp", - "containerPort": 5000, - "hostPort": 5000, - "protocol": "tcp", - "appProtocol": "http" - } - ], - "essential": true, - "environment": [ - { - "name": "FRONTEND_URL", - "value": "https://stage.appointment.day" - }, - { - "name": "SHORT_BASE_URL", - "value": "https://stage.apmt.day" - }, - { - "name": "TIER_BASIC_CALENDAR_LIMIT", - "value": "3" - }, - { - "name": "TIER_PLUS_CALENDAR_LIMIT", - "value": "5" - }, - { - "name": "TIER_PRO_CALENDAR_LIMIT", - "value": "10" - }, - { - "name": "LOG_USE_STREAM", - "value": "True" - }, - { - "name": "LOG_LEVEL", - "value": "INFO" - }, - { - "name": "APP_ENV", - "value": "stage" - }, - { - "name": "SENTRY_DSN", - "value": "https://5dddca3ecc964284bb8008bc2beef808@o4505428107853824.ingest.sentry.io/4505428124827648" - }, - { - "name": "ZOOM_API_ENABLED", - "value": "True" - }, - { - "name": "ZOOM_AUTH_CALLBACK", - "value": "https://stage.appointment.day/api/v1/zoom/callback" - }, - { - "name": "SERVICE_EMAIL", - "value": "no-reply@appointment.day" - }, - { - "name": "AUTH_SCHEME", - "value": "fxa" - }, - { - "name": "JWT_ALGO", - "value": "HS256" - }, - { - "name": "JWT_EXPIRE_IN_MINS", - "value": "10000" - } - ], - "secrets": [ - { - "name": "DATABASE_SECRETS", - "valueFrom": "arn:aws:secretsmanager:us-east-1:768512802988:secret:staging/appointment/db-mysql-Ixf6qD" - }, - { - "name": "DB_ENC_SECRET", - "valueFrom": "arn:aws:secretsmanager:us-east-1:768512802988:secret:staging/appointment/db-secret-CYKglI" - }, - { - "name": "SMTP_SECRETS", - "valueFrom": "arn:aws:secretsmanager:us-east-1:768512802988:secret:staging/appointment/socketlabs-UYmjaC" - }, - { - "name": "GOOGLE_OAUTH_SECRETS", - "valueFrom": "arn:aws:secretsmanager:us-east-1:768512802988:secret:staging/appointment/google-cal-oauth-VevaSo" - }, - { - "name": "ZOOM_SECRETS", - "valueFrom": "arn:aws:secretsmanager:us-east-1:768512802988:secret:staging/appointment/zoom-S862zi" - }, - { - "name": "FXA_SECRETS", - "valueFrom": "arn:aws:secretsmanager:us-east-1:768512802988:secret:staging/appointment/fxa-7koQF0" - } - ], - "mountPoints": [], - "volumesFrom": [], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-create-group": "true", - "awslogs-group": "/ecs/appointments-definition", - "awslogs-region": "us-east-1", - "awslogs-stream-prefix": "ecs" - } - } - }, - { - "name": "frontend", - "image": "frontend-latest", - "cpu": 0, - "portMappings": [ - { - "name": "frontend-80-tcp", - "containerPort": 80, - "hostPort": 80, - "protocol": "tcp", - "appProtocol": "http" - } - ], - "essential": true, - "environment": [], - "mountPoints": [], - "volumesFrom": [], - "logConfiguration": { - "logDriver": "awslogs", - "options": { - "awslogs-create-group": "true", - "awslogs-group": "/ecs/appointments-definition", - "awslogs-region": "us-east-1", - "awslogs-stream-prefix": "ecs" - } - } - } - ], - "family": "appointments-definition", - "executionRoleArn": "arn:aws:iam::768512802988:role/apointments-ci-role", - "networkMode": "awsvpc", - "revision": 24, - "volumes": [], - "status": "ACTIVE", - "requiresAttributes": [ - { - "name": "com.amazonaws.ecs.capability.logging-driver.awslogs" - }, - { - "name": "ecs.capability.execution-role-awslogs" - }, - { - "name": "com.amazonaws.ecs.capability.ecr-auth" - }, - { - "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19" - }, - { - "name": "ecs.capability.execution-role-ecr-pull" - }, - { - "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18" - }, - { - "name": "ecs.capability.task-eni" - }, - { - "name": "com.amazonaws.ecs.capability.docker-remote-api.1.29" - } - ], - "placementConstraints": [], - "compatibilities": [ - "EC2", - "FARGATE" - ], - "requiresCompatibilities": [ - "FARGATE" - ], - "cpu": "512", - "memory": "1024", - "runtimePlatform": { - "cpuArchitecture": "X86_64", - "operatingSystemFamily": "LINUX" - }, - "registeredAt": "2023-03-15T22:19:59.642Z", - "registeredBy": "arn:aws:iam::768512802988:user/melissa", - "tags": [] -} diff --git a/.github/workflows/aws.yml b/.github/workflows/aws.yml deleted file mode 100644 index 1ef62f5a3..000000000 --- a/.github/workflows/aws.yml +++ /dev/null @@ -1,102 +0,0 @@ -# This workflow will build and push a new container image to Amazon ECR, -# and then will deploy a new task definition to Amazon ECS, when there is a push to the "staging" branch. - -name: Deploy to Stage Environment - -# Stop any pending jobs -concurrency: - group: staging - cancel-in-progress: true - -on: - push: - branches: [ "stage" ] - -env: - AWS_REGION: us-east-1 - ECR_REPOSITORY: appointments - ECS_SERVICE: appointments-service - ECS_CLUSTER: appointments - ECS_TASK_DEFINITION: .aws/task-definition.json - - CONTAINER_FRONTEND: frontend - CONTAINER_BACKEND: backend - -permissions: - contents: read - -jobs: - deploy: - name: Build & Deploy - runs-on: ubuntu-latest - environment: - name: staging - url: https://stage.appointment.day - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Configure AWS credentials - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - aws-region: ${{ env.AWS_REGION }} - - - name: Login to Amazon ECR - id: login-ecr - uses: aws-actions/amazon-ecr-login@v2 - with: - mask-password: 'true' - - - name: Build, tag, and push backend to Amazon ECR - id: build-backend - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: backend-${{ github.sha }} - run: | - # Build a docker container and - # push it to ECR so that it can - # be deployed to ECS. - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG ./backend -f ./backend/deploy.dockerfile - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - echo "image_backend=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT - - - name: Build, tag, and push frontend to Amazon ECR - id: build-frontend - env: - ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} - IMAGE_TAG: frontend-${{ github.sha }} - run: | - # Build a docker container and - # push it to ECR so that it can - # be deployed to ECS. - docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG ./frontend -f ./frontend/deploy.dockerfile - docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG - echo "image_frontend=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT - - - name: Fill in the new backend image ID in the Amazon ECS task definition - id: task-def-backend - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ env.ECS_TASK_DEFINITION }} - container-name: ${{ env.CONTAINER_BACKEND }} - image: ${{ steps.build-backend.outputs.image_backend }} - environment-variables: "RELEASE_VERSION=${{ github.sha }}" - - - name: Fill in the new frontend image ID in the Amazon ECS task definition - id: task-def-frontend - uses: aws-actions/amazon-ecs-render-task-definition@v1 - with: - task-definition: ${{ steps.task-def-backend.outputs.task-definition }} - container-name: ${{ env.CONTAINER_FRONTEND }} - image: ${{ steps.build-frontend.outputs.image_frontend }} - - - name: Deploy Amazon ECS task definition - uses: aws-actions/amazon-ecs-deploy-task-definition@v1 - with: - task-definition: ${{ steps.task-def-frontend.outputs.task-definition }} - service: ${{ env.ECS_SERVICE }} - cluster: ${{ env.ECS_CLUSTER }} - wait-for-service-stability: true diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml deleted file mode 100644 index 89cd424ab..000000000 --- a/.github/workflows/ci-tests.yml +++ /dev/null @@ -1,49 +0,0 @@ -# This workflow will install backend's requirements and run tests - -name: Run Tests - -on: - push: - branches: [main] - pull_request: - branches: [main] - -permissions: - contents: read - -jobs: - pytest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: 'pip' - - name: Install dependencies - run: | - cd ./backend - python -m pip install --upgrade pip - python -m pip install .'[test]' - - name: Test with pytest - run: | - cd ./backend && python -m pytest - vitest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - cache: 'yarn' - cache-dependency-path: 'frontend/yarn.lock' - - name: Install dependencies - run: | - cd ./frontend - yarn install - - name: Test with vitest - run: | - cd ./frontend && yarn test --run diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml new file mode 100644 index 000000000..979798911 --- /dev/null +++ b/.github/workflows/deploy-production.yml @@ -0,0 +1,166 @@ +name: deploy-production + +concurrency: + group: deploy-production + cancel-in-progress: true + +on: + release: + types: [published] + +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + deploy-iac: + environment: production + runs-on: ubuntu-latest + outputs: + bucket: ${{ steps.output-bucket-name.outputs.bucket }} + cloudfront_id: ${{ steps.output-cloudfront-distro.outputs.cloudfront_id }} +# env: +# TF_VAR_region: ${{ vars.AWS_REGION }} +# TF_VAR_environment: ${{ vars.ENV_SHORT_NAME }} +# TF_VAR_name_prefix: "tb-${{ vars.PROJECT_SHORT_NAME }}-${{ vars.ENV_SHORT_NAME }}" +# TF_VAR_app_env: ${{ vars.APP_ENV }} +# TF_VAR_db_enc_secret: ${{ vars.DB_ENCRYPTED_SECRET }} +# TF_VAR_frontend_url: ${{ vars.FRONTEND_URL }} +# TF_VAR_fxa_secret: ${{ vars.FXA_SECRET }} +# TF_VAR_google_oauth_secret: ${{ vars.GOOGLE_OAUTH_SECRET }} +# TF_VAR_log_level: ${{ vars.LOG_LEVEL }} +# TF_VAR_short_base_url: ${{ vars.SHORT_BASE_URL }} +# TF_VAR_smtp_secret: ${{ vars.SMTP_SECRET }} +# TF_VAR_zoom_callback: ${{ vars.ZOOM_CALLBACK }} +# TF_VAR_zoom_secret: ${{ vars.zoom_secret }} +# TF_VAR_sentry_dsn: ${{ vars.SENTRY_DSN }} + steps: + - uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + role-session-name: Appointment_GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ vars.AWS_REGION }} + + - name: install opentofu + uses: opentofu/setup-opentofu@v1 + with: + tofu_version: ${{ vars.TF_VERSION }} + tofu_wrapper: false + + - name: install terragrunt + run: | + sudo wget -q -O /bin/terragrunt "https://github.com/gruntwork-io/terragrunt/releases/download/v${{ vars.TG_VERSION }}/terragrunt_linux_amd64" + sudo chmod +x /bin/terragrunt + terragrunt -v + + - name: vpc + working-directory: ./tofu/environments/stage/network/vpc + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan +# terragrunt apply tfplan # will be re-enabled once release workflow is tested + + - name: backend-infra + working-directory: ./tofu/environments/stage/services/backend-infra + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan +# terragrunt apply tfplan # will be re-enabled once release workflow is tested + + - name: cache + working-directory: ./tofu/environments/stage/data-store/cache + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan +# terragrunt apply tfplan # will be re-enabled once release workflow is tested + + - name: database + working-directory: ./tofu/environments/stage/data-store/database + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan +# terragrunt apply tfplan # will be re-enabled once release workflow is tested + + - name: frontend-infra + working-directory: ./tofu/environments/stage/services/frontend-infra + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan +# terragrunt apply tfplan # will be re-enabled once release workflow is tested + + - name: output-bucket-name + id: output-bucket-name + working-directory: ./tofu/environments/stage/services/frontend-infra + run: | + output=$(terragrunt output bucket_name | tr -d '"') + echo bucket=$output >> $GITHUB_OUTPUT + + - name: output-cloudfront-distro + id: output-cloudfront-distro + working-directory: ./tofu/environments/stage/services/frontend-infra + run: | + output=$(terragrunt output cloudfront_id) + echo cloudfront_id=$output >> $GITHUB_OUTPUT + + release-production: + name: Release to Production + needs: deploy-iac + if: startsWith(github.ref_name, 'r-') # the prefix we have added to the tag + environment: production + runs-on: ubuntu-latest + permissions: + id-token: write + contents: read + + steps: + - name: Get Artifact from Release + uses: dsaltares/fetch-gh-release-asset@master + with: + version: ${{ github.event.release.id }} + file: frontend.zip + + - name: Unzip Artifact + run: unzip frontend.zip + + - name: Get ECR tag from Release + id: get_ecr_tag + uses: dsaltares/fetch-gh-release-asset@master + with: + version: ${{ github.event.release.id }} + file: ecr_tag.txt + target: ./tofu/environments/stage/services/backend-service + + - name: Unzip ECR tag + working-directory: ./tofu/environments/stage/services/backend-service + run: unzip ecr_tag.zip + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + role-session-name: Appointment_GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ vars.AWS_REGION }} + + - name: Deploy Backend + working-directory: ./tofu/environments/stage/services/backend-service + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -var "image=$(cat steps.get_ecr_tag.outputs.*)" -out tfplan + cat tfplan +# terragrunt apply tfplan # will be re-enabled once release workflow is tested + +# will be re-enabled once release workflow is tested +# - name: Deploy frontend to S3 +# run: aws s3 sync ./frontend/frontend/dist "s3://${{ needs.deploy-iac.outputs.bucket }}" + + - name: Invalidate Cloudfront cache + run: aws cloudfront create-invalidation --distribution-id ${{ needs.deploy-iac.outputs.cloudfront_id }} --paths "/*" \ No newline at end of file diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml new file mode 100644 index 000000000..46982c571 --- /dev/null +++ b/.github/workflows/deploy-staging.yml @@ -0,0 +1,310 @@ +name: deploy-staging + +concurrency: + group: deploy-staging + cancel-in-progress: true + +on: + push: + branches: + - main + +permissions: + id-token: write # This is required for requesting the JWT + contents: write # This is required to create a release + +jobs: + detect-changes: + runs-on: ubuntu-latest + environment: staging + outputs: + deploy-iac: ${{ steps.check.outputs.deploy-iac }} + deploy-app: ${{ steps.check.outputs.deploy-app }} + steps: + - uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: check + with: + filters: | + deploy-iac: + - 'tofu/modules/**' + - 'tofu/environments/stage/**' + - '.github/workflows/deploy-staging.yml' + deploy-app: + - 'backend/**' + - 'tofu/modules/services/backend-service/**' + - 'tofu/environments/stage/services/backend-service/**' + - 'frontend/**' + - 'tofu/modules/services/frontend-infra/**' + - 'tofu/environments/stage/services/frontend-infra/**' + - '.github/workflows/deploy-staging.yml' + + deploy-iac: + needs: detect-changes + if: needs.detect-changes.outputs.deploy-iac == 'true' + environment: staging + runs-on: ubuntu-latest + outputs: + bucket: ${{ steps.output-bucket-name.outputs.bucket }} + cloudfront_id: ${{ steps.output-cloudfront-distro.outputs.cloudfront_id }} + env: + TF_VAR_region: ${{ vars.AWS_REGION }} + TF_VAR_environment: ${{ vars.ENV_SHORT_NAME }} + TF_VAR_name_prefix: "tb-${{ vars.PROJECT_SHORT_NAME }}-${{ vars.ENV_SHORT_NAME }}" + TF_VAR_app_env: ${{ vars.APP_ENV }} + TF_VAR_db_enc_secret: ${{ vars.DB_ENCRYPTED_SECRET }} + TF_VAR_frontend_url: ${{ vars.FRONTEND_URL }} + TF_VAR_fxa_secret: ${{ vars.FXA_SECRET }} + TF_VAR_google_oauth_secret: ${{ vars.GOOGLE_OAUTH_SECRET }} + TF_VAR_log_level: ${{ vars.LOG_LEVEL }} + TF_VAR_short_base_url: ${{ vars.SHORT_BASE_URL }} + TF_VAR_smtp_secret: ${{ vars.SMTP_SECRET }} + TF_VAR_zoom_callback: ${{ vars.ZOOM_CALLBACK }} + TF_VAR_zoom_secret: ${{ vars.zoom_secret }} + TF_VAR_sentry_dsn: ${{ vars.SENTRY_DSN }} + steps: + - uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + role-session-name: Appointment_GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ vars.AWS_REGION }} + + - name: install opentofu + uses: opentofu/setup-opentofu@v1 + with: + tofu_version: ${{ vars.TF_VERSION }} + tofu_wrapper: false + + - name: install terragrunt + run: | + sudo wget -q -O /bin/terragrunt "https://github.com/gruntwork-io/terragrunt/releases/download/v${{ vars.TG_VERSION }}/terragrunt_linux_amd64" + sudo chmod +x /bin/terragrunt + terragrunt -v + + - name: vpc + working-directory: ./tofu/environments/stage/network/vpc + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan + terragrunt apply tfplan + + - name: backend-infra + working-directory: ./tofu/environments/stage/services/backend-infra + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan + terragrunt apply tfplan + + - name: cache + working-directory: ./tofu/environments/stage/data-store/cache + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan + terragrunt apply tfplan + + - name: database + working-directory: ./tofu/environments/stage/data-store/database + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan + terragrunt apply tfplan + + - name: frontend-infra + working-directory: ./tofu/environments/stage/services/frontend-infra + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -out tfplan + terragrunt apply tfplan + + - name: output-bucket-name + id: output-bucket-name + working-directory: ./tofu/environments/stage/services/frontend-infra + run: | + output=$(terragrunt output bucket_name | tr -d '"') + echo bucket=$output >> $GITHUB_OUTPUT + + - name: output-cloudfront-distro + id: output-cloudfront-distro + working-directory: ./tofu/environments/stage/services/frontend-infra + run: | + output=$(terragrunt output cloudfront_id) + echo cloudfront_id=$output >> $GITHUB_OUTPUT + + deploy-frontend: + needs: + - detect-changes + - deploy-iac + if: needs.detect-changes.outputs.deploy-app == 'true' + environment: staging + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup NPM + uses: actions/setup-node@v4 + with: + node-version: '18.x' + + - name: Install dependencies + run: cd frontend && yarn install + + - name: Build project + run: | + cp frontend/.env.stage.example frontend/.env.stage + cd frontend && yarn build --mode ${{ vars.APP_ENV }} + + - name: Install AWS CLI + uses: unfor19/install-aws-cli-action@v1 + with: + version: 2 + arch: amd64 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + role-session-name: Appointment_GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ vars.AWS_REGION }} + + - name: Invalidate Cloudfront cache + run: aws cloudfront create-invalidation --distribution-id ${{ needs.deploy-iac.outputs.cloudfront_id }} --paths "/*" + + - name: Archive Frontend + uses: actions/upload-artifact@v4 + with: + name: frontend + path: frontend/dist + + - name: Deploy frontend to S3 + run: aws s3 sync frontend/dist "s3://${{ needs.deploy-iac.outputs.bucket }}" + + + + deploy-backend: + needs: + - detect-changes + - deploy-iac + if: needs.detect-changes.outputs.deploy-app == 'true' + environment: staging + runs-on: ubuntu-latest + env: + TF_VAR_region: ${{ vars.AWS_REGION }} + TF_VAR_environment: ${{ vars.ENV_SHORT_NAME }} + TF_VAR_name_prefix: "tb-${{ vars.PROJECT_SHORT_NAME }}-${{ vars.ENV_SHORT_NAME }}" + TF_VAR_app_env: ${{ vars.APP_ENV }} + TF_VAR_db_enc_secret: ${{ vars.DB_ENCRYPTED_SECRET }} + TF_VAR_frontend_url: ${{ vars.FRONTEND_URL }} + TF_VAR_fxa_secret: ${{ vars.FXA_SECRET }} + TF_VAR_google_oauth_secret: ${{ vars.GOOGLE_OAUTH_SECRET }} + TF_VAR_log_level: ${{ vars.LOG_LEVEL }} + TF_VAR_short_base_url: ${{ vars.SHORT_BASE_URL }} + TF_VAR_smtp_secret: ${{ vars.SMTP_SECRET }} + TF_VAR_zoom_callback: ${{ vars.ZOOM_CALLBACK }} + TF_VAR_zoom_secret: ${{ vars.ZOOM_SECRET }} + TF_VAR_sentry_dsn: ${{ vars.SENTRY_DSN }} + steps: + - uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + role-session-name: Appointment_GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ vars.AWS_REGION }} + + - name: install opentofu + uses: opentofu/setup-opentofu@v1 + with: + tofu_version: ${{ vars.TF_VERSION }} + tofu_wrapper: false + + - name: install terragrunt + run: | + sudo wget -q -O /bin/terragrunt "https://github.com/gruntwork-io/terragrunt/releases/download/v${{ vars.TG_VERSION }}/terragrunt_linux_amd64" + sudo chmod +x /bin/terragrunt + terragrunt -v + + - name: Login to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + with: + mask-password: 'true' + + - name: Build, tag, and push backend image to Amazon ECR + id: build-backend + env: + ECR_TAG: '${{ steps.login-ecr.outputs.registry }}/${{ vars.PROJECT }}:backend-${{ github.sha }}' + run: | + # Build a docker container and + # push it to ECR so that it can + # be deployed to ECS. + docker build -t $ECR_TAG ./backend -f ./backend/deploy.dockerfile + docker push $ECR_TAG + echo "image_backend=$ECR_TAG" >> $GITHUB_OUTPUT + echo $ECR_TAG > ecr_tag.txt + + - name: Archive ECR tag + uses: actions/upload-artifact@v4 + with: + name: ecr_tag + path: ecr_tag.txt + + - name: deploy backend-service + working-directory: ./tofu/environments/stage/services/backend-service + run: | + terragrunt init -upgrade + terragrunt validate + terragrunt plan -var 'image=${{ steps.build-backend.outputs.image_backend }}' -out tfplan + terragrunt apply tfplan + + create-release: + needs: + - detect-changes + - deploy-backend + - deploy-frontend + if: needs.detect-changes.outputs.deploy-app == 'true' + environment: staging + runs-on: ubuntu-latest + steps: + - name: download artifact + uses: actions/download-artifact@v4 + with: + name: + frontend + + - name: download ecr tag + uses: actions/download-artifact@v4 + with: + name: + ecr_tag + + - name: create release tag + id: create-release-tag + run: echo "tag_name=r-$(printf %04d $GITHUB_RUN_NUMBER)" >> $GITHUB_OUTPUT + + - name: create draft release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.create-release-tag.outputs.tag_name }} + name: Release ${{ steps.create-release-tag.outputs.tag_name }} + body: | + ## Info + Commit ${{ github.sha }} was deployed to `stage`. [See code diff](${{ github.event.compare }}). + + It was initialized by [${{ github.event.sender.login }}](${{ github.event.sender.html_url }}). + + ## How to Promote? + In order to promote this to prod, edit the draft and press **"Publish release"**. + draft: true + files: | + frontend.zip + ecr_tag.zip diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 000000000..c18171a91 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,171 @@ +name: validate + +concurrency: + group: validate + cancel-in-progress: true + +on: + push: + branches: + - '**' + - '!main' + +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + detect-changes: + runs-on: ubuntu-latest + environment: staging + outputs: + validate-iac: ${{ steps.check.outputs.validate-iac }} + validate-backend: ${{ steps.check.outputs.validate-backend }} + validate-frontend: ${{ steps.check.outputs.validate-frontend }} + steps: + - uses: actions/checkout@v4 + + - uses: dorny/paths-filter@v3 + id: check + with: + filters: | + validate-iac: + - 'tofu/**' + - '.github/workflows/validate.yml' + validate-backend: + - 'backend/**' + - '.github/workflows/validate.yml' + validate-frontend: + - 'frontend/**' + - '.github/workflows/validate.yml' + + validate-iac: + needs: detect-changes + runs-on: ubuntu-latest + environment: staging + if: needs.detect-changes.outputs.validate-iac == 'true' + env: + TF_VAR_region: ${{ vars.AWS_REGION }} + TF_VAR_environment: ${{ vars.ENV_SHORT_NAME }} + TF_VAR_name_prefix: "tb-${{ vars.PROJECT_SHORT_NAME }}-${{ vars.ENV_SHORT_NAME }}" + TF_VAR_app_env: ${{ vars.APP_ENV }} + TF_VAR_db_enc_secret: ${{ vars.DB_ENCRYPTED_SECRET }} + TF_VAR_frontend_url: ${{ vars.FRONTEND_URL }} + TF_VAR_fxa_secret: ${{ vars.FXA_SECRET }} + TF_VAR_google_oauth_secret: ${{ vars.GOOGLE_OAUTH_SECRET }} + TF_VAR_log_level: ${{ vars.LOG_LEVEL }} + TF_VAR_short_base_url: ${{ vars.SHORT_BASE_URL }} + TF_VAR_smtp_secret: ${{ vars.SMTP_SECRET }} + TF_VAR_zoom_callback: ${{ vars.ZOOM_CALLBACK }} + TF_VAR_zoom_secret: ${{ vars.zoom_secret }} + TF_VAR_sentry_dsn: ${{ vars.SENTRY_DSN }} + steps: + - uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.IAM_ROLE }} + role-session-name: GitHub_to_AWS_via_FederatedOIDC + aws-region: ${{ vars.AWS_REGION }} + + - name: install opentofu + uses: opentofu/setup-opentofu@v1 + with: + tofu_version: ${{ vars.TF_VERSION }} + tofu_wrapper: false + + - name: install terragrunt + run: | + sudo wget -q -O /bin/terragrunt "https://github.com/gruntwork-io/terragrunt/releases/download/v${{ vars.TG_VERSION }}/terragrunt_linux_amd64" + sudo chmod +x /bin/terragrunt + terragrunt -v + + - name: vpc + working-directory: ./tofu/environments/stage/network/vpc + continue-on-error: true + run: | + terragrunt init -upgrade + terragrunt validate + + - name: backend-infra + working-directory: ./tofu/environments/stage/services/backend-infra + continue-on-error: true + run: | + terragrunt init -upgrade + terragrunt validate + + - name: cache + working-directory: ./tofu/environments/stage/data-store/cache + continue-on-error: true + run: | + terragrunt init -upgrade + terragrunt validate + + - name: database + working-directory: ./tofu/environments/stage/data-store/database + continue-on-error: true + run: | + terragrunt init -upgrade + terragrunt validate + + - name: frontend-infra + working-directory: ./tofu/environments/stage/services/frontend-infra + continue-on-error: true + run: | + terragrunt init -upgrade + terragrunt validate + + - name: backend-service + working-directory: ./tofu/environments/stage/services/backend-service + run: | + terragrunt init -upgrade + terragrunt validate + + validate-backend: + needs: detect-changes + runs-on: ubuntu-latest + environment: staging + if: needs.detect-changes.outputs.validate-backend == 'true' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + cache: 'pip' + + - name: Install dependencies + run: | + cd ./backend + python -m pip install --upgrade pip + python -m pip install .'[test]' + + - name: Test with pytest + run: | + cd ./backend && python -m pytest + + validate-frontend: + needs: detect-changes + runs-on: ubuntu-latest + environment: staging + if: needs.detect-changes.outputs.validate-frontend == 'true' + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 18 + cache: 'yarn' + cache-dependency-path: 'frontend/yarn.lock' + + - name: Install dependencies + run: | + cd ./frontend + yarn install + + - name: Test with vitest + run: | + cd ./frontend && yarn test --run diff --git a/.gitignore b/.gitignore index 28b32f1c3..c5fdbdc42 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,44 @@ venv .coverage htmlcov caldav + +# Mac noise +**/.DS_Store + +# Terragrunt +**/.terragrunt-cache + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc \ No newline at end of file diff --git a/frontend/.env.staging.example b/frontend/.env.stage.example similarity index 100% rename from frontend/.env.staging.example rename to frontend/.env.stage.example diff --git a/frontend/deploy.dockerfile b/frontend/deploy.dockerfile index 8aa183c14..10f573074 100644 --- a/frontend/deploy.dockerfile +++ b/frontend/deploy.dockerfile @@ -3,8 +3,8 @@ FROM nginx:stable # Copy over files COPY . /build/frontend -# Copy over the staging config -RUN mv /build/frontend/.env.staging.example /build/frontend/.env.staging +# Copy over the stage config +RUN mv /build/frontend/.env.stage.example /build/frontend/.env.stage # Add Node 18 support RUN apt-get update @@ -19,7 +19,7 @@ RUN npm install --global yarn # Build site RUN cd /build/frontend && yarn install -RUN cd /build/frontend && yarn build --mode staging +RUN cd /build/frontend && yarn build --mode stage # Use our custom nginx config RUN rm /etc/nginx/conf.d/default.conf diff --git a/frontend/index.html b/frontend/index.html index 5578045d1..a37594f83 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -27,4 +27,5 @@