From d454b528bd2d08c81cd9f781478cc4897dbad6dc Mon Sep 17 00:00:00 2001 From: Eric Cabrel TIOGO Date: Sun, 4 Aug 2024 13:00:24 +0200 Subject: [PATCH] chore(app): define infrastructure with iac (#97) * chore(backend): define infrastructure using iac * fix: tf var * fix: tf var * fix: tf plan comment * fix: tf plan comment * fix: tf plan comment * fix: tf dns zone * fix: run plan on change * fix: run plan on change * fix: run plan on change * fix: run plan on change * fix: run plan on change * fix: run all folder * fix: run all folders * fix: run all folders * fix: run all folders * fix: define actions for tf apply * fix: plan output comment --- .github/actions/terraform-apply/action.yml | 42 ++++ .github/actions/terraform-plan/action.yml | 79 +++++++ .github/workflows/deploy-backend.yml | 6 +- .github/workflows/infra-deploy.yml | 117 ++++++++++ .github/workflows/infra-plan.yml | 119 ++++++++++ .gitignore | 4 + _infra/.env.template | 2 + _infra/README.md | 51 +++++ _infra/credentials/.terraform.lock.hcl | 24 ++ _infra/credentials/backend_dev.tf | 59 +++++ _infra/credentials/backend_prod.tf | 59 +++++ _infra/credentials/main.tf | 6 + _infra/credentials/outputs.tf | 9 + _infra/credentials/terraform.tf | 16 ++ _infra/global/.terraform.lock.hcl | 25 +++ _infra/global/main.tf | 8 + _infra/global/outputs.tf | 4 + _infra/global/terraform.tf | 16 ++ _infra/global/variables.tf | 16 ++ apps/backend/README.md | 2 +- apps/backend/_infra/README.md | 76 +++++++ .../_infra/prod/compute/.terraform.lock.hcl | 69 ++++++ apps/backend/_infra/prod/compute/data.tf | 21 ++ apps/backend/_infra/prod/compute/main.tf | 205 ++++++++++++++++++ apps/backend/_infra/prod/compute/outputs.tf | 4 + apps/backend/_infra/prod/compute/terraform.tf | 24 ++ apps/backend/_infra/prod/compute/variables.tf | 124 +++++++++++ .../_infra/prod/storage/.terraform.lock.hcl | 25 +++ apps/backend/_infra/prod/storage/main.tf | 24 ++ apps/backend/_infra/prod/storage/outputs.tf | 9 + apps/backend/_infra/prod/storage/terraform.tf | 16 ++ apps/backend/_infra/prod/storage/variables.tf | 16 ++ 32 files changed, 1272 insertions(+), 5 deletions(-) create mode 100644 .github/actions/terraform-apply/action.yml create mode 100644 .github/actions/terraform-plan/action.yml create mode 100644 .github/workflows/infra-deploy.yml create mode 100644 .github/workflows/infra-plan.yml create mode 100644 _infra/.env.template create mode 100644 _infra/README.md create mode 100644 _infra/credentials/.terraform.lock.hcl create mode 100644 _infra/credentials/backend_dev.tf create mode 100644 _infra/credentials/backend_prod.tf create mode 100644 _infra/credentials/main.tf create mode 100644 _infra/credentials/outputs.tf create mode 100644 _infra/credentials/terraform.tf create mode 100644 _infra/global/.terraform.lock.hcl create mode 100644 _infra/global/main.tf create mode 100644 _infra/global/outputs.tf create mode 100644 _infra/global/terraform.tf create mode 100644 _infra/global/variables.tf create mode 100644 apps/backend/_infra/README.md create mode 100644 apps/backend/_infra/prod/compute/.terraform.lock.hcl create mode 100644 apps/backend/_infra/prod/compute/data.tf create mode 100644 apps/backend/_infra/prod/compute/main.tf create mode 100644 apps/backend/_infra/prod/compute/outputs.tf create mode 100644 apps/backend/_infra/prod/compute/terraform.tf create mode 100644 apps/backend/_infra/prod/compute/variables.tf create mode 100644 apps/backend/_infra/prod/storage/.terraform.lock.hcl create mode 100644 apps/backend/_infra/prod/storage/main.tf create mode 100644 apps/backend/_infra/prod/storage/outputs.tf create mode 100644 apps/backend/_infra/prod/storage/terraform.tf create mode 100644 apps/backend/_infra/prod/storage/variables.tf diff --git a/.github/actions/terraform-apply/action.yml b/.github/actions/terraform-apply/action.yml new file mode 100644 index 00000000..20e3374e --- /dev/null +++ b/.github/actions/terraform-apply/action.yml @@ -0,0 +1,42 @@ +name: Run Terraform apply on every applicable directory +description: 'Terraform apply' +inputs: + APP_NAME: + required: true + description: 'The name of the application' + TF_DIRECTORY: + required: true + description: 'The directory to run Terraform apply on' + TF_WORKSPACE: + required: true + description: 'The Terraform workspace to use' + +runs: + using: 'composite' + steps: + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.9.2 + + - name: Upload Configuration + uses: hashicorp/tfc-workflows-github/actions/upload-configuration@v1.3.1 + id: apply-upload + with: + workspace: ${{ inputs.TF_WORKSPACE }} + directory: ${{ inputs.TF_DIRECTORY }} + + - name: Create Apply Run + uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.1 + id: apply-run + with: + workspace: ${{ env.TF_WORKSPACE }} + configuration_version: ${{ steps.apply-upload.outputs.configuration_version_id }} + + - name: Apply + uses: hashicorp/tfc-workflows-github/actions/apply-run@v1.3.1 + if: fromJSON(steps.apply-run.outputs.payload).data.attributes.actions.IsConfirmable + id: apply + with: + run: ${{ steps.apply-run.outputs.run_id }} + comment: "Apply Run from GitHub Actions CI ${{ github.sha }}" diff --git a/.github/actions/terraform-plan/action.yml b/.github/actions/terraform-plan/action.yml new file mode 100644 index 00000000..e34e09af --- /dev/null +++ b/.github/actions/terraform-plan/action.yml @@ -0,0 +1,79 @@ +name: Run Terraform Plan on every applicable directory +description: 'Terraform Plan' +inputs: + APP_NAME: + required: true + description: 'The name of the application' + TF_DIRECTORY: + required: true + description: 'The directory to run Terraform Plan on' + TF_WORKSPACE: + required: true + description: 'The Terraform workspace to use' + GITHUB_TOKEN: + required: true + description: 'The GitHub token to use' + +runs: + using: 'composite' + steps: + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.9.2 + + - name: Upload Configuration + uses: hashicorp/tfc-workflows-github/actions/upload-configuration@v1.3.1 + id: plan-upload + with: + workspace: ${{ inputs.TF_WORKSPACE }} + directory: ${{ inputs.TF_DIRECTORY }} + speculative: true + + - name: Create Plan Run + uses: hashicorp/tfc-workflows-github/actions/create-run@v1.3.1 + id: plan-run + with: + workspace: ${{ inputs.TF_WORKSPACE }} + configuration_version: ${{ steps.plan-upload.outputs.configuration_version_id }} + plan_only: true + + - name: Get Plan Output + uses: hashicorp/tfc-workflows-github/actions/plan-output@v1.3.1 + id: plan-output + with: + plan: ${{ fromJSON(steps.plan-run.outputs.payload).data.relationships.plan.data.id }} + + - name: Update Pull Request + uses: actions/github-script@v6 + id: plan-comment + with: + github-token: ${{ inputs.GITHUB_TOKEN }} + script: | + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + }); + const botComment = comments.find(comment => { + return comment.user.type === 'Bot' && comment.body.includes('${{ inputs.APP_NAME }} - HCP Terraform Plan Output') + }); + const output = `#### ${{ inputs.APP_NAME }} - HCP Terraform Plan Output + \`\`\` + Plan: ${{ steps.plan-output.outputs.add }} to add, ${{ steps.plan-output.outputs.change }} to change, ${{ steps.plan-output.outputs.destroy }} to destroy. + \`\`\` + [View changes in detail](${{ steps.plan-run.outputs.run_link }}) + `; + if (botComment) { + github.rest.issues.deleteComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + }); + } + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: output + }); diff --git a/.github/workflows/deploy-backend.yml b/.github/workflows/deploy-backend.yml index 3e8e502f..a143d737 100644 --- a/.github/workflows/deploy-backend.yml +++ b/.github/workflows/deploy-backend.yml @@ -58,7 +58,7 @@ jobs: env: REGISTRY: public.ecr.aws/x9y5g9l2 REGION: us-east-1 # Public ECR aren't region specific - REPOSITORY: ${{ (github.ref == 'refs/heads/main' && 'snipcode') || 'snipcode-dev' }} + REPOSITORY: ${{ (github.ref == 'refs/heads/main' && 'snipcode-backend-prod') || 'snipcode-backend-dev' }} IMAGE_TAG: ${{ inputs.version }} run: | aws ecr-public get-login-password --region $REGION | docker login --username AWS --password-stdin $REGISTRY @@ -76,6 +76,4 @@ jobs: - uses: actions/checkout@v4 - name: Deploy the application - run: | - touch file.json && echo '${{ secrets.CORE_APP_ARN }}' > file.json - aws apprunner start-deployment --region $AWS_DEFAULT_REGION --cli-input-json file://file.json + run: aws apprunner start-deployment --region $AWS_DEFAULT_REGION --service-arn ${{ secrets.CORE_APP_ARN }} diff --git a/.github/workflows/infra-deploy.yml b/.github/workflows/infra-deploy.yml new file mode 100644 index 00000000..c421a779 --- /dev/null +++ b/.github/workflows/infra-deploy.yml @@ -0,0 +1,117 @@ +name: Infrastructure Deploy + +on: + push: + branches: + - main + paths: + - '_infra/credentials/**/*.tf' + - '_infra/global/**/*.tf' + - 'apps/backend/_infra/prod/storage/**/*.tf' + - 'apps/backend/_infra/prod/compute/**/*.tf' + +env: + TF_API_TOKEN: "${{ secrets.TERRAFORM_API_TOKEN }}" + TF_CLOUD_ORGANIZATION: ${{ secrets.TERRAFORM_CLOUD_ORGANIZATION }} + TF_VAR_organization: "\"${{ secrets.TERRAFORM_CLOUD_ORGANIZATION }}\"" + +jobs: + apply-credentials: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + _infra/credentials/**/*.tf + + - name: Run terraform apply + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-apply + with: + APP_NAME: 'App Credentials' + TF_DIRECTORY: '_infra/credentials' + TF_WORKSPACE: 'snipcode-credentials' + + apply-global: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + _infra/global/**/*.tf + + - name: Run terraform apply + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-apply + with: + APP_NAME: 'App Global' + TF_DIRECTORY: '_infra/global' + TF_WORKSPACE: 'snipcode-global-prod' + + apply-backend-storage: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + apps/backend/_infra/prod/storage/**/*.tf + + - name: Run terraform apply + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-apply + with: + APP_NAME: 'Backend Storage' + TF_DIRECTORY: 'apps/backend/_infra/prod/storage' + TF_WORKSPACE: 'snipcode-backend-storage-prod' + + apply-backend-compute: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + apps/backend/_infra/prod/compute/**/*.tf + + - name: Run terraform apply + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-apply + with: + APP_NAME: 'Backend Compute' + TF_DIRECTORY: 'apps/backend/_infra/prod/compute' + TF_WORKSPACE: 'snipcode-backend-compute-prod' diff --git a/.github/workflows/infra-plan.yml b/.github/workflows/infra-plan.yml new file mode 100644 index 00000000..293acfcb --- /dev/null +++ b/.github/workflows/infra-plan.yml @@ -0,0 +1,119 @@ +name: Infrastructure Plan + +on: + pull_request: + paths: + - '_infra/credentials/**/*.tf' + - '_infra/global/**/*.tf' + - 'apps/backend/_infra/prod/storage/**/*.tf' + - 'apps/backend/_infra/prod/compute/**/*.tf' + +env: + TF_API_TOKEN: "${{ secrets.TERRAFORM_API_TOKEN }}" + TF_CLOUD_ORGANIZATION: ${{ secrets.TERRAFORM_CLOUD_ORGANIZATION }} + TF_VAR_organization: "\"${{ secrets.TERRAFORM_CLOUD_ORGANIZATION }}\"" + +jobs: + plan-credentials: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + _infra/credentials/**/*.tf + + - name: Run terraform plan + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-plan + with: + APP_NAME: 'App Credentials' + TF_DIRECTORY: '_infra/credentials' + TF_WORKSPACE: 'snipcode-credentials' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + plan-global: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + _infra/global/**/*.tf + + - name: Run terraform plan + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-plan + with: + APP_NAME: 'App Global' + TF_DIRECTORY: '_infra/global' + TF_WORKSPACE: 'snipcode-global-prod' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + plan-backend-storage: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + apps/backend/_infra/prod/storage/**/*.tf + + - name: Run terraform plan + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-plan + with: + APP_NAME: 'Backend Storage' + TF_DIRECTORY: 'apps/backend/_infra/prod/storage' + TF_WORKSPACE: 'snipcode-backend-storage-prod' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + plan-backend-compute: + runs-on: ubuntu-latest + + outputs: + infraChanged: ${{ steps.infra-changed.outputs.any_changed }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check if infra files changed + id: infra-changed + uses: tj-actions/changed-files@v44 + with: + files: | + apps/backend/_infra/prod/compute/**/*.tf + + - name: Run terraform plan + if: ${{ steps.infra-changed.outputs.any_changed == 'true' }} + uses: ./.github/actions/terraform-plan + with: + APP_NAME: 'Backend Compute' + TF_DIRECTORY: 'apps/backend/_infra/prod/compute' + TF_WORKSPACE: 'snipcode-backend-compute-prod' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 3654d67f..86fcad75 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,7 @@ key.txt .yarn/build-state.yml .yarn/install-state.gz .vercel + +.env + +.terraform diff --git a/_infra/.env.template b/_infra/.env.template new file mode 100644 index 00000000..df104788 --- /dev/null +++ b/_infra/.env.template @@ -0,0 +1,2 @@ +TF_CLOUD_ORGANIZATION= +TF_VAR_organization= diff --git a/_infra/README.md b/_infra/README.md new file mode 100644 index 00000000..413967b7 --- /dev/null +++ b/_infra/README.md @@ -0,0 +1,51 @@ +# Global infrastructure +Define the global infrastructure to used in the whole project by any services. + +## Prerequisites +Make sure you have these tools installed before running the project +* Terraform 1.9+ +* Terraform Cloud account +* Hashicorp Cloud Platform account +* AWS account +* OVH account +* Doppler account +* You defined "variables set" in the Hashicorp Cloud Platform organization for: + * AWS: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` + * Doppler: `DOPPLER_TOKEN` + * OVH: `OVH_APPLICATION_KEY`, `OVH_APPLICATION_SECRET`, and `OVH_CONSUMER_KEY` + +## Setup +Create configuration file from the template +```shell +cp .env.template .env + +# Edit configuration to set the Hashicorp Cloud Platform organization TF_CLOUD_ORGANIZATION and TF_VAR_organization +nano .env +``` + +Export the variables environment in the .env file +```shell +export $(grep -v '^#' .env | xargs) +``` + +Authenticate to HCP to generate a token on your computer +```shell +terraform login +``` +You will be redirected in the browser to perform the authentication + +Initialize the Terraform configuration for each folders +```shell +cd global +terraform init + +cd ../credentials +terraform init +``` + +Verify you can access the remote Terraform state of the workspace +```shell +terraform plan +``` + +You can now edit you configuration, plan and apply! diff --git a/_infra/credentials/.terraform.lock.hcl b/_infra/credentials/.terraform.lock.hcl new file mode 100644 index 00000000..02135efd --- /dev/null +++ b/_infra/credentials/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/dopplerhq/doppler" { + version = "1.8.0" + constraints = "1.8.0" + hashes = [ + "h1:Ljxv+eaV/2FRDGWLdLBaFq4vBWFf6ZQIhVpGneOiCSA=", + "zh:0174ee84d7699a1866d5922fb9435e38446f1d52575afc268c55f52b7f77e244", + "zh:1604257d98f94e3a206dcf2a5f709d608649296d56be5318e3b5b00f2defaad7", + "zh:29ac750bb0260e59e52e28ade8b2972fd3f8b1acc31c6d86a6b7f69dcd6db061", + "zh:3afbee2ce98e155f0a1932f5c330d4ef3e4fe71496b3c0dad1082da2d020baa6", + "zh:45ec78eed293e7645d09fb2ab6707a90de5a66b4ecb1b202ae2b39076606762d", + "zh:7155d2ce82441649a753892563b089035eac8e03b01273b41c62cacbdf2f9ec1", + "zh:80047811c40530646d72d0dd754ca791fa3b7d7032362c08119bda93ee590265", + "zh:95cf9f8746b7641b948ebf88ed74715e42e856f4a1b88987ca794bc9f7a0d648", + "zh:a4318d071eaf2717064b30894f8fc020a03e2bf21c013f14cc392c68eea1443d", + "zh:ccac8c138b95d12f90d7ea62624f8f4599c37ceb2004e08a5476573b7e0bb2dd", + "zh:e7b4a7067f6e04080fe045fc01f7cf63fc37ddf816542e7685868b6417885d6f", + "zh:ec016f1314ccce6ac80cc48f82d53b573c26e2b8215eb383caa9bea94bfa3919", + "zh:edf1e3f9d0141861efcb45e6a6d4dd1b2c3bac9435e641954eea1636a3286cd1", + "zh:fb36c53d425d9b72cf72b1d7ee84ed2cabddd25be97917ab87dd37be2532da5f", + ] +} diff --git a/_infra/credentials/backend_dev.tf b/_infra/credentials/backend_dev.tf new file mode 100644 index 00000000..311669b7 --- /dev/null +++ b/_infra/credentials/backend_dev.tf @@ -0,0 +1,59 @@ +resource "doppler_environment" "backend_dev" { + project = doppler_project.backend.name + slug = "dev" + name = "Development" +} + +resource "doppler_config" "backend_dev_config" { + project = doppler_project.backend.name + environment = doppler_environment.backend_dev.slug + name = "dev_backend" +} + +resource "doppler_service_token" "backend_dev_token" { + project = doppler_project.backend.name + config = doppler_config.backend_dev_config.name + name = "CI Token" + access = "read" +} + +variable "backend_dev_secrets_map" { + type = map(string) + default = { + "ADMIN_PASSWORD" = "", + "CONVERTKIT_API_KEY" = "", + "CONVERTKIT_FORM_ID" = "", + "CONVERTKIT_TAG_ID" = "" + "DATABASE_URL" = "" + "ENABLE_INTROSPECTION" = "false" + "GITHUB_CLIENT_ID" = "" + "GITHUB_CLIENT_SECRET" = "" + "HOST" = "http://localhost" + "JWT_SECRET" = "" + "PORT" = "7501" + "REQUEST_TIMEOUT" = "30000" + "SENTRY_DSN" = "" + "SENTRY_ENABLED" = "true" + "SESSION_LIFETIME" = "14" + "SNIPPET_RENDERER_API_URL" = "" + "SNIPPET_RENDERER_URL" = "" + "WEB_APP_URL" = "" + "WEB_AUTH_ERROR_URL" = "" + "WEB_AUTH_SUCCESS_URL" = "" + } +} + +resource "doppler_secret" "backend_dev_secrets" { + for_each = var.backend_dev_secrets_map + + project = doppler_project.backend.name + config = doppler_config.backend_dev_config.name + name = each.key + value = each.value + + lifecycle { + ignore_changes = [ + value, # Ignore changes to the secret value + ] + } +} \ No newline at end of file diff --git a/_infra/credentials/backend_prod.tf b/_infra/credentials/backend_prod.tf new file mode 100644 index 00000000..0ace17da --- /dev/null +++ b/_infra/credentials/backend_prod.tf @@ -0,0 +1,59 @@ +resource "doppler_environment" "backend_prod" { + project = doppler_project.backend.name + slug = "prod" + name = "Production" +} + +resource "doppler_config" "backend_prod_config" { + project = doppler_project.backend.name + environment = doppler_environment.backend_prod.slug + name = "prod_backend" +} + +resource "doppler_service_token" "backend_prod_token" { + project = doppler_project.backend.name + config = doppler_config.backend_prod_config.name + name = "CI Token" + access = "read" +} + +variable "backend_prod_secrets_map" { + type = map(string) + default = { + "ADMIN_PASSWORD" = "", + "CONVERTKIT_API_KEY" = "", + "CONVERTKIT_FORM_ID" = "", + "CONVERTKIT_TAG_ID" = "" + "DATABASE_URL" = "" + "ENABLE_INTROSPECTION" = "false" + "GITHUB_CLIENT_ID" = "" + "GITHUB_CLIENT_SECRET" = "" + "HOST" = "http://localhost" + "JWT_SECRET" = "" + "PORT" = "7501" + "REQUEST_TIMEOUT" = "30000" + "SENTRY_DSN" = "" + "SENTRY_ENABLED" = "true" + "SESSION_LIFETIME" = "14" + "SNIPPET_RENDERER_API_URL" = "" + "SNIPPET_RENDERER_URL" = "" + "WEB_APP_URL" = "" + "WEB_AUTH_ERROR_URL" = "" + "WEB_AUTH_SUCCESS_URL" = "" + } +} + +resource "doppler_secret" "backend_prod_secrets" { + for_each = var.backend_prod_secrets_map + + project = doppler_project.backend.name + config = doppler_config.backend_prod_config.name + name = each.key + value = each.value + + lifecycle { + ignore_changes = [ + value, # Ignore changes to the secret value + ] + } +} \ No newline at end of file diff --git a/_infra/credentials/main.tf b/_infra/credentials/main.tf new file mode 100644 index 00000000..725e0ee0 --- /dev/null +++ b/_infra/credentials/main.tf @@ -0,0 +1,6 @@ +provider "doppler" {} + +resource "doppler_project" "backend" { + name = "snipcode-backend" + description = "The main backend project" +} \ No newline at end of file diff --git a/_infra/credentials/outputs.tf b/_infra/credentials/outputs.tf new file mode 100644 index 00000000..5fe1dec0 --- /dev/null +++ b/_infra/credentials/outputs.tf @@ -0,0 +1,9 @@ +output "backend_prod_token_key" { + value = doppler_service_token.backend_prod_token.key + sensitive = true +} + +output "backend_dev_token_key" { + value = doppler_service_token.backend_dev_token.key + sensitive = true +} \ No newline at end of file diff --git a/_infra/credentials/terraform.tf b/_infra/credentials/terraform.tf new file mode 100644 index 00000000..bdf79c3a --- /dev/null +++ b/_infra/credentials/terraform.tf @@ -0,0 +1,16 @@ +terraform { + cloud { + workspaces { + name = "snipcode-credentials" + } + } + + required_providers { + doppler = { + source = "DopplerHQ/doppler" + version = "1.8.0" + } + } + + required_version = "~> 1.2" +} diff --git a/_infra/global/.terraform.lock.hcl b/_infra/global/.terraform.lock.hcl new file mode 100644 index 00000000..0a23c725 --- /dev/null +++ b/_infra/global/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.59.0" + constraints = "~> 5.59.0" + hashes = [ + "h1:mfO15RYgLZVr1BJkGP1h6Y9e3nma35BwWOyN6ukM+SU=", + "zh:077f41a15057d01d833d7438322adf9b507d17ac0c8e1287430a305b6e609775", + "zh:130b112c85b67413bc65e95e5927188d8e41b45abd75350690b93d95771a587c", + "zh:16e97f1af67a5d4c6bf4f2df824a6a332b446be4516dd85a2e097317c959a174", + "zh:1cd7b0946eaf0fb11090710e9c774d22d90de0ca4516485253be96e332ebaf73", + "zh:2591d8a269014fb59111793cb8a175aafa12e370cd856fe2522577efbb72e5be", + "zh:3db5387ecc7da4e6a55a34877ea426ae87d10238bdbdf284a52e16b4be83302c", + "zh:78169400a85912d7f05fe99d4f3ba9a56871411442bdc133083dd657b18fae4e", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ad93fedbf1d2694faab6d793c6697ff5732449cdebacaa49acf6452c0c8e2ea0", + "zh:b8a2884858dde9d204dc6855903e3078a1c402485ae85b41c28e667f99a2a777", + "zh:bd3d4bd51172d08c0df277673a25fb3f0818ef47ef9f491b0c41e880b1dedce3", + "zh:d8e132bcafee2e69e21173fac409e4b99d8c81d60a7d25c58c379c67067dbf36", + "zh:eee5113ff29a42c5a75c83e9853e99a9b5c0ed066e36d6fe251083b19d38c7eb", + "zh:f0d8bcdb01d0fa0c9ed2ca8c198d4f11aabfd9d42fa239286b65ddcc6f606dfd", + "zh:f8ae46d14ec54c275e20f71d052f1b6af0cf948819b0667016045a6244edf292", + ] +} diff --git a/_infra/global/main.tf b/_infra/global/main.tf new file mode 100644 index 00000000..95c262e6 --- /dev/null +++ b/_infra/global/main.tf @@ -0,0 +1,8 @@ +provider "aws" { + region = var.aws_region +} + +# Create Route53 Zone +resource "aws_route53_zone" "hosted_zone" { + name = var.domain_name +} diff --git a/_infra/global/outputs.tf b/_infra/global/outputs.tf new file mode 100644 index 00000000..9e9467d0 --- /dev/null +++ b/_infra/global/outputs.tf @@ -0,0 +1,4 @@ +output "hosted_zone_id" { + description = "The ID of the Route 53 hosted zone" + value = aws_route53_zone.hosted_zone.zone_id +} diff --git a/_infra/global/terraform.tf b/_infra/global/terraform.tf new file mode 100644 index 00000000..3bf416f5 --- /dev/null +++ b/_infra/global/terraform.tf @@ -0,0 +1,16 @@ +terraform { + cloud { + workspaces { + name = "snipcode-global-prod" + } + } + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.59.0" + } + } + + required_version = "~> 1.2" +} diff --git a/_infra/global/variables.tf b/_infra/global/variables.tf new file mode 100644 index 00000000..2b856ec6 --- /dev/null +++ b/_infra/global/variables.tf @@ -0,0 +1,16 @@ +variable "aws_region" { + description = "The region in which the resources will be created" + default = "eu-west-1" +} + +variable "project_name" { + default = "snipcode" +} + +variable "domain_name" { + default = "snipcode.dev" +} + +variable "environment" { + default = "prod" +} diff --git a/apps/backend/README.md b/apps/backend/README.md index b7713c6c..be03994b 100644 --- a/apps/backend/README.md +++ b/apps/backend/README.md @@ -10,7 +10,7 @@ This is the backend of Snipcode, containing the business logics related to . * Prisma ## Prerequisites -Make sure you have this tools installed before running the project +Make sure you have these tools installed before running the project * Node.js 20+ * NPM or Yarn * Docker diff --git a/apps/backend/_infra/README.md b/apps/backend/_infra/README.md new file mode 100644 index 00000000..ac18bddd --- /dev/null +++ b/apps/backend/_infra/README.md @@ -0,0 +1,76 @@ +# Backend infrastructure +Define the infrastructure needed to run this application in production which is AWS AppRunner. + +## Prerequisites +Make sure you have these tools installed before running the project +* You have configured [the global infrastructure](../../../_infra/README.md) of the project. +* The organization environment variables are exported in the shell; test by running `echo $TF_CLOUD_ORGANIZATION`. + +## Setup + +Export the variables environment in the .env file +```shell +export $(grep -v '^#' ../../../_infra/.env | xargs) +``` + +Authenticate to HCP to generate a token on your computer +```shell +terraform login +``` +You will be redirected in the browser to perform the authentication + +Initialize the Terraform configuration +```shell +cd apps/backend/_infra/prod/storage +terraform init + +cd apps/backend/_infra/prod/compute +terraform init +``` + +Verify you can access the remote Terraform state of the workspace +```shell +terraform plan +``` + +## Deploy the infrastructure from scratch +You must follow these steps to deploy the infrastructure from scratch + +Build the Docker image +```shell +cd apps/backend +docker build -t snipcode-backend:v1 --progress plain --build-arg APP_VERSION=1.1.7 -f apps/backend/Dockerfile . +``` +The APP_VERSION value is the package.json version of this project + +Deploy the resources for storing backend artefacts +```shell +cd apps/backend/_infra/prod/storage +terraform init +terraform apply +``` + +Tag the Docker image and push it to the ECR repository. Log into the public ECR repository created by Terraform +```shell +aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin $(terraform output -raw container_repository_url) + +docker tag snipcode-backend:v1 $(terraform output -raw container_repository_url):1.1.7 +docker tag snipcode-backend:v1 $(terraform output -raw container_repository_url):latest +docker push --all-tags $(terraform output -raw container_repository_url) +``` + +Deploy the resources for running the backend +```shell +cd apps/backend/_infra/prod/compute +terraform init +terraform apply -target=aws_apprunner_custom_domain_association.api_domain +terraform apply +``` + +## Deploy the infrastructure changes +There is no particular task to do when apply new changes to the infrastructure. +Once you configuration changes are ready, run the following commands: +```shell +terraform plan +terraform apply +``` diff --git a/apps/backend/_infra/prod/compute/.terraform.lock.hcl b/apps/backend/_infra/prod/compute/.terraform.lock.hcl new file mode 100644 index 00000000..89632e3d --- /dev/null +++ b/apps/backend/_infra/prod/compute/.terraform.lock.hcl @@ -0,0 +1,69 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/dopplerhq/doppler" { + version = "1.8.0" + constraints = "~> 1.8.0" + hashes = [ + "h1:Ljxv+eaV/2FRDGWLdLBaFq4vBWFf6ZQIhVpGneOiCSA=", + "zh:0174ee84d7699a1866d5922fb9435e38446f1d52575afc268c55f52b7f77e244", + "zh:1604257d98f94e3a206dcf2a5f709d608649296d56be5318e3b5b00f2defaad7", + "zh:29ac750bb0260e59e52e28ade8b2972fd3f8b1acc31c6d86a6b7f69dcd6db061", + "zh:3afbee2ce98e155f0a1932f5c330d4ef3e4fe71496b3c0dad1082da2d020baa6", + "zh:45ec78eed293e7645d09fb2ab6707a90de5a66b4ecb1b202ae2b39076606762d", + "zh:7155d2ce82441649a753892563b089035eac8e03b01273b41c62cacbdf2f9ec1", + "zh:80047811c40530646d72d0dd754ca791fa3b7d7032362c08119bda93ee590265", + "zh:95cf9f8746b7641b948ebf88ed74715e42e856f4a1b88987ca794bc9f7a0d648", + "zh:a4318d071eaf2717064b30894f8fc020a03e2bf21c013f14cc392c68eea1443d", + "zh:ccac8c138b95d12f90d7ea62624f8f4599c37ceb2004e08a5476573b7e0bb2dd", + "zh:e7b4a7067f6e04080fe045fc01f7cf63fc37ddf816542e7685868b6417885d6f", + "zh:ec016f1314ccce6ac80cc48f82d53b573c26e2b8215eb383caa9bea94bfa3919", + "zh:edf1e3f9d0141861efcb45e6a6d4dd1b2c3bac9435e641954eea1636a3286cd1", + "zh:fb36c53d425d9b72cf72b1d7ee84ed2cabddd25be97917ab87dd37be2532da5f", + ] +} + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.59.0" + constraints = "~> 5.59.0" + hashes = [ + "h1:mfO15RYgLZVr1BJkGP1h6Y9e3nma35BwWOyN6ukM+SU=", + "zh:077f41a15057d01d833d7438322adf9b507d17ac0c8e1287430a305b6e609775", + "zh:130b112c85b67413bc65e95e5927188d8e41b45abd75350690b93d95771a587c", + "zh:16e97f1af67a5d4c6bf4f2df824a6a332b446be4516dd85a2e097317c959a174", + "zh:1cd7b0946eaf0fb11090710e9c774d22d90de0ca4516485253be96e332ebaf73", + "zh:2591d8a269014fb59111793cb8a175aafa12e370cd856fe2522577efbb72e5be", + "zh:3db5387ecc7da4e6a55a34877ea426ae87d10238bdbdf284a52e16b4be83302c", + "zh:78169400a85912d7f05fe99d4f3ba9a56871411442bdc133083dd657b18fae4e", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ad93fedbf1d2694faab6d793c6697ff5732449cdebacaa49acf6452c0c8e2ea0", + "zh:b8a2884858dde9d204dc6855903e3078a1c402485ae85b41c28e667f99a2a777", + "zh:bd3d4bd51172d08c0df277673a25fb3f0818ef47ef9f491b0c41e880b1dedce3", + "zh:d8e132bcafee2e69e21173fac409e4b99d8c81d60a7d25c58c379c67067dbf36", + "zh:eee5113ff29a42c5a75c83e9853e99a9b5c0ed066e36d6fe251083b19d38c7eb", + "zh:f0d8bcdb01d0fa0c9ed2ca8c198d4f11aabfd9d42fa239286b65ddcc6f606dfd", + "zh:f8ae46d14ec54c275e20f71d052f1b6af0cf948819b0667016045a6244edf292", + ] +} + +provider "registry.terraform.io/ovh/ovh" { + version = "0.47.0" + constraints = "~> 0.47.0" + hashes = [ + "h1:L7eENzVrx6ioDfaCAvDLUstEXJGt5WITabnoCF7bvk4=", + "zh:0b57f860a4eba594794af016fb0f56db5e176dd4609df897e3032d139b870952", + "zh:36ac78ac59b8f2199a91364302e7230653fe5c26776fc9ba6db7d62323b6254f", + "zh:3947ce1dfc75dcef40256101f9dd282dffa2b244f8a4c51d5aaa7c38231d54eb", + "zh:53304426ece46de142ed26952309af2c0262919788cee321d7c44abe7f111be5", + "zh:5fe5803d849c36dc6dc6b019c0ae4ef270be14cef196a94d40ed6f9a03a2d94e", + "zh:7d218b24ef9e300d7bf0b6701193955c5508b1f74182c9cebff8705146b21e0c", + "zh:7da960c6b686c970687bcad17d8c763f078f49f5e1377658d4ad0b96a12d6a1c", + "zh:c16ad3a8f3959cd72f2aba897dafd19ea69bb4a4f5ffbf749af601eaf4836b96", + "zh:c2d73e2bc353af74441d86def7fb268729743c596b48b4676ee89f0162a25f12", + "zh:c9881c7a6eade0853946c71adc0a164f8b3bb6e9587370e626d0778ff4583c25", + "zh:cdf04a29cb8b5601d08226f6c5dc8c642709e518fcc710be6a71bc7c2ed33bad", + "zh:e34e92df061acb476b1e705374e6d9aa1b1532b431f0e8c750039d544d36194c", + "zh:ee190088be95cd43c44b30eeb7c9ec32268571d9e2285c926edfba72a6b29305", + "zh:fddd0fda38d6e3830fa4be630b9e09351ef335ccd8db7c881dcdc655daac212b", + ] +} diff --git a/apps/backend/_infra/prod/compute/data.tf b/apps/backend/_infra/prod/compute/data.tf new file mode 100644 index 00000000..40f6ac05 --- /dev/null +++ b/apps/backend/_infra/prod/compute/data.tf @@ -0,0 +1,21 @@ +data "terraform_remote_state" "storage" { + backend = "remote" + + config = { + organization = var.organization + workspaces = { + name = "snipcode-backend-storage-prod" + } + } +} + +data "terraform_remote_state" "global" { + backend = "remote" + + config = { + organization = var.organization + workspaces = { + name = "snipcode-global-prod" + } + } +} diff --git a/apps/backend/_infra/prod/compute/main.tf b/apps/backend/_infra/prod/compute/main.tf new file mode 100644 index 00000000..4be2abf5 --- /dev/null +++ b/apps/backend/_infra/prod/compute/main.tf @@ -0,0 +1,205 @@ +provider "aws" { + region = var.aws_region +} + +provider "ovh" { + endpoint = "ovh-eu" +} + +provider "doppler" {} + +provider "doppler" { + doppler_token = var.doppler_backend_prod_token + alias = "backend_prod" +} + +data "doppler_secrets" "prod" { + provider = doppler.backend_prod +} + +# Create IAM Role for App Runner +resource "aws_iam_role" "app_runner_role" { + name = "${var.project_name}-backend-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [{ + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "build.apprunner.amazonaws.com" + } + }] + }) +} + +# Attach Policy to IAM Role +resource "aws_iam_role_policy" "app_runner_policy" { + role = aws_iam_role.app_runner_role.id + + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = [ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "ecr:BatchCheckLayerAvailability" + ], + Effect = "Allow", + Resource = data.terraform_remote_state.storage.outputs.container_repository_arn + }, + { + Action = "sts:AssumeRole", + Effect = "Allow", + Resource = "*" + } + ] + }) +} + +# Create App Runner Auto Scaling Configuration +resource "aws_apprunner_auto_scaling_configuration_version" "app_runner_scaling" { + auto_scaling_configuration_name = "${var.project_name}-backend-auto-scaling" + max_concurrency = 200 + max_size = 3 + min_size = 1 +} + +# Create App Runner Service +resource "aws_apprunner_service" "api_service" { + service_name = "${var.project_name}-backend-${var.environment}" + + instance_configuration { + cpu = "1 vCPU" + memory = "2 GB" + } + + source_configuration { + auto_deployments_enabled = false + + image_repository { + image_identifier = "${data.terraform_remote_state.storage.outputs.container_repository_url}:latest" + image_repository_type = "ECR_PUBLIC" + + image_configuration { + port = data.doppler_secrets.prod.map.PORT + + runtime_environment_variables = { + NODE_ENV = "production" + ADMIN_PASSWORD = data.doppler_secrets.prod.map.ADMIN_PASSWORD + CONVERTKIT_API_KEY = data.doppler_secrets.prod.map.CONVERTKIT_API_KEY + CONVERTKIT_FORM_ID = data.doppler_secrets.prod.map.CONVERTKIT_FORM_ID + CONVERTKIT_TAG_ID = data.doppler_secrets.prod.map.CONVERTKIT_TAG_ID + DATABASE_URL = data.doppler_secrets.prod.map.DATABASE_URL + ENABLE_INTROSPECTION = data.doppler_secrets.prod.map.ENABLE_INTROSPECTION + GITHUB_CLIENT_ID = data.doppler_secrets.prod.map.GITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET = data.doppler_secrets.prod.map.GITHUB_CLIENT_SECRET + HOST = data.doppler_secrets.prod.map.HOST + JWT_SECRET = data.doppler_secrets.prod.map.JWT_SECRET + PORT = data.doppler_secrets.prod.map.PORT + REQUEST_TIMEOUT = data.doppler_secrets.prod.map.REQUEST_TIMEOUT + SENTRY_DSN = data.doppler_secrets.prod.map.SENTRY_DSN + SENTRY_ENABLED = data.doppler_secrets.prod.map.SENTRY_ENABLED + SESSION_LIFETIME = data.doppler_secrets.prod.map.SESSION_LIFETIME + SNIPPET_RENDERER_API_URL = data.doppler_secrets.prod.map.SNIPPET_RENDERER_API_URL + SNIPPET_RENDERER_URL = data.doppler_secrets.prod.map.SNIPPET_RENDERER_URL + WEB_APP_URL = data.doppler_secrets.prod.map.WEB_APP_URL + WEB_AUTH_ERROR_URL = data.doppler_secrets.prod.map.WEB_AUTH_ERROR_URL + WEB_AUTH_SUCCESS_URL = data.doppler_secrets.prod.map.WEB_AUTH_SUCCESS_URL + } + } + } + } + + auto_scaling_configuration_arn = aws_apprunner_auto_scaling_configuration_version.app_runner_scaling.arn + + tags = { + env = var.environment + } +} + +# Create ACM Certificate +resource "aws_acm_certificate" "api_domain_cert" { + domain_name = "${var.api_subdomain_name}.${var.domain_name}" + validation_method = "DNS" + + tags = { + Name = "${var.project_name}-${var.api_subdomain_name}-cert" + } +} + +# Create Route53 Record for ACM Certificate Validation +resource "aws_route53_record" "api_domain_cert_validation" { + for_each = { + for dvo in aws_acm_certificate.api_domain_cert.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + type = dvo.resource_record_type + value = dvo.resource_record_value + } + } + + zone_id = data.terraform_remote_state.global.outputs.hosted_zone_id + + name = each.value.name + type = each.value.type + ttl = 60 + records = [each.value.value] +} + +# Create Route53 Record for App Runner Service +resource "aws_route53_record" "api_domain" { + zone_id = data.terraform_remote_state.global.outputs.hosted_zone_id + name = "${var.api_subdomain_name}.${var.domain_name}" + type = "CNAME" + ttl = 300 + + records = [aws_apprunner_service.api_service.service_url] +} + +# Create Custom Domain Association +resource "aws_apprunner_custom_domain_association" "api_domain" { + service_arn = aws_apprunner_service.api_service.arn + domain_name = aws_acm_certificate.api_domain_cert.domain_name +} + +# Create DNS record for SSL certificate validation and the API domain on OVH +resource "ovh_domain_zone_record" "certificate_validation_dns_records" { + for_each = { + for dvo in aws_acm_certificate.api_domain_cert.domain_validation_options : dvo.domain_name => { + name = replace(dvo.resource_record_name, ".${var.domain_name}.", "") # Remove the domain name from the record name because it will be added in the zone field + type = dvo.resource_record_type + value = dvo.resource_record_value + } + } + + zone = var.domain_name + subdomain = each.value.name + fieldtype = each.value.type + ttl = 60 + target = each.value.value +} + +resource "ovh_domain_zone_record" "api_service_domain_validation_dns_records" { + for_each = { + for record in aws_apprunner_custom_domain_association.api_domain.certificate_validation_records : record.name => { + name = replace(record.name, ".${var.domain_name}.", "") + type = record.type + value = record.value + } + } + + zone = var.domain_name + subdomain = each.value.name + fieldtype = each.value.type + ttl = 60 + target = each.value.value +} + +resource "ovh_domain_zone_record" "api_domain_dns_record" { + zone = var.domain_name + subdomain = var.api_subdomain_name + fieldtype = "CNAME" + ttl = 60 + target = "${aws_apprunner_service.api_service.service_url}." +} diff --git a/apps/backend/_infra/prod/compute/outputs.tf b/apps/backend/_infra/prod/compute/outputs.tf new file mode 100644 index 00000000..47a849ab --- /dev/null +++ b/apps/backend/_infra/prod/compute/outputs.tf @@ -0,0 +1,4 @@ +output "app_runner_service_url" { + description = "The URL of the App Runner service" + value = aws_apprunner_service.api_service.service_url +} diff --git a/apps/backend/_infra/prod/compute/terraform.tf b/apps/backend/_infra/prod/compute/terraform.tf new file mode 100644 index 00000000..14ae62ef --- /dev/null +++ b/apps/backend/_infra/prod/compute/terraform.tf @@ -0,0 +1,24 @@ +terraform { + cloud { + workspaces { + name = "snipcode-backend-compute-prod" + } + } + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.59.0" + } + ovh = { + source = "ovh/ovh" + version = "~> 0.47.0" + } + doppler = { + source = "DopplerHQ/doppler" + version = "~> 1.8.0" + } + } + + required_version = "~> 1.2" +} diff --git a/apps/backend/_infra/prod/compute/variables.tf b/apps/backend/_infra/prod/compute/variables.tf new file mode 100644 index 00000000..e8b9bd4b --- /dev/null +++ b/apps/backend/_infra/prod/compute/variables.tf @@ -0,0 +1,124 @@ +variable "aws_region" { + description = "The region in which the resources will be created" + default = "eu-west-1" +} + +variable "organization" { + description = "The organization name" + type = string +} + +variable "project_name" { + default = "snipcode" +} + +variable "doppler_backend_prod_token" { + default = "" + sensitive = true +} + +variable "doppler_backend_dev_token" { + default = "" + sensitive = true +} + +variable "domain_name" { + default = "snipcode.dev" +} + +variable "environment" { + default = "prod" +} + +variable "api_subdomain_name" { + default = "api" +} + +variable "admin_password" { + default = "" + sensitive = true +} + +variable "convertkit_api_key" { + default = "" + sensitive = true +} + +variable "convertkit_form_id" { + default = "" + sensitive = true +} + +variable "convertkit_tag_id" { + default = "" + sensitive = true +} + +variable "database_url" { + default = "" + sensitive = true +} + +variable "enable_introspection" { + default = "false" +} + +variable "github_client_id" { + default = "" + sensitive = true +} + +variable "github_client_secret" { + default = "" + sensitive = true +} + +variable "jwt_secret" { + default = "" + sensitive = true +} + +variable "request_timeout" { + default = "30000" +} + +variable "sentry_dsn" { + default = "" + sensitive = true +} + +variable "sentry_enabled" { + default = "true" +} + +variable "session_lifetime" { + default = "90" +} + +variable "snippet_renderer_api_url" { + default = "https://apidev.snipcode.dev" +} + +variable "snippet_renderer_url" { + default = "https://embed.snipcode.dev" +} + +variable "web_app_url" { + default = "https://www.snipcode.dev" +} + +variable "web_auth_error_url" { + default = "https://www.snipcode.dev/auth/error" +} + +variable "web_auth_success_url" { + default = "https://www.snipcode.dev/auth/success" +} + +variable "port" { + default = "7501" +} + +variable "host" { + default = "http://localhost" +} diff --git a/apps/backend/_infra/prod/storage/.terraform.lock.hcl b/apps/backend/_infra/prod/storage/.terraform.lock.hcl new file mode 100644 index 00000000..0a23c725 --- /dev/null +++ b/apps/backend/_infra/prod/storage/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.59.0" + constraints = "~> 5.59.0" + hashes = [ + "h1:mfO15RYgLZVr1BJkGP1h6Y9e3nma35BwWOyN6ukM+SU=", + "zh:077f41a15057d01d833d7438322adf9b507d17ac0c8e1287430a305b6e609775", + "zh:130b112c85b67413bc65e95e5927188d8e41b45abd75350690b93d95771a587c", + "zh:16e97f1af67a5d4c6bf4f2df824a6a332b446be4516dd85a2e097317c959a174", + "zh:1cd7b0946eaf0fb11090710e9c774d22d90de0ca4516485253be96e332ebaf73", + "zh:2591d8a269014fb59111793cb8a175aafa12e370cd856fe2522577efbb72e5be", + "zh:3db5387ecc7da4e6a55a34877ea426ae87d10238bdbdf284a52e16b4be83302c", + "zh:78169400a85912d7f05fe99d4f3ba9a56871411442bdc133083dd657b18fae4e", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:ad93fedbf1d2694faab6d793c6697ff5732449cdebacaa49acf6452c0c8e2ea0", + "zh:b8a2884858dde9d204dc6855903e3078a1c402485ae85b41c28e667f99a2a777", + "zh:bd3d4bd51172d08c0df277673a25fb3f0818ef47ef9f491b0c41e880b1dedce3", + "zh:d8e132bcafee2e69e21173fac409e4b99d8c81d60a7d25c58c379c67067dbf36", + "zh:eee5113ff29a42c5a75c83e9853e99a9b5c0ed066e36d6fe251083b19d38c7eb", + "zh:f0d8bcdb01d0fa0c9ed2ca8c198d4f11aabfd9d42fa239286b65ddcc6f606dfd", + "zh:f8ae46d14ec54c275e20f71d052f1b6af0cf948819b0667016045a6244edf292", + ] +} diff --git a/apps/backend/_infra/prod/storage/main.tf b/apps/backend/_infra/prod/storage/main.tf new file mode 100644 index 00000000..b6a54f37 --- /dev/null +++ b/apps/backend/_infra/prod/storage/main.tf @@ -0,0 +1,24 @@ +provider "aws" { + region = var.aws_region +} + +# Create ECR Public Repository +resource "aws_ecrpublic_repository" "app_container_repository" { + # provider = aws.us_east_1 + + repository_name = "${var.project_name}-backend-${var.environment}" + + force_destroy = true + + catalog_data { + architectures = ["x86-64"] + operating_systems = ["Linux"] + # about_text = "About Text" + # description = "Description" + # usage_text = "Usage Text" + } + + tags = { + env = var.environment + } +} diff --git a/apps/backend/_infra/prod/storage/outputs.tf b/apps/backend/_infra/prod/storage/outputs.tf new file mode 100644 index 00000000..62a68400 --- /dev/null +++ b/apps/backend/_infra/prod/storage/outputs.tf @@ -0,0 +1,9 @@ +output "container_repository_arn" { + description = "The ARN of the public ECR repository" + value = aws_ecrpublic_repository.app_container_repository.arn +} + +output "container_repository_url" { + description = "The URL of the public ECR repository" + value = aws_ecrpublic_repository.app_container_repository.repository_uri +} \ No newline at end of file diff --git a/apps/backend/_infra/prod/storage/terraform.tf b/apps/backend/_infra/prod/storage/terraform.tf new file mode 100644 index 00000000..4e161540 --- /dev/null +++ b/apps/backend/_infra/prod/storage/terraform.tf @@ -0,0 +1,16 @@ +terraform { + cloud { + workspaces { + name = "snipcode-backend-storage-prod" + } + } + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.59.0" + } + } + + required_version = "~> 1.2" +} diff --git a/apps/backend/_infra/prod/storage/variables.tf b/apps/backend/_infra/prod/storage/variables.tf new file mode 100644 index 00000000..2525b0a2 --- /dev/null +++ b/apps/backend/_infra/prod/storage/variables.tf @@ -0,0 +1,16 @@ +variable "aws_region" { + description = "The region in which the resources will be created" + default = "us-east-1" +} + +variable "project_name" { + default = "snipcode" +} + +variable "domain_name" { + default = "snipcode.dev" +} + +variable "environment" { + default = "prod" +} \ No newline at end of file