From c059f844b0b998d635ac17cfa3b36ff60cbf3f58 Mon Sep 17 00:00:00 2001 From: Sergey Davtyan Date: Sun, 20 Sep 2026 13:09:05 +0400 Subject: [PATCH] tf ver --- .../workflows/call-docker-build-result.yaml | 82 - .github/workflows/call-docker-build-vote.yaml | 82 - .../workflows/call-docker-build-worker.yaml | 82 - .github/workflows/deploy-ecs.yml | 148 + result/server.js | 27 +- terraform/.terraform.lock.hcl | 46 + terraform/README.md | 294 + terraform/acm.tf | 41 + terraform/alb.tf | 137 + terraform/aurora.tf | 57 + terraform/autoscaling.tf | 133 + terraform/backend.tf | 9 + terraform/dashboard.tf | 157 + terraform/data.tf | 37 + terraform/ecr.tf | 95 + terraform/ecs.tf | 247 + terraform/errored.tfstate | 4764 +++++++++++++++++ terraform/graph.dot | 63 + terraform/iam.tf | 67 + terraform/logs.tf | 20 + terraform/outputs.tf | 112 + terraform/route53.tf | 25 + terraform/secrets.tf | 52 + terraform/sg.tf | 111 + terraform/terraform.tfvars.example | 27 + terraform/valkey.tf | 67 + terraform/variables.tf | 171 + terraform/versions.tf | 25 + vote/app.py | 23 +- vote/requirements.txt | 2 +- worker/Dockerfile | 27 +- worker/Program.cs | 192 +- 32 files changed, 7080 insertions(+), 342 deletions(-) delete mode 100644 .github/workflows/call-docker-build-result.yaml delete mode 100644 .github/workflows/call-docker-build-vote.yaml delete mode 100644 .github/workflows/call-docker-build-worker.yaml create mode 100644 .github/workflows/deploy-ecs.yml create mode 100644 terraform/.terraform.lock.hcl create mode 100644 terraform/README.md create mode 100644 terraform/acm.tf create mode 100644 terraform/alb.tf create mode 100644 terraform/aurora.tf create mode 100644 terraform/autoscaling.tf create mode 100644 terraform/backend.tf create mode 100644 terraform/dashboard.tf create mode 100644 terraform/data.tf create mode 100644 terraform/ecr.tf create mode 100644 terraform/ecs.tf create mode 100644 terraform/errored.tfstate create mode 100644 terraform/graph.dot create mode 100644 terraform/iam.tf create mode 100644 terraform/logs.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/route53.tf create mode 100644 terraform/secrets.tf create mode 100644 terraform/sg.tf create mode 100644 terraform/terraform.tfvars.example create mode 100644 terraform/valkey.tf create mode 100644 terraform/variables.tf create mode 100644 terraform/versions.tf diff --git a/.github/workflows/call-docker-build-result.yaml b/.github/workflows/call-docker-build-result.yaml deleted file mode 100644 index a946a87b03..0000000000 --- a/.github/workflows/call-docker-build-result.yaml +++ /dev/null @@ -1,82 +0,0 @@ -name: Build Result -# template source: https://github.com/dockersamples/.github/blob/main/templates/call-docker-build.yaml - -on: - # we want pull requests so we can build(test) but not push to image registry - push: - branches: - - 'main' - # only build when important files change - paths: - - 'result/**' - - '.github/workflows/call-docker-build-result.yaml' - pull_request: - branches: - - 'main' - # only build when important files change - paths: - - 'result/**' - - '.github/workflows/call-docker-build-result.yaml' - -jobs: - call-docker-build: - - name: Result Call Docker Build - - uses: dockersamples/.github/.github/workflows/reusable-docker-build.yaml@main - - permissions: - contents: read - packages: write # needed to push docker image to ghcr.io - pull-requests: write # needed to create and update comments in PRs - - secrets: - - # Only needed if with:dockerhub-enable is true below - dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} - - # Only needed if with:dockerhub-enable is true below - dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - - with: - - ### REQUIRED - ### ENABLE ONE OR BOTH REGISTRIES - ### tell docker where to push. - ### NOTE if Docker Hub is set to true, you must set secrets above and also add account/repo/tags below - dockerhub-enable: true - ghcr-enable: true - - ### REQUIRED - ### A list of the account/repo names for docker build. List should match what's enabled above - ### defaults to: - image-names: | - ghcr.io/dockersamples/example-voting-app-result - dockersamples/examplevotingapp_result - - ### REQUIRED set rules for tagging images, based on special action syntax: - ### https://github.com/docker/metadata-action#tags-input - ### defaults to: - tag-rules: | - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=raw,value=before,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=raw,value=after,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=pr - - ### path to where docker should copy files into image - ### defaults to root of repository (.) - context: result - - ### Dockerfile alternate name. Default is Dockerfile (relative to context path) - # file: Containerfile - - ### build stage to target, defaults to empty, which builds to last stage in Dockerfile - # target: - - ### platforms to build for, defaults to linux/amd64 - ### other options: linux/amd64,linux/arm64,linux/arm/v7 - platforms: linux/amd64,linux/arm64,linux/arm/v7 - - ### Create a PR comment with image tags and labels - ### defaults to false - # comment-enable: false diff --git a/.github/workflows/call-docker-build-vote.yaml b/.github/workflows/call-docker-build-vote.yaml deleted file mode 100644 index cb4a484a2a..0000000000 --- a/.github/workflows/call-docker-build-vote.yaml +++ /dev/null @@ -1,82 +0,0 @@ -name: Build Vote -# template source: https://github.com/dockersamples/.github/blob/main/templates/call-docker-build.yaml - -on: - # we want pull requests so we can build(test) but not push to image registry - push: - branches: - - 'main' - # only build when important files change - paths: - - 'vote/**' - - '.github/workflows/call-docker-build-vote.yaml' - pull_request: - branches: - - 'main' - # only build when important files change - paths: - - 'vote/**' - - '.github/workflows/call-docker-build-vote.yaml' - -jobs: - call-docker-build: - - name: Vote Call Docker Build - - uses: dockersamples/.github/.github/workflows/reusable-docker-build.yaml@main - - permissions: - contents: read - packages: write # needed to push docker image to ghcr.io - pull-requests: write # needed to create and update comments in PRs - - secrets: - - # Only needed if with:dockerhub-enable is true below - dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} - - # Only needed if with:dockerhub-enable is true below - dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - - with: - - ### REQUIRED - ### ENABLE ONE OR BOTH REGISTRIES - ### tell docker where to push. - ### NOTE if Docker Hub is set to true, you must set secrets above and also add account/repo/tags below - dockerhub-enable: true - ghcr-enable: true - - ### REQUIRED - ### A list of the account/repo names for docker build. List should match what's enabled above - ### defaults to: - image-names: | - ghcr.io/dockersamples/example-voting-app-vote - dockersamples/examplevotingapp_vote - - ### REQUIRED set rules for tagging images, based on special action syntax: - ### https://github.com/docker/metadata-action#tags-input - ### defaults to: - tag-rules: | - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=raw,value=before,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=raw,value=after,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=pr - - ### path to where docker should copy files into image - ### defaults to root of repository (.) - context: vote - - ### Dockerfile alternate name. Default is Dockerfile (relative to context path) - # file: Containerfile - - ### build stage to target, defaults to empty, which builds to last stage in Dockerfile - # target: - - ### platforms to build for, defaults to linux/amd64 - ### other options: linux/amd64,linux/arm64,linux/arm/v7 - platforms: linux/amd64,linux/arm64,linux/arm/v7 - - ### Create a PR comment with image tags and labels - ### defaults to false - # comment-enable: false diff --git a/.github/workflows/call-docker-build-worker.yaml b/.github/workflows/call-docker-build-worker.yaml deleted file mode 100644 index 5abfb6bc9c..0000000000 --- a/.github/workflows/call-docker-build-worker.yaml +++ /dev/null @@ -1,82 +0,0 @@ -name: Build Worker -# template source: https://github.com/dockersamples/.github/blob/main/templates/call-docker-build.yaml - -on: - # we want pull requests so we can build(test) but not push to image registry - push: - branches: - - 'main' - # only build when important files change - paths: - - 'worker/**' - - '.github/workflows/call-docker-build-worker.yaml' - pull_request: - branches: - - 'main' - # only build when important files change - paths: - - 'worker/**' - - '.github/workflows/call-docker-build-worker.yaml' - -jobs: - call-docker-build: - - name: Worker Call Docker Build - - uses: dockersamples/.github/.github/workflows/reusable-docker-build.yaml@main - - permissions: - contents: read - packages: write # needed to push docker image to ghcr.io - pull-requests: write # needed to create and update comments in PRs - - secrets: - - # Only needed if with:dockerhub-enable is true below - dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} - - # Only needed if with:dockerhub-enable is true below - dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }} - - with: - - ### REQUIRED - ### ENABLE ONE OR BOTH REGISTRIES - ### tell docker where to push. - ### NOTE if Docker Hub is set to true, you must set secrets above and also add account/repo/tags below - dockerhub-enable: true - ghcr-enable: true - - ### REQUIRED - ### A list of the account/repo names for docker build. List should match what's enabled above - ### defaults to: - image-names: | - ghcr.io/dockersamples/example-voting-app-worker - dockersamples/examplevotingapp_worker - - ### REQUIRED set rules for tagging images, based on special action syntax: - ### https://github.com/docker/metadata-action#tags-input - ### defaults to: - tag-rules: | - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - type=ref,event=pr - - ### path to where docker should copy files into image - ### defaults to root of repository (.) - context: worker - - ### Dockerfile alternate name. Default is Dockerfile (relative to context path) - # file: Containerfile - - ### build stage to target, defaults to empty, which builds to last stage in Dockerfile - # target: - - ### platforms to build for, defaults to linux/amd64 - ### other options: linux/amd64,linux/arm64,linux/arm/v7 - # FIXME worker arm/v7 support doesn't build in .net core 3.1 with QEMU - # a fix would likely run the .net build on amd64 but with a target of arm/v7 - platforms: linux/amd64,linux/arm64,linux/arm/v7 - - ### Create a PR comment with image tags and labels - ### defaults to false - # comment-enable: false diff --git a/.github/workflows/deploy-ecs.yml b/.github/workflows/deploy-ecs.yml new file mode 100644 index 0000000000..cae0976ff7 --- /dev/null +++ b/.github/workflows/deploy-ecs.yml @@ -0,0 +1,148 @@ +name: Infrastructure & ECS Deployment + +on: + push: + branches: + - main + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + id-token: write + contents: read + pull-requests: write + +env: + AWS_REGION: eu-north-1 + AWS_ROLE_ARN: arn:aws:iam::597936860210:role/github-oidc-pipeline-role1 + ECS_CLUSTER: aca-voting-app-cluster + APP_NAME: aca-voting-app + +jobs: + voting-terraform-ci: + name: 1. Infrastructure CI/CD + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./terraform + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: "1.15.8" + + - name: Configure AWS Credentials via GitHub OIDC Role + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME || env.AWS_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + + - name: Terraform Format + run: terraform fmt -check + + - name: Terraform Init + run: terraform init + + - name: Terraform Validate + run: terraform validate -no-color + + - name: Terraform Plan + run: terraform plan -no-color -input=false + + - name: Terraform Apply + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + run: terraform apply -auto-approve -input=false + + build-and-push: + name: 2. Build and Push Docker Images + needs: voting-terraform-ci + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + outputs: + ecr_registry: ${{ steps.login-ecr.outputs.registry }} + image_tag: ${{ steps.prep.outputs.tag }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate Image Tag + id: prep + run: echo "tag=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT + + - name: Configure AWS Credentials via GitHub OIDC Role + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME || env.AWS_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + + - name: Log in to Amazon ECR + id: login-ecr + uses: aws-actions/amazon-ecr-login@v2 + + - name: Build and Push Vote Image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ steps.prep.outputs.tag }} + run: | + REPO_URI="$ECR_REGISTRY/${APP_NAME}-vote" + echo "Building $REPO_URI:$IMAGE_TAG..." + docker build -t $REPO_URI:$IMAGE_TAG -t $REPO_URI:latest ./vote + docker push $REPO_URI:$IMAGE_TAG + docker push $REPO_URI:latest + + - name: Build and Push Result Image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ steps.prep.outputs.tag }} + run: | + REPO_URI="$ECR_REGISTRY/${APP_NAME}-result" + echo "Building $REPO_URI:$IMAGE_TAG..." + docker build -t $REPO_URI:$IMAGE_TAG -t $REPO_URI:latest ./result + docker push $REPO_URI:$IMAGE_TAG + docker push $REPO_URI:latest + + - name: Build and Push Worker Image + env: + ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} + IMAGE_TAG: ${{ steps.prep.outputs.tag }} + run: | + REPO_URI="$ECR_REGISTRY/${APP_NAME}-worker" + echo "Building $REPO_URI:$IMAGE_TAG..." + docker build -t $REPO_URI:$IMAGE_TAG -t $REPO_URI:latest ./worker + docker push $REPO_URI:$IMAGE_TAG + docker push $REPO_URI:latest + + deploy-to-ecs: + name: 3. Deploy to ECS Services + needs: [voting-terraform-ci, build-and-push] + if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Configure AWS Credentials via GitHub OIDC Role + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME || env.AWS_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + + - name: Force New ECS Deployment for Services + run: | + aws ecs update-service --cluster ${{ env.ECS_CLUSTER }} --service vote --force-new-deployment + aws ecs update-service --cluster ${{ env.ECS_CLUSTER }} --service result --force-new-deployment + aws ecs update-service --cluster ${{ env.ECS_CLUSTER }} --service worker --force-new-deployment + + - name: Wait for Services to Stabilize + run: | + aws ecs wait services-stable --cluster ${{ env.ECS_CLUSTER }} --services vote + aws ecs wait services-stable --cluster ${{ env.ECS_CLUSTER }} --services result + aws ecs wait services-stable --cluster ${{ env.ECS_CLUSTER }} --services worker diff --git a/result/server.js b/result/server.js index 1c8593e7ee..c48196c453 100644 --- a/result/server.js +++ b/result/server.js @@ -1,6 +1,5 @@ var express = require('express'), async = require('async'), - { Pool } = require('pg'), cookieParser = require('cookie-parser'), app = express(), server = require('http').Server(app), @@ -17,8 +16,26 @@ io.on('connection', function (socket) { }); }); -var pool = new Pool({ - connectionString: 'postgres://postgres:postgres@db/postgres' +const { Pool } = require('pg'); + +const dbHost = process.env.POSTGRES_HOST || 'db'; +const dbPort = parseInt(process.env.POSTGRES_PORT || '5432', 10); +const dbUser = process.env.POSTGRES_USER || 'postgres'; +const dbPassword = process.env.POSTGRES_PASSWORD || 'postgres'; +const dbName = process.env.POSTGRES_DB || 'postgres'; +const useSSL = process.env.POSTGRES_SSL === 'true' || process.env.POSTGRES_SSL === '1'; + +const pool = new Pool({ + host: dbHost, + port: dbPort, + user: dbUser, + password: dbPassword, + database: dbName, + ssl: useSSL ? { + rejectUnauthorized: false, + require: true + } : false, + connectionTimeoutMillis: 5000 // Fails fast instead of hanging on timeouts }); async.retry( @@ -26,7 +43,9 @@ async.retry( function(callback) { pool.connect(function(err, client, done) { if (err) { - console.error("Waiting for db"); + console.error("Waiting for db. Error details:", err.message, err.code || '', err.stack); + if (done) done(); // Release client back to pool on failure + return callback(err); } callback(err, client); }); diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 0000000000..596ab4e965 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,46 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.100.0" + constraints = ">= 4.66.1, >= 5.0.0, ~> 5.0, >= 5.89.0, >= 5.93.0, >= 5.99.0" + hashes = [ + "h1:Ijt7pOlB7Tr7maGQIqtsLFbl7pSMIj06TVdkoSBcYOw=", + "zh:054b8dd49f0549c9a7cc27d159e45327b7b65cf404da5e5a20da154b90b8a644", + "zh:0b97bf8d5e03d15d83cc40b0530a1f84b459354939ba6f135a0086c20ebbe6b2", + "zh:1589a2266af699cbd5d80737a0fe02e54ec9cf2ca54e7e00ac51c7359056f274", + "zh:6330766f1d85f01ae6ea90d1b214b8b74cc8c1badc4696b165b36ddd4cc15f7b", + "zh:7c8c2e30d8e55291b86fcb64bdf6c25489d538688545eb48fd74ad622e5d3862", + "zh:99b1003bd9bd32ee323544da897148f46a527f622dc3971af63ea3e251596342", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9f8b909d3ec50ade83c8062290378b1ec553edef6a447c56dadc01a99f4eaa93", + "zh:aaef921ff9aabaf8b1869a86d692ebd24fbd4e12c21205034bb679b9caf883a2", + "zh:ac882313207aba00dd5a76dbd572a0ddc818bb9cbf5c9d61b28fe30efaec951e", + "zh:bb64e8aff37becab373a1a0cc1080990785304141af42ed6aa3dd4913b000421", + "zh:dfe495f6621df5540d9c92ad40b8067376350b005c637ea6efac5dc15028add4", + "zh:f0ddf0eaf052766cfe09dea8200a946519f653c384ab4336e2a4a64fdd6310e9", + "zh:f1b7e684f4c7ae1eed272b6de7d2049bb87a0275cb04dbb7cda6636f600699c9", + "zh:ff461571e3f233699bf690db319dfe46aec75e58726636a0d97dd9ac6e32fb70", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.9.1" + constraints = ">= 3.0.0, ~> 3.0" + hashes = [ + "h1:PlW+UZ4EElQF3NQwf41KQwavFujab3Czc51zu9dyVM8=", + "zh:05f4734c1f0be840b711b3eff259ebc5fca436784c728955b1678078466f48d7", + "zh:0b91bf19371d012434eba1deeb6aab77158def9b39601dcbd94450b3974a2a26", + "zh:0ee6eacd47ec00183d55d726a4b6c4ce951a199f944bf22f1aa58392ebdfa7a2", + "zh:19388a4074b76a89a43a6c8328d7ae8ee2e7de3d346af51e80d3e6d3d12925f1", + "zh:23e74d48c5e2ac2e823fd527f49fee9db37d32a1990c9e3bf126ead697b843eb", + "zh:3cabf7fbd096c520064aae3aba61aba670af83ab91291a71fa1b1332929c2b7f", + "zh:5c0a3b8af0be60be4eca12ddee385cfa8babc1ec8e98cdf9de2f2274c73eabfa", + "zh:60b4f8a8ef18f52bf8e19215229dae408bee732825964092db7c989fd2de4097", + "zh:7359015acfedcbd6366f2329c854cf8d3c8ca5cd0faa89d2d37db358d6eba6c5", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b38758402f0e13a1071162da28994023cd2ac676e54af350c9ffd8dfa73fa7b", + "zh:7c7fbb8895eb75bb4de1f933e98553bd99c8d048c89a925ddba490aa5a67f7dc", + "zh:8c2b8c6a7ccdec16b73e2fb9f3700ea097f58c592571e4c5de60c93d2301732c", + ] +} diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 0000000000..6e895b3ac5 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,294 @@ +# ACA Voting App Terraform Infrastructure Documentation + +This project provisions the complete cloud infrastructure to host the `vote`, `result`, and `worker` applications on AWS ECS Fargate, backed by **AWS ElastiCache Valkey** and **AWS Aurora Serverless v2 PostgreSQL**. + +It reuses the shared network infrastructure exported by `../aca-terraform-common`. + +--- + +## 📐 Architecture & Dependency Graph + +```mermaid +graph TD + subgraph SharedInfra["Shared Base Infrastructure (aca-terraform-common)"] + SharedVPC["Shared VPC (10.1.0.0/16)"] + PublicSubnets["Public Subnets"] + PrivateSubnets["Private Subnets"] + Route53Zone["Route 53 Zone (sergey.c-loud.am)"] + end + + subgraph ACAVotingApp["Application Infrastructure (aca-voting-app)"] + ALB["Application Load Balancer (alb.tf)"] + SecretsManager["AWS Secrets Manager (secrets.tf)"] + + subgraph Compute["ECS Fargate Cluster (ecs.tf)"] + VoteTask["Vote Service Task (Port 80)"] + ResultTask["Result Service Task (Port 80)"] + WorkerTask["Worker Service Task (Consumer)"] + end + + subgraph SecurityGroups["Dedicated Security Groups (sg.tf)"] + VoteSG["vote_task SG"] + ResultSG["result_task SG"] + WorkerSG["worker_task SG"] + ValkeySG["valkey SG"] + end + + subgraph BackingServices["Stateful Storage Services"] + ElastiCache["ElastiCache Valkey (valkey.tf)"] + Aurora["Aurora Serverless v2 Postgres (aurora.tf)"] + end + + ECR["Amazon ECR (ecr.tf)"] + DNS["Route 53 Alias Records (route53.tf)"] + end + + SharedVPC --> ALB + SharedVPC --> VoteSG + SharedVPC --> ResultSG + SharedVPC --> WorkerSG + PublicSubnets --> ALB + PrivateSubnets --> VoteTask + PrivateSubnets --> ResultTask + PrivateSubnets --> WorkerTask + PrivateSubnets --> ElastiCache + PrivateSubnets --> Aurora + + ALB -->|Port 80| VoteTask + ALB -->|Port 8081| ResultTask + DNS -->|Alias| ALB + + VoteTask -->|Write Queue| ElastiCache + ResultTask -->|Read Results| Aurora + WorkerTask -->|Pop Queue| ElastiCache + WorkerTask -->|Write Results| Aurora + + VoteSG -->|Ingress| ALB + ResultSG -->|Ingress| ALB + ValkeySG -->|Ingress Port 6379| VoteSG + ValkeySG -->|Ingress Port 6379| WorkerSG + Aurora -->|Ingress Port 5432| ResultSG + Aurora -->|Ingress Port 5432| WorkerSG + + SecretsManager -.->|Inject Credentials| VoteTask + SecretsManager -.->|Inject Credentials| ResultTask + SecretsManager -.->|Inject Credentials| WorkerTask +``` + +--- + +## 📋 Requirements + +| Name | Version | +|------|---------| +| **Terraform** | `>= 1.0.0` | +| **AWS Provider** | `~> 5.0` | +| **Random Provider** | `~> 3.0` | + +--- + +## 📦 Terraform Community Modules + +| Module Source | Name | Version | Purpose | +|---------------|------|---------|---------| +| `terraform-aws-modules/ecr/aws` | `ecr_vote`, `ecr_result`, `ecr_worker` | `~> 2.2` | Amazon ECR container image repositories | +| `terraform-aws-modules/alb/aws` | `alb` | `~> 9.0` | Application Load Balancer with target groups & listeners | +| `terraform-aws-modules/rds-aurora/aws` | `aurora` | `~> 9.0` | AWS Aurora Serverless v2 PostgreSQL cluster | +| `terraform-aws-modules/ecs/aws` | `ecs` | `~> 5.9` | AWS ECS Fargate cluster and task management | + +--- + +## 📥 Inputs / Variables + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| `aws_region` | AWS region for deployment | `string` | `"eu-north-1"` | no | +| `app_name` | Application name identifier | `string` | `"aca-voting-app"` | no | +| `environment` | Deployment environment | `string` | `"production"` | no | +| `common_state_bucket` | S3 bucket storing `aca-terraform-common` state | `string` | `"tfstate-597936860210-eu-north-1-an"` | no | +| `common_state_key` | S3 key for `aca-terraform-common` state | `string` | `"aca-terraform-common/terraform.tfstate"` | no | +| `common_state_region` | AWS region of shared state bucket | `string` | `"eu-north-1"` | no | +| `domain_name` | Base Route 53 domain name | `string` | `"sergey.c-loud.am"` | no | +| `vote_subdomain` | Subdomain for vote web app | `string` | `"vote"` | no | +| `result_subdomain` | Subdomain for result web app | `string` | `"result"` | no | +| `vote_image_tag` | ECR Image tag for vote service | `string` | `"latest"` | no | +| `result_image_tag` | ECR Image tag for result service | `string` | `"latest"` | no | +| `worker_image_tag` | ECR Image tag for worker service | `string` | `"latest"` | no | +| `container_cpu` | Fargate Task CPU units | `number` | `256` | no | +| `container_memory` | Fargate Task memory (MB) | `number` | `512` | no | +| `aurora_min_capacity` | Aurora Serverless v2 Min ACU | `number` | `0.5` | no | +| `aurora_max_capacity` | Aurora Serverless v2 Max ACU | `number` | `1.0` | no | + +--- + +## 📤 Outputs + +| Name | Description | +|------|-------------| +| `shared_vpc_id` | Shared VPC ID imported from `aca-terraform-common` | +| `public_subnet_ids` | Public Subnet IDs used by the ALB | +| `private_subnet_ids` | Private Subnet IDs used by ECS Tasks, Aurora, and Valkey | +| `alb_dns_name` | Public Application Load Balancer DNS name | +| `vote_url` | Direct HTTP access URL for vote app (`http://`) | +| `result_url` | Direct HTTP access URL for result app (`http://:8081`) | +| `vote_domain_url` | Domain URL for vote app (`http://vote.sergey.c-loud.am`) | +| `result_domain_url` | Domain URL for result app (`http://result.sergey.c-loud.am`) | +| `aurora_cluster_endpoint` | PostgreSQL cluster writer endpoint | +| `elasticache_valkey_endpoint` | ElastiCache Valkey cluster endpoint | +| `secrets_manager_valkey_arn` | Secrets Manager ARN for Valkey password | +| `secrets_manager_postgres_arn` | Secrets Manager ARN for PostgreSQL user/password | +| `ecr_repository_vote_url` | ECR repository URL for vote image | +| `ecr_repository_result_url` | ECR repository URL for result image | +| `ecr_repository_worker_url` | ECR repository URL for worker image | +| `ecs_cluster_name` | Name of the ECS Cluster | +| `ecs_service_vote_name` | Name of the vote ECS Service | +| `ecs_service_result_name` | Name of the result ECS Service | +| `ecs_service_worker_name` | Name of the worker ECS Service | + +--- + +## 🏗️ Resources Created + +- **Application Load Balancer**: Multi-target group routing HTTP 80 & 8081. +- **Dedicated Security Groups**: `vote_task`, `result_task`, `worker_task`, `valkey`. +- **AWS Secrets Manager**: Storing auto-generated secure credentials. +- **AWS ElastiCache Valkey**: Multi-AZ capable single-node `cache.t4g.micro` with transit encryption. +- **AWS Aurora Serverless v2 PostgreSQL**: Auto-scaling database cluster (0.5 to 1.0 ACU). +- **Amazon ECS Cluster & Fargate Services**: `vote`, `result`, and `worker` tasks. +- **Route 53 A Alias Records**: Pointing `vote.sergey.c-loud.am` & `result.sergey.c-loud.am` to ALB. + + +## Requirements + +| Name | Version | +| ---- | ------- | +| [terraform](#requirement\_terraform) | >= 1.0.0 | +| [aws](#requirement\_aws) | ~> 5.0 | +| [random](#requirement\_random) | ~> 3.0 | + +## Providers + +| Name | Version | +| ---- | ------- | +| [aws](#provider\_aws) | 5.100.0 | +| [random](#provider\_random) | 3.9.1 | + +## Modules + +| Name | Source | Version | +| ---- | ------ | ------- | +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 | +| [aurora](#module\_aurora) | terraform-aws-modules/rds-aurora/aws | ~> 9.0 | +| [ecr\_result](#module\_ecr\_result) | terraform-aws-modules/ecr/aws | ~> 2.2 | +| [ecr\_vote](#module\_ecr\_vote) | terraform-aws-modules/ecr/aws | ~> 2.2 | +| [ecr\_worker](#module\_ecr\_worker) | terraform-aws-modules/ecr/aws | ~> 2.2 | +| [ecs](#module\_ecs) | terraform-aws-modules/ecs/aws | ~> 5.9 | +| [secrets\_manager\_postgres](#module\_secrets\_manager\_postgres) | terraform-aws-modules/secrets-manager/aws | ~> 1.1 | +| [secrets\_manager\_valkey](#module\_secrets\_manager\_valkey) | terraform-aws-modules/secrets-manager/aws | ~> 1.1 | +| [valkey](#module\_valkey) | terraform-aws-modules/elasticache/aws | ~> 1.3 | + +## Resources + +| Name | Type | +| ---- | ---- | +| [aws_acm_certificate.cert](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate) | resource | +| [aws_acm_certificate_validation.cert](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/acm_certificate_validation) | resource | +| [aws_appautoscaling_policy.result_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.result_cpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.vote_alb](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.vote_cpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_policy.worker_cpu](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_target.result](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_appautoscaling_target.vote](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_appautoscaling_target.worker](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_cloudwatch_dashboard.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_dashboard) | resource | +| [aws_cloudwatch_log_group.result](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cloudwatch_log_group.valkey](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cloudwatch_log_group.vote](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_cloudwatch_log_group.worker](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_service.result](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_service.vote](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_service.worker](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.result](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ecs_task_definition.vote](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ecs_task_definition.worker](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_elasticache_user.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_user) | resource | +| [aws_elasticache_user.valkey_user](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_user) | resource | +| [aws_elasticache_user_group.valkey](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticache_user_group) | resource | +| [aws_iam_policy.ecs_secrets_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.ecs_execution_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.ecs_task_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.ecs_execution_role_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.ecs_execution_secrets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_route53_record.cert_validation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | +| [aws_route53_record.result](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | +| [aws_route53_record.vote](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route53_record) | resource | +| [aws_security_group.result_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.valkey](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.vote_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group.worker_task](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [random_password.postgres_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [random_password.valkey_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [aws_ssm_parameter.private_subnet_ids](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | +| [aws_ssm_parameter.public_subnet_ids](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | +| [aws_ssm_parameter.route53_zone_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | +| [aws_ssm_parameter.route53_zone_name](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | +| [aws_ssm_parameter.vpc_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +| ---- | ----------- | ---- | ------- | :------: | +| [app\_name](#input\_app\_name) | Application name identifier | `string` | `"aca-voting-app"` | no | +| [aurora\_db\_name](#input\_aurora\_db\_name) | Database name for Aurora Serverless PostgreSQL | `string` | `"postgres"` | no | +| [aurora\_master\_password](#input\_aurora\_master\_password) | Master password for Aurora Serverless PostgreSQL | `string` | `"postgrespassword"` | no | +| [aurora\_master\_username](#input\_aurora\_master\_username) | Master username for Aurora Serverless PostgreSQL | `string` | `"postgres"` | no | +| [aurora\_max\_capacity](#input\_aurora\_max\_capacity) | Maximum ACU for Aurora Serverless v2 (cost-optimized maximum) | `number` | `1` | no | +| [aurora\_min\_capacity](#input\_aurora\_min\_capacity) | Minimum ACU for Aurora Serverless v2 (cost-optimized minimum) | `number` | `0.5` | no | +| [aws\_region](#input\_aws\_region) | AWS region for resources (matches aca-terraform-common) | `string` | `"eu-north-1"` | no | +| [common\_environment](#input\_common\_environment) | Environment identifier of common base infrastructure (matches aca-terraform-common environment) | `string` | `"core"` | no | +| [container\_cpu](#input\_container\_cpu) | CPU unit allocation for each ECS container task | `number` | `256` | no | +| [container\_memory](#input\_container\_memory) | Memory allocation (MB) for each ECS container task | `number` | `512` | no | +| [domain\_name](#input\_domain\_name) | Domain name for Route 53 zone hosted in aca-terraform-common | `string` | `"sergey.c-loud.am"` | no | +| [ecs\_alb\_request\_target\_value](#input\_ecs\_alb\_request\_target\_value) | Target ALB request count per target for scaling | `number` | `1000` | no | +| [ecs\_cpu\_target\_value](#input\_ecs\_cpu\_target\_value) | Target average CPU utilization percentage for scaling | `number` | `70` | no | +| [ecs\_max\_capacity](#input\_ecs\_max\_capacity) | Maximum number of tasks to scale up to | `number` | `2` | no | +| [environment](#input\_environment) | Environment name (e.g. production, staging, dev) | `string` | `"production"` | no | +| [result\_desired\_count](#input\_result\_desired\_count) | Number of desired instances for the result service | `number` | `1` | no | +| [result\_image\_tag](#input\_result\_image\_tag) | Image tag to deploy for the result service | `string` | `"latest"` | no | +| [result\_subdomain](#input\_result\_subdomain) | Subdomain prefix for result web app | `string` | `"result"` | no | +| [valkey\_password](#input\_valkey\_password) | Password for Valkey in-memory data store | `string` | `"valkeypassword"` | no | +| [valkey\_username](#input\_valkey\_username) | Username for Valkey RBAC authentication | `string` | `"valkeyuser"` | no | +| [vote\_desired\_count](#input\_vote\_desired\_count) | Number of desired instances for the vote service | `number` | `1` | no | +| [vote\_image\_tag](#input\_vote\_image\_tag) | Image tag to deploy for the vote service | `string` | `"latest"` | no | +| [vote\_subdomain](#input\_vote\_subdomain) | Subdomain prefix for vote web app | `string` | `"vote"` | no | +| [worker\_desired\_count](#input\_worker\_desired\_count) | Number of desired instances for the worker service | `number` | `1` | no | +| [worker\_image\_tag](#input\_worker\_image\_tag) | Image tag to deploy for the worker service | `string` | `"latest"` | no | + +## Outputs + +| Name | Description | +| ---- | ----------- | +| [acm\_certificate\_arn](#output\_acm\_certificate\_arn) | ARN of the validated ACM Certificate | +| [alb\_dns\_name](#output\_alb\_dns\_name) | DNS name of the Application Load Balancer | +| [aurora\_cluster\_endpoint](#output\_aurora\_cluster\_endpoint) | Aurora Serverless v2 PostgreSQL cluster writer endpoint | +| [cloudwatch\_dashboard\_name](#output\_cloudwatch\_dashboard\_name) | Name of the CloudWatch Custom Dashboard | +| [cloudwatch\_dashboard\_url](#output\_cloudwatch\_dashboard\_url) | Direct AWS Console link to the CloudWatch Custom Dashboard | +| [ecr\_repository\_result\_url](#output\_ecr\_repository\_result\_url) | URL of the ECR repository for the result service | +| [ecr\_repository\_vote\_url](#output\_ecr\_repository\_vote\_url) | URL of the ECR repository for the vote service | +| [ecr\_repository\_worker\_url](#output\_ecr\_repository\_worker\_url) | URL of the ECR repository for the worker service | +| [ecs\_cluster\_name](#output\_ecs\_cluster\_name) | Name of the ECS Cluster | +| [ecs\_service\_result\_name](#output\_ecs\_service\_result\_name) | Name of the result ECS Service | +| [ecs\_service\_vote\_name](#output\_ecs\_service\_vote\_name) | Name of the vote ECS Service | +| [ecs\_service\_worker\_name](#output\_ecs\_service\_worker\_name) | Name of the worker ECS Service | +| [elasticache\_valkey\_endpoint](#output\_elasticache\_valkey\_endpoint) | Primary endpoint for AWS ElastiCache Valkey | +| [private\_subnet\_ids](#output\_private\_subnet\_ids) | Private subnet IDs used by ECS Tasks, Aurora, and ElastiCache | +| [public\_subnet\_ids](#output\_public\_subnet\_ids) | Public subnet IDs used by ALB | +| [result\_domain\_url](#output\_result\_domain\_url) | Secure HTTPS domain URL for result app | +| [result\_url](#output\_result\_url) | HTTPS URL to access result app directly on port 8443 | +| [secrets\_manager\_postgres\_arn](#output\_secrets\_manager\_postgres\_arn) | Secrets Manager ARN storing PostgreSQL credentials | +| [secrets\_manager\_valkey\_arn](#output\_secrets\_manager\_valkey\_arn) | Secrets Manager ARN storing Valkey credentials | +| [shared\_vpc\_id](#output\_shared\_vpc\_id) | The ID of the shared VPC reused from aca-terraform-common | +| [vote\_domain\_url](#output\_vote\_domain\_url) | Secure HTTPS domain URL for vote app | +| [vote\_url](#output\_vote\_url) | HTTP redirect URL for vote web app | + \ No newline at end of file diff --git a/terraform/acm.tf b/terraform/acm.tf new file mode 100644 index 0000000000..19d37d760b --- /dev/null +++ b/terraform/acm.tf @@ -0,0 +1,41 @@ +# ACM Certificate for HTTPS on ALB +resource "aws_acm_certificate" "cert" { + domain_name = var.domain_name + validation_method = "DNS" + + subject_alternative_names = [ + "*.${var.domain_name}" + ] + + lifecycle { + create_before_destroy = true + } + + tags = { + Name = "${var.app_name}-acm-cert" + } +} + +# Route 53 DNS records for ACM Certificate Validation +resource "aws_route53_record" "cert_validation" { + for_each = { + for dvo in aws_acm_certificate.cert.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + record = dvo.resource_record_value + type = dvo.resource_record_type + } + } + + allow_overwrite = true + name = each.value.name + records = [each.value.record] + ttl = 60 + type = each.value.type + zone_id = local.route53_zone_id +} + +# ACM Certificate Validation +resource "aws_acm_certificate_validation" "cert" { + certificate_arn = aws_acm_certificate.cert.arn + validation_record_fqdns = [for record in aws_route53_record.cert_validation : record.fqdn] +} diff --git a/terraform/alb.tf b/terraform/alb.tf new file mode 100644 index 0000000000..b12278ae43 --- /dev/null +++ b/terraform/alb.tf @@ -0,0 +1,137 @@ +# Application Load Balancer using AWS Community Module +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 9.0" + + name = "${var.app_name}-alb" + vpc_id = local.vpc_id + subnets = local.public_subnet_ids + enable_deletion_protection = false + + # Security Group rules + security_group_ingress_rules = { + http_80 = { + from_port = 80 + to_port = 80 + ip_protocol = "tcp" + description = "HTTP traffic for redirect to HTTPS" + cidr_ipv4 = "0.0.0.0/0" + } + https_443 = { + from_port = 80 + to_port = 443 + ip_protocol = "tcp" + description = "HTTPS secure web traffic for Vote and Result apps" + cidr_ipv4 = "0.0.0.0/0" + } + https_8443 = { + from_port = 8443 + to_port = 8443 + ip_protocol = "tcp" + description = "HTTPS secondary port for Result app" + cidr_ipv4 = "0.0.0.0/0" + } + } + + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" + } + } + + # Listeners configuration + listeners = { + # 1. Port 80 HTTP -> Redirect to HTTPS 443 + http = { + port = 80 + protocol = "HTTP" + redirect = { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } + + # 2. Port 443 HTTPS Listener with Host-based Routing + https = { + port = 443 + protocol = "HTTPS" + certificate_arn = aws_acm_certificate_validation.cert.certificate_arn + forward = { + target_group_key = "vote" + } + rules = { + vote_host = { + actions = [{ + type = "forward" + target_group_key = "vote" + }] + conditions = [{ + host_header = { + values = ["${var.vote_subdomain}.${var.domain_name}"] + } + }] + } + result_host = { + actions = [{ + type = "forward" + target_group_key = "result" + }] + conditions = [{ + host_header = { + values = ["${var.result_subdomain}.${var.domain_name}"] + } + }] + } + } + } + + # 3. Port 8443 HTTPS Listener directly for Result App + result_https = { + port = 8443 + protocol = "HTTPS" + certificate_arn = aws_acm_certificate_validation.cert.certificate_arn + forward = { + target_group_key = "result" + } + } + } + + target_groups = { + vote = { + name_prefix = "vote-" + protocol = "HTTP" + port = 80 + target_type = "ip" + create_attachment = false + health_check = { + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 5 + interval = 15 + path = "/" + matcher = "200-399" + } + } + result = { + name_prefix = "rslt-" + protocol = "HTTP" + port = 80 + target_type = "ip" + create_attachment = false + health_check = { + healthy_threshold = 3 + unhealthy_threshold = 3 + timeout = 5 + interval = 15 + path = "/" + matcher = "200-399" + } + } + } + + tags = { + Name = "${var.app_name}-alb" + } +} diff --git a/terraform/aurora.tf b/terraform/aurora.tf new file mode 100644 index 0000000000..5bf8e6ede4 --- /dev/null +++ b/terraform/aurora.tf @@ -0,0 +1,57 @@ +# Aurora Serverless v2 PostgreSQL cluster using AWS Community Module +module "aurora" { + source = "terraform-aws-modules/rds-aurora/aws" + version = "~> 9.0" + + name = "${var.app_name}-aurora" + engine = "aurora-postgresql" + engine_version = "15.14" + master_username = var.aurora_master_username + master_password = random_password.postgres_password.result + database_name = var.aurora_db_name + + vpc_id = local.vpc_id + subnets = local.private_subnet_ids + + # Deletion protection explicitly disabled for easy tear down + deletion_protection = false + skip_final_snapshot = true + + # Minimum resources: Aurora Serverless v2 scaling configuration + serverlessv2_scaling_configuration = { + min_capacity = var.aurora_min_capacity # 0.5 ACU + max_capacity = var.aurora_max_capacity # 1.0 ACU + } + + instance_class = "db.serverless" + instances = { + 1 = {} + } + + manage_master_user_password = false + + security_group_rules = { + result_ingress = { + description = "Allow PostgreSQL traffic from Result task" + type = "ingress" + from_port = 5432 + to_port = 5432 + protocol = "tcp" + source_security_group_id = aws_security_group.result_task.id + } + worker_ingress = { + description = "Allow PostgreSQL traffic from Worker task" + type = "ingress" + from_port = 5432 + to_port = 5432 + protocol = "tcp" + source_security_group_id = aws_security_group.worker_task.id + } + } + + create_db_subnet_group = true + + tags = { + Name = "${var.app_name}-aurora" + } +} diff --git a/terraform/autoscaling.tf b/terraform/autoscaling.tf new file mode 100644 index 0000000000..924cabf16a --- /dev/null +++ b/terraform/autoscaling.tf @@ -0,0 +1,133 @@ +# ========================================== +# ECS SERVICE DYNAMIC AUTO SCALING (1 to 2 Tasks) +# ========================================== + +# ------------------------------------------ +# 1. VOTE SERVICE AUTO SCALING +# ------------------------------------------ +resource "aws_appautoscaling_target" "vote" { + max_capacity = var.ecs_max_capacity # 2 + min_capacity = var.vote_desired_count # 1 + resource_id = "service/${module.ecs.cluster_name}/${aws_ecs_service.vote.name}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +# Vote Service Target Tracking Scaling Policy: CPU Utilization +resource "aws_appautoscaling_policy" "vote_cpu" { + name = "${var.app_name}-vote-cpu-scaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.vote.resource_id + scalable_dimension = aws_appautoscaling_target.vote.scalable_dimension + service_namespace = aws_appautoscaling_target.vote.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + + target_value = var.ecs_cpu_target_value # 70.0% + scale_in_cooldown = 300 + scale_out_cooldown = 60 + } +} + +# Vote Service Target Tracking Scaling Policy: ALB Request Count Per Target +resource "aws_appautoscaling_policy" "vote_alb" { + name = "${var.app_name}-vote-alb-scaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.vote.resource_id + scalable_dimension = aws_appautoscaling_target.vote.scalable_dimension + service_namespace = aws_appautoscaling_target.vote.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ALBRequestCountPerTarget" + resource_label = "${module.alb.arn_suffix}/${module.alb.target_groups["vote"].arn_suffix}" + } + + target_value = var.ecs_alb_request_target_value # 1000.0 requests + scale_in_cooldown = 300 + scale_out_cooldown = 60 + } +} + +# ------------------------------------------ +# 2. RESULT SERVICE AUTO SCALING +# ------------------------------------------ +resource "aws_appautoscaling_target" "result" { + max_capacity = var.ecs_max_capacity # 2 + min_capacity = var.result_desired_count # 1 + resource_id = "service/${module.ecs.cluster_name}/${aws_ecs_service.result.name}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +# Result Service Target Tracking Scaling Policy: CPU Utilization +resource "aws_appautoscaling_policy" "result_cpu" { + name = "${var.app_name}-result-cpu-scaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.result.resource_id + scalable_dimension = aws_appautoscaling_target.result.scalable_dimension + service_namespace = aws_appautoscaling_target.result.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + + target_value = var.ecs_cpu_target_value # 70.0% + scale_in_cooldown = 300 + scale_out_cooldown = 60 + } +} + +# Result Service Target Tracking Scaling Policy: ALB Request Count Per Target +resource "aws_appautoscaling_policy" "result_alb" { + name = "${var.app_name}-result-alb-scaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.result.resource_id + scalable_dimension = aws_appautoscaling_target.result.scalable_dimension + service_namespace = aws_appautoscaling_target.result.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ALBRequestCountPerTarget" + resource_label = "${module.alb.arn_suffix}/${module.alb.target_groups["result"].arn_suffix}" + } + + target_value = var.ecs_alb_request_target_value # 1000.0 requests + scale_in_cooldown = 300 + scale_out_cooldown = 60 + } +} + +# ------------------------------------------ +# 3. WORKER SERVICE AUTO SCALING +# ------------------------------------------ +resource "aws_appautoscaling_target" "worker" { + max_capacity = var.ecs_max_capacity # 2 + min_capacity = var.worker_desired_count # 1 + resource_id = "service/${module.ecs.cluster_name}/${aws_ecs_service.worker.name}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +# Worker Service Target Tracking Scaling Policy: CPU Utilization +resource "aws_appautoscaling_policy" "worker_cpu" { + name = "${var.app_name}-worker-cpu-scaling" + policy_type = "TargetTrackingScaling" + resource_id = aws_appautoscaling_target.worker.resource_id + scalable_dimension = aws_appautoscaling_target.worker.scalable_dimension + service_namespace = aws_appautoscaling_target.worker.service_namespace + + target_tracking_scaling_policy_configuration { + predefined_metric_specification { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + + target_value = var.ecs_cpu_target_value # 70.0% + scale_in_cooldown = 300 + scale_out_cooldown = 60 + } +} diff --git a/terraform/backend.tf b/terraform/backend.tf new file mode 100644 index 0000000000..1c9bc12f20 --- /dev/null +++ b/terraform/backend.tf @@ -0,0 +1,9 @@ +terraform { + backend "s3" { + bucket = "tfstate-597936860210-eu-north-1-an" + key = "aca-voting-app/terraform.tfstate" + region = "eu-north-1" + encrypt = true + use_lockfile = true + } +} diff --git a/terraform/dashboard.tf b/terraform/dashboard.tf new file mode 100644 index 0000000000..194aadf97a --- /dev/null +++ b/terraform/dashboard.tf @@ -0,0 +1,157 @@ +# Custom AWS CloudWatch Dashboard for System Metrics & Connections +resource "aws_cloudwatch_dashboard" "main" { + dashboard_name = "${var.app_name}-dashboard" + + dashboard_body = jsonencode({ + widgets = [ + # Row 1: ALB Traffic & Response Times + { + type = "metric" + x = 0 + y = 0 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/ApplicationELB", "RequestCount", "LoadBalancer", module.alb.arn_suffix, { period = 60, stat = "Sum", label = "Total Requests" }], + [".", "HTTPCode_Target_2XX_Count", ".", ".", { period = 60, stat = "Sum", label = "2XX Success" }], + [".", "HTTPCode_Target_4XX_Count", ".", ".", { period = 60, stat = "Sum", label = "4XX Client Errors" }], + [".", "HTTPCode_Target_5XX_Count", ".", ".", { period = 60, stat = "Sum", label = "5XX Server Errors" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "🌐 Application Load Balancer Traffic & HTTP Status Codes" + } + }, + { + type = "metric" + x = 12 + y = 0 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/ApplicationELB", "TargetResponseTime", "LoadBalancer", module.alb.arn_suffix, { period = 60, stat = "Average", label = "Avg Response Time (s)" }], + [".", "TargetResponseTime", ".", ".", { period = 60, stat = "p95", label = "p95 Response Time (s)" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "⚡ ALB Target Response Times (Latency)" + } + }, + + # Row 2: Database Connection Requests & Capacity (Aurora & Valkey) + { + type = "metric" + x = 0 + y = 6 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/RDS", "DatabaseConnections", "DBClusterIdentifier", module.aurora.cluster_id, { period = 60, stat = "Average", label = "PostgreSQL Active Connections" }], + ["AWS/ElastiCache", "CurrConnections", "CacheClusterId", "${var.app_name}-valkey-001", { period = 60, stat = "Average", label = "Valkey Active Client Connections" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "🔌 Database & Cache Active Connection Requests" + } + }, + { + type = "metric" + x = 12 + y = 6 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/RDS", "ServerlessDatabaseCapacity", "DBClusterIdentifier", module.aurora.cluster_id, { period = 60, stat = "Average", label = "Aurora ACUs Capacity" }], + ["AWS/RDS", "CPUUtilization", "DBClusterIdentifier", module.aurora.cluster_id, { period = 60, stat = "Average", label = "Aurora CPU %" }], + ["AWS/ElastiCache", "EngineCPUUtilization", "CacheClusterId", "${var.app_name}-valkey-001", { period = 60, stat = "Average", label = "Valkey Engine CPU %" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "📉 Aurora Serverless v2 Capacity & DB Engine CPU" + } + }, + + # Row 3: ECS Fargate Services CPU & Memory Utilization + { + type = "metric" + x = 0 + y = 12 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/ECS", "CPUUtilization", "ClusterName", module.ecs.cluster_name, "ServiceName", aws_ecs_service.vote.name, { period = 60, stat = "Average", label = "Vote Service CPU %" }], + [".", "CPUUtilization", ".", ".", "ServiceName", aws_ecs_service.result.name, { period = 60, stat = "Average", label = "Result Service CPU %" }], + [".", "CPUUtilization", ".", ".", "ServiceName", aws_ecs_service.worker.name, { period = 60, stat = "Average", label = "Worker Service CPU %" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "💻 ECS Fargate Tasks CPU Utilization" + } + }, + { + type = "metric" + x = 12 + y = 12 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/ECS", "MemoryUtilization", "ClusterName", module.ecs.cluster_name, "ServiceName", aws_ecs_service.vote.name, { period = 60, stat = "Average", label = "Vote Service Memory %" }], + [".", "MemoryUtilization", ".", ".", "ServiceName", aws_ecs_service.result.name, { period = 60, stat = "Average", label = "Result Service Memory %" }], + [".", "MemoryUtilization", ".", ".", "ServiceName", aws_ecs_service.worker.name, { period = 60, stat = "Average", label = "Worker Service Memory %" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "🧠 ECS Fargate Tasks Memory Utilization" + } + }, + + # Row 4: Valkey Cache Hit Ratio & Storage Throughput + { + type = "metric" + x = 0 + y = 18 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/ElastiCache", "CacheHits", "CacheClusterId", "${var.app_name}-valkey-001", { period = 60, stat = "Sum", label = "Valkey Cache Hits" }], + [".", "CacheMisses", ".", ".", { period = 60, stat = "Sum", label = "Valkey Cache Misses" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "🚀 Valkey Cache Hits vs Misses" + } + }, + { + type = "metric" + x = 12 + y = 18 + width = 12 + height = 6 + properties = { + metrics = [ + ["AWS/RDS", "ReadThroughput", "DBClusterIdentifier", module.aurora.cluster_id, { period = 60, stat = "Average", label = "Aurora Read Throughput (B/s)" }], + [".", "WriteThroughput", ".", ".", { period = 60, stat = "Average", label = "Aurora Write Throughput (B/s)" }] + ] + view = "timeSeries" + stacked = false + region = var.aws_region + title = "💾 PostgreSQL Aurora Storage I/O Throughput" + } + } + ] + }) +} diff --git a/terraform/data.tf b/terraform/data.tf new file mode 100644 index 0000000000..bdc5bad50c --- /dev/null +++ b/terraform/data.tf @@ -0,0 +1,37 @@ +# ========================================== +# UNIFIED DATA SOURCING VIA SSM PARAMETER STORE +# ========================================== + +# VPC ID +data "aws_ssm_parameter" "vpc_id" { + name = "/${var.common_environment}/vpc/id" +} + +# Public Subnet IDs List +data "aws_ssm_parameter" "public_subnet_ids" { + name = "/${var.common_environment}/subnets/public_ids" +} + +# Private Subnet IDs List +data "aws_ssm_parameter" "private_subnet_ids" { + name = "/${var.common_environment}/subnets/private_ids" +} + +# Route 53 Zone ID +data "aws_ssm_parameter" "route53_zone_id" { + name = "/${var.common_environment}/route53/zone_id" +} + +# Route 53 Zone Name +data "aws_ssm_parameter" "route53_zone_name" { + name = "/${var.common_environment}/route53/zone_name" +} + +# Local helpers for unified, typed data sourcing across all files +locals { + vpc_id = data.aws_ssm_parameter.vpc_id.value + public_subnet_ids = split(",", data.aws_ssm_parameter.public_subnet_ids.value) + private_subnet_ids = split(",", data.aws_ssm_parameter.private_subnet_ids.value) + route53_zone_id = data.aws_ssm_parameter.route53_zone_id.value + route53_zone_name = data.aws_ssm_parameter.route53_zone_name.value +} diff --git a/terraform/ecr.tf b/terraform/ecr.tf new file mode 100644 index 0000000000..05891d5574 --- /dev/null +++ b/terraform/ecr.tf @@ -0,0 +1,95 @@ +# ECR Repository for Vote service using AWS Community Module +module "ecr_vote" { + source = "terraform-aws-modules/ecr/aws" + version = "~> 2.2" + + repository_name = "${var.app_name}-vote" + repository_image_tag_mutability = "MUTABLE" + repository_force_delete = true + + create_lifecycle_policy = true + repository_lifecycle_policy = jsonencode({ + rules = [ + { + rulePriority = 1 + description = "Keep last 5 images for cost optimization" + selection = { + tagStatus = "any" + countType = "imageCountMoreThan" + countNumber = 5 + } + action = { + type = "expire" + } + } + ] + }) + + tags = { + Name = "${var.app_name}-vote" + } +} + +# ECR Repository for Result service using AWS Community Module +module "ecr_result" { + source = "terraform-aws-modules/ecr/aws" + version = "~> 2.2" + + repository_name = "${var.app_name}-result" + repository_image_tag_mutability = "MUTABLE" + repository_force_delete = true + + create_lifecycle_policy = true + repository_lifecycle_policy = jsonencode({ + rules = [ + { + rulePriority = 1 + description = "Keep last 5 images for cost optimization" + selection = { + tagStatus = "any" + countType = "imageCountMoreThan" + countNumber = 5 + } + action = { + type = "expire" + } + } + ] + }) + + tags = { + Name = "${var.app_name}-result" + } +} + +# ECR Repository for Worker service using AWS Community Module +module "ecr_worker" { + source = "terraform-aws-modules/ecr/aws" + version = "~> 2.2" + + repository_name = "${var.app_name}-worker" + repository_image_tag_mutability = "MUTABLE" + repository_force_delete = true + + create_lifecycle_policy = true + repository_lifecycle_policy = jsonencode({ + rules = [ + { + rulePriority = 1 + description = "Keep last 5 images for cost optimization" + selection = { + tagStatus = "any" + countType = "imageCountMoreThan" + countNumber = 5 + } + action = { + type = "expire" + } + } + ] + }) + + tags = { + Name = "${var.app_name}-worker" + } +} diff --git a/terraform/ecs.tf b/terraform/ecs.tf new file mode 100644 index 0000000000..9f043dbabb --- /dev/null +++ b/terraform/ecs.tf @@ -0,0 +1,247 @@ +# ECS Cluster using AWS Community Module +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + version = "~> 5.9" + + cluster_name = "${var.app_name}-cluster" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + } + } + + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 100 + } + } + } + + tags = { + Name = "${var.app_name}-cluster" + } +} + +# ========================================== +# 1. VOTE SERVICE +# ========================================== +resource "aws_ecs_task_definition" "vote" { + family = "${var.app_name}-vote" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.container_cpu + memory = var.container_memory + execution_role_arn = aws_iam_role.ecs_execution_role.arn + task_role_arn = aws_iam_role.ecs_task_role.arn + + container_definitions = jsonencode([ + { + name = "vote" + image = "${module.ecr_vote.repository_url}:${var.vote_image_tag}" + essential = true + environment = [ + { name = "REDIS_HOST", value = module.valkey.replication_group_primary_endpoint_address }, + { name = "REDIS_PORT", value = "6379" }, + { name = "REDIS_SSL", value = "true" }, + { name = "OPTION_A", value = "Cats" }, + { name = "OPTION_B", value = "Dogs" } + ] + secrets = [ + { + name = "REDIS_USERNAME" + valueFrom = "${module.secrets_manager_valkey.secret_arn}:username::" + }, + { + name = "REDIS_PASSWORD" + valueFrom = "${module.secrets_manager_valkey.secret_arn}:password::" + } + ] + portMappings = [ + { + containerPort = 80 + hostPort = 80 + protocol = "tcp" + } + ] + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.vote.name + "awslogs-region" = var.aws_region + "awslogs-stream-prefix" = "vote" + } + } + } + ]) +} + +resource "aws_ecs_service" "vote" { + name = "vote" + cluster = module.ecs.cluster_id + task_definition = aws_ecs_task_definition.vote.arn + desired_count = var.vote_desired_count + launch_type = "FARGATE" + + network_configuration { + security_groups = [aws_security_group.vote_task.id] + subnets = local.private_subnet_ids + assign_public_ip = false + } + + load_balancer { + target_group_arn = module.alb.target_groups["vote"].arn + container_name = "vote" + container_port = 80 + } + + depends_on = [module.valkey] +} + +# ========================================== +# 2. RESULT SERVICE +# ========================================== +resource "aws_ecs_task_definition" "result" { + family = "${var.app_name}-result" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.container_cpu + memory = var.container_memory + execution_role_arn = aws_iam_role.ecs_execution_role.arn + task_role_arn = aws_iam_role.ecs_task_role.arn + + container_definitions = jsonencode([ + { + name = "result" + image = "${module.ecr_result.repository_url}:${var.result_image_tag}" + essential = true + environment = [ + { name = "POSTGRES_HOST", value = module.aurora.cluster_endpoint }, + { name = "POSTGRES_PORT", value = "5432" }, + { name = "POSTGRES_DB", value = var.aurora_db_name }, + { name = "POSTGRES_SSL", value = "true" }, + { name = "PORT", value = "80" } + ] + secrets = [ + { + name = "POSTGRES_USER" + valueFrom = "${module.secrets_manager_postgres.secret_arn}:username::" + }, + { + name = "POSTGRES_PASSWORD" + valueFrom = "${module.secrets_manager_postgres.secret_arn}:password::" + } + ] + portMappings = [ + { + containerPort = 80 + hostPort = 80 + protocol = "tcp" + } + ] + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.result.name + "awslogs-region" = var.aws_region + "awslogs-stream-prefix" = "result" + } + } + } + ]) +} + +resource "aws_ecs_service" "result" { + name = "result" + cluster = module.ecs.cluster_id + task_definition = aws_ecs_task_definition.result.arn + desired_count = var.result_desired_count + launch_type = "FARGATE" + + network_configuration { + security_groups = [aws_security_group.result_task.id] + subnets = local.private_subnet_ids + assign_public_ip = false + } + + load_balancer { + target_group_arn = module.alb.target_groups["result"].arn + container_name = "result" + container_port = 80 + } + + depends_on = [module.aurora] +} + +# ========================================== +# 3. WORKER SERVICE +# ========================================== +resource "aws_ecs_task_definition" "worker" { + family = "${var.app_name}-worker" + network_mode = "awsvpc" + requires_compatibilities = ["FARGATE"] + cpu = var.container_cpu + memory = var.container_memory + execution_role_arn = aws_iam_role.ecs_execution_role.arn + task_role_arn = aws_iam_role.ecs_task_role.arn + + container_definitions = jsonencode([ + { + name = "worker" + image = "${module.ecr_worker.repository_url}:${var.worker_image_tag}" + essential = true + environment = [ + { name = "REDIS_HOST", value = module.valkey.replication_group_primary_endpoint_address }, + { name = "REDIS_PORT", value = "6379" }, + { name = "REDIS_SSL", value = "true" }, + { name = "POSTGRES_HOST", value = module.aurora.cluster_endpoint }, + { name = "POSTGRES_PORT", value = "5432" }, + { name = "POSTGRES_DB", value = var.aurora_db_name }, + { name = "POSTGRES_SSL", value = "true" } + ] + secrets = [ + { + name = "REDIS_USERNAME" + valueFrom = "${module.secrets_manager_valkey.secret_arn}:username::" + }, + { + name = "REDIS_PASSWORD" + valueFrom = "${module.secrets_manager_valkey.secret_arn}:password::" + }, + { + name = "POSTGRES_USER" + valueFrom = "${module.secrets_manager_postgres.secret_arn}:username::" + }, + { + name = "POSTGRES_PASSWORD" + valueFrom = "${module.secrets_manager_postgres.secret_arn}:password::" + } + ] + logConfiguration = { + logDriver = "awslogs" + options = { + "awslogs-group" = aws_cloudwatch_log_group.worker.name + "awslogs-region" = var.aws_region + "awslogs-stream-prefix" = "worker" + } + } + } + ]) +} + +resource "aws_ecs_service" "worker" { + name = "worker" + cluster = module.ecs.cluster_id + task_definition = aws_ecs_task_definition.worker.arn + desired_count = var.worker_desired_count + launch_type = "FARGATE" + + network_configuration { + security_groups = [aws_security_group.worker_task.id] + subnets = local.private_subnet_ids + assign_public_ip = false + } + + depends_on = [module.valkey, module.aurora] +} diff --git a/terraform/errored.tfstate b/terraform/errored.tfstate new file mode 100644 index 0000000000..73ef856ca5 --- /dev/null +++ b/terraform/errored.tfstate @@ -0,0 +1,4764 @@ +{ + "version": 4, + "terraform_version": "1.15.8", + "serial": 22, + "lineage": "e2df8f77-8841-468e-62ed-89d220a990d8", + "outputs": { + "acm_certificate_arn": { + "value": "arn:aws:acm:eu-north-1:597936860210:certificate/9e020264-788b-43da-ba79-5225851bbf1c", + "type": "string" + }, + "alb_dns_name": { + "value": "aca-voting-app-alb-2040942990.eu-north-1.elb.amazonaws.com", + "type": "string" + }, + "aurora_cluster_endpoint": { + "value": "aca-voting-app-aurora.cluster-c5is6oakwkpa.eu-north-1.rds.amazonaws.com", + "type": "string" + }, + "cloudwatch_dashboard_name": { + "value": "aca-voting-app-dashboard", + "type": "string" + }, + "cloudwatch_dashboard_url": { + "value": "https://eu-north-1.console.aws.amazon.com/cloudwatch/home?region=eu-north-1#dashboards:name=aca-voting-app-dashboard", + "type": "string" + }, + "ecr_repository_result_url": { + "value": "597936860210.dkr.ecr.eu-north-1.amazonaws.com/aca-voting-app-result", + "type": "string" + }, + "ecr_repository_vote_url": { + "value": "597936860210.dkr.ecr.eu-north-1.amazonaws.com/aca-voting-app-vote", + "type": "string" + }, + "ecr_repository_worker_url": { + "value": "597936860210.dkr.ecr.eu-north-1.amazonaws.com/aca-voting-app-worker", + "type": "string" + }, + "ecs_cluster_name": { + "value": "aca-voting-app-cluster", + "type": "string" + }, + "ecs_service_result_name": { + "value": "result", + "type": "string" + }, + "ecs_service_vote_name": { + "value": "vote", + "type": "string" + }, + "ecs_service_worker_name": { + "value": "worker", + "type": "string" + }, + "private_subnet_ids": { + "value": [ + "subnet-0caa8bd66179f08a2", + "subnet-098835d1205ea3fd4" + ], + "type": [ + "list", + "string" + ], + "sensitive": true + }, + "public_subnet_ids": { + "value": [ + "subnet-001e0b414a6336eac", + "subnet-0c5e252dcafaf9786" + ], + "type": [ + "list", + "string" + ], + "sensitive": true + }, + "result_domain_url": { + "value": "https://result.sergey.c-loud.am", + "type": "string" + }, + "result_url": { + "value": "https://aca-voting-app-alb-2040942990.eu-north-1.elb.amazonaws.com:8443", + "type": "string" + }, + "secrets_manager_postgres_arn": { + "value": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh", + "type": "string" + }, + "secrets_manager_valkey_arn": { + "value": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-valkey-credentials-VdZ3KO", + "type": "string" + }, + "shared_vpc_id": { + "value": "vpc-0b0fce02ab20d23a4", + "type": "string", + "sensitive": true + }, + "vote_domain_url": { + "value": "https://vote.sergey.c-loud.am", + "type": "string" + }, + "vote_url": { + "value": "http://aca-voting-app-alb-2040942990.eu-north-1.elb.amazonaws.com", + "type": "string" + } + }, + "resources": [ + { + "mode": "data", + "type": "aws_ssm_parameter", + "name": "private_subnet_ids", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ssm:eu-north-1:597936860210:parameter/core/subnets/private_ids", + "id": "/core/subnets/private_ids", + "insecure_value": "subnet-0caa8bd66179f08a2,subnet-098835d1205ea3fd4", + "name": "/core/subnets/private_ids", + "type": "StringList", + "value": "subnet-0caa8bd66179f08a2,subnet-098835d1205ea3fd4", + "version": 1, + "with_decryption": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "value" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "mode": "data", + "type": "aws_ssm_parameter", + "name": "public_subnet_ids", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ssm:eu-north-1:597936860210:parameter/core/subnets/public_ids", + "id": "/core/subnets/public_ids", + "insecure_value": "subnet-001e0b414a6336eac,subnet-0c5e252dcafaf9786", + "name": "/core/subnets/public_ids", + "type": "StringList", + "value": "subnet-001e0b414a6336eac,subnet-0c5e252dcafaf9786", + "version": 1, + "with_decryption": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "value" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "mode": "data", + "type": "aws_ssm_parameter", + "name": "route53_zone_id", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ssm:eu-north-1:597936860210:parameter/core/route53/zone_id", + "id": "/core/route53/zone_id", + "insecure_value": "Z04483424JOIMESKDYKS", + "name": "/core/route53/zone_id", + "type": "String", + "value": "Z04483424JOIMESKDYKS", + "version": 1, + "with_decryption": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "value" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "mode": "data", + "type": "aws_ssm_parameter", + "name": "route53_zone_name", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ssm:eu-north-1:597936860210:parameter/core/route53/zone_name", + "id": "/core/route53/zone_name", + "insecure_value": "sergey.c-loud.am", + "name": "/core/route53/zone_name", + "type": "String", + "value": "sergey.c-loud.am", + "version": 1, + "with_decryption": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "value" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "mode": "data", + "type": "aws_ssm_parameter", + "name": "vpc_id", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ssm:eu-north-1:597936860210:parameter/core/vpc/id", + "id": "/core/vpc/id", + "insecure_value": "vpc-0b0fce02ab20d23a4", + "name": "/core/vpc/id", + "type": "String", + "value": "vpc-0b0fce02ab20d23a4", + "version": 1, + "with_decryption": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "value" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "mode": "managed", + "type": "aws_acm_certificate", + "name": "cert", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:acm:eu-north-1:597936860210:certificate/9e020264-788b-43da-ba79-5225851bbf1c", + "certificate_authority_arn": "", + "certificate_body": null, + "certificate_chain": null, + "domain_name": "sergey.c-loud.am", + "domain_validation_options": [ + { + "domain_name": "*.sergey.c-loud.am", + "resource_record_name": "_d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am.", + "resource_record_type": "CNAME", + "resource_record_value": "_caaaea4d371bf4b211b2d3a770509cae.wzccmgtwzk.acm-validations.aws." + }, + { + "domain_name": "sergey.c-loud.am", + "resource_record_name": "_d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am.", + "resource_record_type": "CNAME", + "resource_record_value": "_caaaea4d371bf4b211b2d3a770509cae.wzccmgtwzk.acm-validations.aws." + } + ], + "early_renewal_duration": "", + "id": "arn:aws:acm:eu-north-1:597936860210:certificate/9e020264-788b-43da-ba79-5225851bbf1c", + "key_algorithm": "RSA_2048", + "not_after": "2027-04-04T23:59:59Z", + "not_before": "2026-09-19T00:00:00Z", + "options": [ + { + "certificate_transparency_logging_preference": "ENABLED" + } + ], + "pending_renewal": false, + "private_key": null, + "renewal_eligibility": "ELIGIBLE", + "renewal_summary": [], + "status": "ISSUED", + "subject_alternative_names": [ + "*.sergey.c-loud.am", + "sergey.c-loud.am" + ], + "tags": { + "Name": "aca-voting-app-acm-cert" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-acm-cert" + }, + "type": "AMAZON_ISSUED", + "validation_emails": [], + "validation_method": "DNS", + "validation_option": [] + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "private_key" + } + ] + ], + "identity_schema_version": 0, + "private": "bnVsbA==", + "create_before_destroy": true + } + ] + }, + { + "mode": "managed", + "type": "aws_acm_certificate_validation", + "name": "cert", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "certificate_arn": "arn:aws:acm:eu-north-1:597936860210:certificate/9e020264-788b-43da-ba79-5225851bbf1c", + "id": "2026-09-19 20:54:18.88 +0000 UTC", + "timeouts": null, + "validation_record_fqdns": [ + "_d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am" + ] + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo0NTAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "aws_acm_certificate.cert", + "aws_route53_record.cert_validation", + "data.aws_ssm_parameter.route53_zone_id" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_appautoscaling_policy", + "name": "result_alb", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "alarm_arns": [ + "arn:aws:cloudwatch:eu-north-1:597936860210:alarm:TargetTracking-service/aca-voting-app-cluster/result-AlarmHigh-1e13aa6e-7089-470e-8d4f-0151a547f9e6", + "arn:aws:cloudwatch:eu-north-1:597936860210:alarm:TargetTracking-service/aca-voting-app-cluster/result-AlarmLow-d3d13bca-2d63-4429-9c07-d1d341d25117" + ], + "arn": "arn:aws:autoscaling:eu-north-1:597936860210:scalingPolicy:9be1b3e0-5642-43bd-b75a-926e3b0180f0:resource/ecs/service/aca-voting-app-cluster/result:policyName/aca-voting-app-result-alb-scaling", + "id": "aca-voting-app-result-alb-scaling", + "name": "aca-voting-app-result-alb-scaling", + "policy_type": "TargetTrackingScaling", + "resource_id": "service/aca-voting-app-cluster/result", + "scalable_dimension": "ecs:service:DesiredCount", + "service_namespace": "ecs", + "step_scaling_policy_configuration": [], + "target_tracking_scaling_policy_configuration": [ + { + "customized_metric_specification": [], + "disable_scale_in": false, + "predefined_metric_specification": [ + { + "predefined_metric_type": "ALBRequestCountPerTarget", + "resource_label": "app/aca-voting-app-alb/8d56328e3d693d02/targetgroup/rslt-20260919205358523300000007/dff490837c2471ad" + } + ], + "scale_in_cooldown": 300, + "scale_out_cooldown": 60, + "target_value": 1000 + } + ] + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_appautoscaling_target.result", + "aws_cloudwatch_log_group.result", + "aws_ecs_service.result", + "aws_ecs_task_definition.result", + "aws_iam_role.ecs_execution_role", + "aws_iam_role.ecs_task_role", + "aws_security_group.result_task", + "aws_security_group.worker_task", + "data.aws_ssm_parameter.private_subnet_ids", + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this", + "module.aurora.aws_appautoscaling_policy.this", + "module.aurora.aws_appautoscaling_target.this", + "module.aurora.aws_cloudwatch_log_group.this", + "module.aurora.aws_db_parameter_group.this", + "module.aurora.aws_db_subnet_group.this", + "module.aurora.aws_iam_role.rds_enhanced_monitoring", + "module.aurora.aws_iam_role_policy_attachment.rds_enhanced_monitoring", + "module.aurora.aws_rds_cluster.this", + "module.aurora.aws_rds_cluster_activity_stream.this", + "module.aurora.aws_rds_cluster_endpoint.this", + "module.aurora.aws_rds_cluster_instance.this", + "module.aurora.aws_rds_cluster_parameter_group.this", + "module.aurora.aws_rds_cluster_role_association.this", + "module.aurora.aws_rds_shard_group.this", + "module.aurora.aws_secretsmanager_secret_rotation.this", + "module.aurora.aws_security_group.this", + "module.aurora.aws_security_group_rule.this", + "module.aurora.data.aws_iam_policy_document.monitoring_rds_assume_role", + "module.aurora.data.aws_partition.current", + "module.ecr_result.aws_ecr_repository.this", + "module.ecr_result.aws_ecrpublic_repository.this", + "module.ecs.module.cluster.aws_cloudwatch_log_group.this", + "module.ecs.module.cluster.aws_ecs_cluster.this", + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "random_password.postgres_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_appautoscaling_policy", + "name": "result_cpu", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "alarm_arns": [ + "arn:aws:cloudwatch:eu-north-1:597936860210:alarm:TargetTracking-service/aca-voting-app-cluster/result-AlarmHigh-bbf441db-71d4-4c4d-9202-81070a8d017c", + "arn:aws:cloudwatch:eu-north-1:597936860210:alarm:TargetTracking-service/aca-voting-app-cluster/result-AlarmLow-020dc50b-1f41-4c72-a6c1-3c68441b3bed" + ], + "arn": "arn:aws:autoscaling:eu-north-1:597936860210:scalingPolicy:9be1b3e0-5642-43bd-b75a-926e3b0180f0:resource/ecs/service/aca-voting-app-cluster/result:policyName/aca-voting-app-result-cpu-scaling", + "id": "aca-voting-app-result-cpu-scaling", + "name": "aca-voting-app-result-cpu-scaling", + "policy_type": "TargetTrackingScaling", + "resource_id": "service/aca-voting-app-cluster/result", + "scalable_dimension": "ecs:service:DesiredCount", + "service_namespace": "ecs", + "step_scaling_policy_configuration": [], + "target_tracking_scaling_policy_configuration": [ + { + "customized_metric_specification": [], + "disable_scale_in": false, + "predefined_metric_specification": [ + { + "predefined_metric_type": "ECSServiceAverageCPUUtilization", + "resource_label": "" + } + ], + "scale_in_cooldown": 300, + "scale_out_cooldown": 60, + "target_value": 70 + } + ] + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_appautoscaling_target.result", + "aws_cloudwatch_log_group.result", + "aws_ecs_service.result", + "aws_ecs_task_definition.result", + "aws_iam_role.ecs_execution_role", + "aws_iam_role.ecs_task_role", + "aws_security_group.result_task", + "aws_security_group.worker_task", + "data.aws_ssm_parameter.private_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this", + "module.aurora.aws_appautoscaling_policy.this", + "module.aurora.aws_appautoscaling_target.this", + "module.aurora.aws_cloudwatch_log_group.this", + "module.aurora.aws_db_parameter_group.this", + "module.aurora.aws_db_subnet_group.this", + "module.aurora.aws_iam_role.rds_enhanced_monitoring", + "module.aurora.aws_iam_role_policy_attachment.rds_enhanced_monitoring", + "module.aurora.aws_rds_cluster.this", + "module.aurora.aws_rds_cluster_activity_stream.this", + "module.aurora.aws_rds_cluster_endpoint.this", + "module.aurora.aws_rds_cluster_instance.this", + "module.aurora.aws_rds_cluster_parameter_group.this", + "module.aurora.aws_rds_cluster_role_association.this", + "module.aurora.aws_rds_shard_group.this", + "module.aurora.aws_secretsmanager_secret_rotation.this", + "module.aurora.aws_security_group.this", + "module.aurora.aws_security_group_rule.this", + "module.aurora.data.aws_iam_policy_document.monitoring_rds_assume_role", + "module.aurora.data.aws_partition.current", + "module.ecr_result.aws_ecr_repository.this", + "module.ecr_result.aws_ecrpublic_repository.this", + "module.ecs.module.cluster.aws_cloudwatch_log_group.this", + "module.ecs.module.cluster.aws_ecs_cluster.this", + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "random_password.postgres_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_appautoscaling_target", + "name": "result", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:application-autoscaling:eu-north-1:597936860210:scalable-target/0ec59be1b3e0564243bdb75a926e3b0180f0", + "id": "service/aca-voting-app-cluster/result", + "max_capacity": 2, + "min_capacity": 1, + "resource_id": "service/aca-voting-app-cluster/result", + "role_arn": "arn:aws:iam::597936860210:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService", + "scalable_dimension": "ecs:service:DesiredCount", + "service_namespace": "ecs", + "suspended_state": [ + { + "dynamic_scaling_in_suspended": false, + "dynamic_scaling_out_suspended": false, + "scheduled_scaling_suspended": false + } + ], + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_cloudwatch_log_group.result", + "aws_ecs_service.result", + "aws_ecs_task_definition.result", + "aws_iam_role.ecs_execution_role", + "aws_iam_role.ecs_task_role", + "aws_security_group.result_task", + "aws_security_group.worker_task", + "data.aws_ssm_parameter.private_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this", + "module.aurora.aws_appautoscaling_policy.this", + "module.aurora.aws_appautoscaling_target.this", + "module.aurora.aws_cloudwatch_log_group.this", + "module.aurora.aws_db_parameter_group.this", + "module.aurora.aws_db_subnet_group.this", + "module.aurora.aws_iam_role.rds_enhanced_monitoring", + "module.aurora.aws_iam_role_policy_attachment.rds_enhanced_monitoring", + "module.aurora.aws_rds_cluster.this", + "module.aurora.aws_rds_cluster_activity_stream.this", + "module.aurora.aws_rds_cluster_endpoint.this", + "module.aurora.aws_rds_cluster_instance.this", + "module.aurora.aws_rds_cluster_parameter_group.this", + "module.aurora.aws_rds_cluster_role_association.this", + "module.aurora.aws_rds_shard_group.this", + "module.aurora.aws_secretsmanager_secret_rotation.this", + "module.aurora.aws_security_group.this", + "module.aurora.aws_security_group_rule.this", + "module.aurora.data.aws_iam_policy_document.monitoring_rds_assume_role", + "module.aurora.data.aws_partition.current", + "module.ecr_result.aws_ecr_repository.this", + "module.ecr_result.aws_ecrpublic_repository.this", + "module.ecs.module.cluster.aws_cloudwatch_log_group.this", + "module.ecs.module.cluster.aws_ecs_cluster.this", + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "random_password.postgres_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "result", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:logs:eu-north-1:597936860210:log-group:/ecs/aca-voting-app/result", + "id": "/ecs/aca-voting-app/result", + "kms_key_id": "", + "log_group_class": "STANDARD", + "name": "/ecs/aca-voting-app/result", + "name_prefix": "", + "retention_in_days": 7, + "skip_destroy": false, + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "valkey", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:logs:eu-north-1:597936860210:log-group:/ecs/aca-voting-app/valkey", + "id": "/ecs/aca-voting-app/valkey", + "kms_key_id": "", + "log_group_class": "STANDARD", + "name": "/ecs/aca-voting-app/valkey", + "name_prefix": "", + "retention_in_days": 7, + "skip_destroy": false, + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "vote", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:logs:eu-north-1:597936860210:log-group:/ecs/aca-voting-app/vote", + "id": "/ecs/aca-voting-app/vote", + "kms_key_id": "", + "log_group_class": "STANDARD", + "name": "/ecs/aca-voting-app/vote", + "name_prefix": "", + "retention_in_days": 7, + "skip_destroy": false, + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "worker", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:logs:eu-north-1:597936860210:log-group:/ecs/aca-voting-app/worker", + "id": "/ecs/aca-voting-app/worker", + "kms_key_id": "", + "log_group_class": "STANDARD", + "name": "/ecs/aca-voting-app/worker", + "name_prefix": "", + "retention_in_days": 7, + "skip_destroy": false, + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "mode": "managed", + "type": "aws_ecs_service", + "name": "result", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "alarms": [], + "availability_zone_rebalancing": "DISABLED", + "capacity_provider_strategy": [], + "cluster": "arn:aws:ecs:eu-north-1:597936860210:cluster/aca-voting-app-cluster", + "deployment_circuit_breaker": [ + { + "enable": false, + "rollback": false + } + ], + "deployment_controller": [ + { + "type": "ECS" + } + ], + "deployment_maximum_percent": 200, + "deployment_minimum_healthy_percent": 100, + "desired_count": 1, + "enable_ecs_managed_tags": false, + "enable_execute_command": false, + "force_delete": null, + "force_new_deployment": null, + "health_check_grace_period_seconds": 0, + "iam_role": "/aws-service-role/ecs.amazonaws.com/AWSServiceRoleForECS", + "id": "arn:aws:ecs:eu-north-1:597936860210:service/aca-voting-app-cluster/result", + "launch_type": "FARGATE", + "load_balancer": [ + { + "container_name": "result", + "container_port": 80, + "elb_name": "", + "target_group_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/rslt-20260919205358523300000007/dff490837c2471ad" + } + ], + "name": "result", + "network_configuration": [ + { + "assign_public_ip": false, + "security_groups": [ + "sg-098f0586d57367f0a" + ], + "subnets": [ + "subnet-098835d1205ea3fd4", + "subnet-0caa8bd66179f08a2" + ] + } + ], + "ordered_placement_strategy": [], + "placement_constraints": [], + "platform_version": "LATEST", + "propagate_tags": "NONE", + "scheduling_strategy": "REPLICA", + "service_connect_configuration": [], + "service_registries": [], + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + }, + "task_definition": "arn:aws:ecs:eu-north-1:597936860210:task-definition/aca-voting-app-result:2", + "timeouts": null, + "triggers": {}, + "volume_configuration": [], + "vpc_lattice_configurations": [], + "wait_for_steady_state": false + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "network_configuration" + }, + { + "type": "index", + "value": { + "value": 0, + "type": "number" + } + }, + { + "type": "get_attr", + "value": "subnets" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMSJ9", + "dependencies": [ + "aws_cloudwatch_log_group.result", + "aws_ecs_task_definition.result", + "aws_iam_role.ecs_execution_role", + "aws_iam_role.ecs_task_role", + "aws_security_group.result_task", + "aws_security_group.worker_task", + "data.aws_ssm_parameter.private_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this", + "module.aurora.aws_appautoscaling_policy.this", + "module.aurora.aws_appautoscaling_target.this", + "module.aurora.aws_cloudwatch_log_group.this", + "module.aurora.aws_db_parameter_group.this", + "module.aurora.aws_db_subnet_group.this", + "module.aurora.aws_iam_role.rds_enhanced_monitoring", + "module.aurora.aws_iam_role_policy_attachment.rds_enhanced_monitoring", + "module.aurora.aws_rds_cluster.this", + "module.aurora.aws_rds_cluster_activity_stream.this", + "module.aurora.aws_rds_cluster_endpoint.this", + "module.aurora.aws_rds_cluster_instance.this", + "module.aurora.aws_rds_cluster_parameter_group.this", + "module.aurora.aws_rds_cluster_role_association.this", + "module.aurora.aws_rds_shard_group.this", + "module.aurora.aws_secretsmanager_secret_rotation.this", + "module.aurora.aws_security_group.this", + "module.aurora.aws_security_group_rule.this", + "module.aurora.data.aws_iam_policy_document.monitoring_rds_assume_role", + "module.aurora.data.aws_partition.current", + "module.ecr_result.aws_ecr_repository.this", + "module.ecr_result.aws_ecrpublic_repository.this", + "module.ecs.module.cluster.aws_cloudwatch_log_group.this", + "module.ecs.module.cluster.aws_ecs_cluster.this", + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "random_password.postgres_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_ecs_task_definition", + "name": "result", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ecs:eu-north-1:597936860210:task-definition/aca-voting-app-result:2", + "arn_without_revision": "arn:aws:ecs:eu-north-1:597936860210:task-definition/aca-voting-app-result", + "container_definitions": "[{\"environment\":[{\"name\":\"PORT\",\"value\":\"80\"},{\"name\":\"POSTGRES_DB\",\"value\":\"postgres\"},{\"name\":\"POSTGRES_HOST\",\"value\":\"aca-voting-app-aurora.cluster-c5is6oakwkpa.eu-north-1.rds.amazonaws.com\"},{\"name\":\"POSTGRES_PORT\",\"value\":\"5432\"},{\"name\":\"POSTGRES_SSL\",\"value\":\"true\"}],\"essential\":true,\"image\":\"597936860210.dkr.ecr.eu-north-1.amazonaws.com/aca-voting-app-result:latest\",\"logConfiguration\":{\"logDriver\":\"awslogs\",\"options\":{\"awslogs-group\":\"/ecs/aca-voting-app/result\",\"awslogs-region\":\"eu-north-1\",\"awslogs-stream-prefix\":\"result\"}},\"mountPoints\":[],\"name\":\"result\",\"portMappings\":[{\"containerPort\":80,\"hostPort\":80,\"protocol\":\"tcp\"}],\"secrets\":[{\"name\":\"POSTGRES_PASSWORD\",\"valueFrom\":\"arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh:password::\"},{\"name\":\"POSTGRES_USER\",\"valueFrom\":\"arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh:username::\"}],\"systemControls\":[],\"volumesFrom\":[]}]", + "cpu": "256", + "enable_fault_injection": false, + "ephemeral_storage": [], + "execution_role_arn": "arn:aws:iam::597936860210:role/aca-voting-app-ecs-execution-role", + "family": "aca-voting-app-result", + "id": "aca-voting-app-result", + "inference_accelerator": [], + "ipc_mode": "", + "memory": "512", + "network_mode": "awsvpc", + "pid_mode": "", + "placement_constraints": [], + "proxy_configuration": [], + "requires_compatibilities": [ + "FARGATE" + ], + "revision": 2, + "runtime_platform": [], + "skip_destroy": false, + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + }, + "task_role_arn": "arn:aws:iam::597936860210:role/aca-voting-app-ecs-task-role", + "track_latest": false, + "volume": [] + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJzY2hlbWFfdmVyc2lvbiI6IjEifQ==", + "dependencies": [ + "aws_cloudwatch_log_group.result", + "aws_iam_role.ecs_execution_role", + "aws_iam_role.ecs_task_role", + "data.aws_ssm_parameter.private_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.aurora.aws_cloudwatch_log_group.this", + "module.aurora.aws_db_subnet_group.this", + "module.aurora.aws_iam_role.rds_enhanced_monitoring", + "module.aurora.aws_rds_cluster.this", + "module.aurora.aws_rds_cluster_parameter_group.this", + "module.aurora.aws_security_group.this", + "module.aurora.data.aws_iam_policy_document.monitoring_rds_assume_role", + "module.ecr_result.aws_ecr_repository.this", + "module.ecr_result.aws_ecrpublic_repository.this", + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "random_password.postgres_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_elasticache_user", + "name": "default", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "access_string": "on ~* +@all", + "arn": "arn:aws:elasticache:eu-north-1:597936860210:user:aca-voting-app-default-user", + "authentication_mode": [ + { + "password_count": 1, + "passwords": [], + "type": "password" + } + ], + "engine": "valkey", + "id": "aca-voting-app-default-user", + "no_password_required": false, + "passwords": [ + "xMNn7wXRQDlIYxC6" + ], + "tags": { + "Name": "aca-voting-app-default-user" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-default-user" + }, + "timeouts": null, + "user_id": "aca-voting-app-default-user", + "user_name": "default" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "authentication_mode" + }, + { + "type": "index", + "value": { + "value": 0, + "type": "number" + } + }, + { + "type": "get_attr", + "value": "passwords" + } + ], + [ + { + "type": "get_attr", + "value": "passwords" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDAsImRlbGV0ZSI6MzAwMDAwMDAwMDAwLCJyZWFkIjozMDAwMDAwMDAwMDAsInVwZGF0ZSI6MzAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "random_password.valkey_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_elasticache_user", + "name": "valkey_user", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "access_string": "on ~* +@all", + "arn": "arn:aws:elasticache:eu-north-1:597936860210:user:aca-voting-app-valkey-user", + "authentication_mode": [ + { + "password_count": 1, + "passwords": [], + "type": "password" + } + ], + "engine": "valkey", + "id": "aca-voting-app-valkey-user", + "no_password_required": false, + "passwords": [ + "xMNn7wXRQDlIYxC6" + ], + "tags": { + "Name": "aca-voting-app-valkey-user" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-valkey-user" + }, + "timeouts": null, + "user_id": "aca-voting-app-valkey-user", + "user_name": "valkeyuser" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "authentication_mode" + }, + { + "type": "index", + "value": { + "value": 0, + "type": "number" + } + }, + { + "type": "get_attr", + "value": "passwords" + } + ], + [ + { + "type": "get_attr", + "value": "passwords" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDAsImRlbGV0ZSI6MzAwMDAwMDAwMDAwLCJyZWFkIjozMDAwMDAwMDAwMDAsInVwZGF0ZSI6MzAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "random_password.valkey_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_elasticache_user_group", + "name": "valkey", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:elasticache:eu-north-1:597936860210:usergroup:aca-voting-app-valkey-ug", + "engine": "valkey", + "id": "aca-voting-app-valkey-ug", + "tags": { + "Name": "aca-voting-app-valkey-ug" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-valkey-ug" + }, + "user_group_id": "aca-voting-app-valkey-ug", + "user_ids": [ + "aca-voting-app-default-user", + "aca-voting-app-valkey-user" + ] + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_elasticache_user.default", + "aws_elasticache_user.valkey_user", + "random_password.valkey_password" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_iam_policy", + "name": "ecs_secrets_policy", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:iam::597936860210:policy/aca-voting-app-ecs-secrets-policy", + "attachment_count": 1, + "description": "Allows ECS Execution Role to fetch secrets from AWS Secrets Manager", + "id": "arn:aws:iam::597936860210:policy/aca-voting-app-ecs-secrets-policy", + "name": "aca-voting-app-ecs-secrets-policy", + "name_prefix": "", + "path": "/", + "policy": "{\"Statement\":[{\"Action\":[\"secretsmanager:GetSecretValue\"],\"Effect\":\"Allow\",\"Resource\":[\"arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-valkey-credentials-VdZ3KO\",\"arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh\"]}],\"Version\":\"2012-10-17\"}", + "policy_id": "ANPAYWN6QNQZBAU36P3IG", + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "module.secrets_manager_valkey.aws_secretsmanager_secret.this" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_iam_role", + "name": "ecs_execution_role", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:iam::597936860210:role/aca-voting-app-ecs-execution-role", + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ecs-tasks.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "create_date": "2026-09-19T20:53:57Z", + "description": "", + "force_detach_policies": false, + "id": "aca-voting-app-ecs-execution-role", + "inline_policy": [], + "managed_policy_arns": [ + "arn:aws:iam::597936860210:policy/aca-voting-app-ecs-secrets-policy", + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" + ], + "max_session_duration": 3600, + "name": "aca-voting-app-ecs-execution-role", + "name_prefix": "", + "path": "/", + "permissions_boundary": "", + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + }, + "unique_id": "AROAYWN6QNQZN6DBN4DUR" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "mode": "managed", + "type": "aws_iam_role", + "name": "ecs_task_role", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "arn": "arn:aws:iam::597936860210:role/aca-voting-app-ecs-task-role", + "assume_role_policy": "{\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"ecs-tasks.amazonaws.com\"}}],\"Version\":\"2012-10-17\"}", + "create_date": "2026-09-19T20:53:58Z", + "description": "", + "force_detach_policies": false, + "id": "aca-voting-app-ecs-task-role", + "inline_policy": [], + "managed_policy_arns": [], + "max_session_duration": 3600, + "name": "aca-voting-app-ecs-task-role", + "name_prefix": "", + "path": "/", + "permissions_boundary": "", + "tags": {}, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform" + }, + "unique_id": "AROAYWN6QNQZLCPC4AML3" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "ecs_execution_role_policy", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-ecs-execution-role-20260919205359940000000009", + "policy_arn": "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + "role": "aca-voting-app-ecs-execution-role" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_iam_role.ecs_execution_role" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "ecs_execution_secrets", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-ecs-execution-role-2026091920540087140000000b", + "policy_arn": "arn:aws:iam::597936860210:policy/aca-voting-app-ecs-secrets-policy", + "role": "aca-voting-app-ecs-execution-role" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_iam_policy.ecs_secrets_policy", + "aws_iam_role.ecs_execution_role", + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "module.secrets_manager_valkey.aws_secretsmanager_secret.this" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_route53_record", + "name": "cert_validation", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "*.sergey.c-loud.am", + "schema_version": 2, + "attributes": { + "alias": [], + "allow_overwrite": true, + "cidr_routing_policy": [], + "failover_routing_policy": [], + "fqdn": "_d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am", + "geolocation_routing_policy": [], + "geoproximity_routing_policy": [], + "health_check_id": "", + "id": "Z04483424JOIMESKDYKS__d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am._CNAME", + "latency_routing_policy": [], + "multivalue_answer_routing_policy": false, + "name": "_d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am", + "records": [ + "_caaaea4d371bf4b211b2d3a770509cae.wzccmgtwzk.acm-validations.aws." + ], + "set_identifier": "", + "timeouts": null, + "ttl": 60, + "type": "CNAME", + "weighted_routing_policy": [], + "zone_id": "Z04483424JOIMESKDYKS" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "zone_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInVwZGF0ZSI6MTgwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMiJ9", + "dependencies": [ + "aws_acm_certificate.cert", + "data.aws_ssm_parameter.route53_zone_id" + ] + }, + { + "index_key": "sergey.c-loud.am", + "schema_version": 2, + "attributes": { + "alias": [], + "allow_overwrite": true, + "cidr_routing_policy": [], + "failover_routing_policy": [], + "fqdn": "_d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am", + "geolocation_routing_policy": [], + "geoproximity_routing_policy": [], + "health_check_id": "", + "id": "Z04483424JOIMESKDYKS__d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am._CNAME", + "latency_routing_policy": [], + "multivalue_answer_routing_policy": false, + "name": "_d86586121d735fece87a3e46fa9a8fc4.sergey.c-loud.am", + "records": [ + "_caaaea4d371bf4b211b2d3a770509cae.wzccmgtwzk.acm-validations.aws." + ], + "set_identifier": "", + "timeouts": null, + "ttl": 60, + "type": "CNAME", + "weighted_routing_policy": [], + "zone_id": "Z04483424JOIMESKDYKS" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "zone_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInVwZGF0ZSI6MTgwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMiJ9", + "dependencies": [ + "aws_acm_certificate.cert", + "data.aws_ssm_parameter.route53_zone_id" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_route53_record", + "name": "result", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 2, + "attributes": { + "alias": [ + { + "evaluate_target_health": true, + "name": "aca-voting-app-alb-2040942990.eu-north-1.elb.amazonaws.com", + "zone_id": "Z23TAZ6LKFMNIO" + } + ], + "allow_overwrite": null, + "cidr_routing_policy": [], + "failover_routing_policy": [], + "fqdn": "result.sergey.c-loud.am", + "geolocation_routing_policy": [], + "geoproximity_routing_policy": [], + "health_check_id": "", + "id": "Z04483424JOIMESKDYKS_result.sergey.c-loud.am_A", + "latency_routing_policy": [], + "multivalue_answer_routing_policy": false, + "name": "result.sergey.c-loud.am", + "records": [], + "set_identifier": "", + "timeouts": null, + "ttl": 0, + "type": "A", + "weighted_routing_policy": [], + "zone_id": "Z04483424JOIMESKDYKS" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "zone_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInVwZGF0ZSI6MTgwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMiJ9", + "dependencies": [ + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.route53_zone_id", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_route53_record", + "name": "vote", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 2, + "attributes": { + "alias": [ + { + "evaluate_target_health": true, + "name": "aca-voting-app-alb-2040942990.eu-north-1.elb.amazonaws.com", + "zone_id": "Z23TAZ6LKFMNIO" + } + ], + "allow_overwrite": null, + "cidr_routing_policy": [], + "failover_routing_policy": [], + "fqdn": "vote.sergey.c-loud.am", + "geolocation_routing_policy": [], + "geoproximity_routing_policy": [], + "health_check_id": "", + "id": "Z04483424JOIMESKDYKS_vote.sergey.c-loud.am_A", + "latency_routing_policy": [], + "multivalue_answer_routing_policy": false, + "name": "vote.sergey.c-loud.am", + "records": [], + "set_identifier": "", + "timeouts": null, + "ttl": 0, + "type": "A", + "weighted_routing_policy": [], + "zone_id": "Z04483424JOIMESKDYKS" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "zone_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxODAwMDAwMDAwMDAwLCJkZWxldGUiOjE4MDAwMDAwMDAwMDAsInVwZGF0ZSI6MTgwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMiJ9", + "dependencies": [ + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.route53_zone_id", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_security_group", + "name": "result_task", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group/sg-098f0586d57367f0a", + "description": "Allow inbound traffic from ALB to Result service and outbound access", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "Allow all outbound traffic for Aurora PostgreSQL and AWS API access", + "from_port": 0, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "-1", + "security_groups": [], + "self": false, + "to_port": 0 + } + ], + "id": "sg-098f0586d57367f0a", + "ingress": [ + { + "cidr_blocks": [], + "description": "HTTP inbound traffic from ALB", + "from_port": 80, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [ + "sg-076b09be76e6ac155" + ], + "self": false, + "to_port": 80 + } + ], + "name": "aca-voting-app-result-task-sg", + "name_prefix": "", + "owner_id": "597936860210", + "revoke_rules_on_delete": false, + "tags": { + "Name": "aca-voting-app-result-task-sg" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-result-task-sg" + }, + "timeouts": null, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", + "dependencies": [ + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_security_group", + "name": "valkey", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group/sg-09cb692e45d5c4e70", + "description": "Security group for AWS ElastiCache Valkey (Allows Vote and Worker tasks)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "Allow all outbound traffic", + "from_port": 0, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "-1", + "security_groups": [], + "self": false, + "to_port": 0 + } + ], + "id": "sg-09cb692e45d5c4e70", + "ingress": [ + { + "cidr_blocks": [], + "description": "Valkey traffic from Vote task", + "from_port": 6379, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [ + "sg-09ddd75d36a689ae6" + ], + "self": false, + "to_port": 6379 + }, + { + "cidr_blocks": [], + "description": "Valkey traffic from Worker task", + "from_port": 6379, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [ + "sg-0d2fde29138deb496" + ], + "self": false, + "to_port": 6379 + } + ], + "name": "aca-voting-app-valkey-sg", + "name_prefix": "", + "owner_id": "597936860210", + "revoke_rules_on_delete": false, + "tags": { + "Name": "aca-voting-app-valkey-sg" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-valkey-sg" + }, + "timeouts": null, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", + "dependencies": [ + "aws_security_group.vote_task", + "aws_security_group.worker_task", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_security_group", + "name": "vote_task", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group/sg-09ddd75d36a689ae6", + "description": "Allow inbound traffic from ALB to Vote service and outbound access", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "Allow all outbound traffic for Valkey and AWS API access", + "from_port": 0, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "-1", + "security_groups": [], + "self": false, + "to_port": 0 + } + ], + "id": "sg-09ddd75d36a689ae6", + "ingress": [ + { + "cidr_blocks": [], + "description": "HTTP inbound traffic from ALB", + "from_port": 80, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [ + "sg-076b09be76e6ac155" + ], + "self": false, + "to_port": 80 + } + ], + "name": "aca-voting-app-vote-task-sg", + "name_prefix": "", + "owner_id": "597936860210", + "revoke_rules_on_delete": false, + "tags": { + "Name": "aca-voting-app-vote-task-sg" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-vote-task-sg" + }, + "timeouts": null, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", + "dependencies": [ + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "mode": "managed", + "type": "aws_security_group", + "name": "worker_task", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group/sg-0d2fde29138deb496", + "description": "Background worker service security group (No inbound access required)", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "Allow all outbound traffic for Valkey, Aurora PostgreSQL, and AWS API access", + "from_port": 0, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "-1", + "security_groups": [], + "self": false, + "to_port": 0 + } + ], + "id": "sg-0d2fde29138deb496", + "ingress": [], + "name": "aca-voting-app-worker-task-sg", + "name_prefix": "", + "owner_id": "597936860210", + "revoke_rules_on_delete": false, + "tags": { + "Name": "aca-voting-app-worker-task-sg" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-worker-task-sg" + }, + "timeouts": null, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", + "dependencies": [ + "data.aws_ssm_parameter.vpc_id" + ] + } + ] + }, + { + "mode": "managed", + "type": "random_password", + "name": "postgres_password", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$gIc7u4MQ2HxDAxPojdkN4.GhDag9Y4oRcubMlj1Qu.GSHzEVQShqO", + "id": "none", + "keepers": null, + "length": 16, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "Iwli79t4sbtD4cl8", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0, + "create_before_destroy": true + } + ] + }, + { + "mode": "managed", + "type": "random_password", + "name": "valkey_password", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [ + { + "schema_version": 3, + "attributes": { + "bcrypt_hash": "$2a$10$CxRWcLsNWnT9Tv7xs8eqK.DWLEkAFIs0fmr6OEB27ndaNg2ZIS0r2", + "id": "none", + "keepers": null, + "length": 16, + "lower": true, + "min_lower": 0, + "min_numeric": 0, + "min_special": 0, + "min_upper": 0, + "number": true, + "numeric": true, + "override_special": null, + "result": "xMNn7wXRQDlIYxC6", + "special": false, + "upper": true + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "bcrypt_hash" + } + ], + [ + { + "type": "get_attr", + "value": "result" + } + ] + ], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.alb", + "mode": "data", + "type": "aws_partition", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "dns_suffix": "amazonaws.com", + "id": "aws", + "partition": "aws", + "reverse_dns_prefix": "com.amazonaws" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lambda_permission", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lb", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "access_logs": [ + { + "bucket": "", + "enabled": false, + "prefix": "" + } + ], + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:loadbalancer/app/aca-voting-app-alb/8d56328e3d693d02", + "arn_suffix": "app/aca-voting-app-alb/8d56328e3d693d02", + "client_keep_alive": 3600, + "connection_logs": [ + { + "bucket": "", + "enabled": false, + "prefix": "" + } + ], + "customer_owned_ipv4_pool": "", + "desync_mitigation_mode": "defensive", + "dns_name": "aca-voting-app-alb-2040942990.eu-north-1.elb.amazonaws.com", + "dns_record_client_routing_policy": null, + "drop_invalid_header_fields": true, + "enable_cross_zone_load_balancing": true, + "enable_deletion_protection": false, + "enable_http2": true, + "enable_tls_version_and_cipher_suite_headers": false, + "enable_waf_fail_open": false, + "enable_xff_client_port": false, + "enable_zonal_shift": false, + "enforce_security_group_inbound_rules_on_private_link_traffic": "", + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:loadbalancer/app/aca-voting-app-alb/8d56328e3d693d02", + "idle_timeout": 60, + "internal": false, + "ip_address_type": "ipv4", + "ipam_pools": [], + "load_balancer_type": "application", + "minimum_load_balancer_capacity": [], + "name": "aca-voting-app-alb", + "name_prefix": "", + "preserve_host_header": false, + "security_groups": [ + "sg-076b09be76e6ac155" + ], + "subnet_mapping": [ + { + "allocation_id": "", + "ipv6_address": "", + "outpost_id": "", + "private_ipv4_address": "", + "subnet_id": "subnet-001e0b414a6336eac" + }, + { + "allocation_id": "", + "ipv6_address": "", + "outpost_id": "", + "private_ipv4_address": "", + "subnet_id": "subnet-0c5e252dcafaf9786" + } + ], + "subnets": [ + "subnet-001e0b414a6336eac", + "subnet-0c5e252dcafaf9786" + ], + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "timeouts": { + "create": null, + "delete": null, + "update": null + }, + "vpc_id": "vpc-0b0fce02ab20d23a4", + "xff_header_processing_mode": "append", + "zone_id": "Z23TAZ6LKFMNIO" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "subnets" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6NjAwMDAwMDAwMDAwLCJ1cGRhdGUiOjYwMDAwMDAwMDAwMH19", + "dependencies": [ + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lb_listener", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "http", + "schema_version": 0, + "attributes": { + "alpn_policy": null, + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/c11cabe3412f7726", + "certificate_arn": null, + "default_action": [ + { + "authenticate_cognito": [], + "authenticate_oidc": [], + "fixed_response": [], + "forward": [], + "order": 1, + "redirect": [ + { + "host": "#{host}", + "path": "/#{path}", + "port": "443", + "protocol": "HTTPS", + "query": "#{query}", + "status_code": "HTTP_301" + } + ], + "target_group_arn": "", + "type": "redirect" + } + ], + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/c11cabe3412f7726", + "load_balancer_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:loadbalancer/app/aca-voting-app-alb/8d56328e3d693d02", + "mutual_authentication": [], + "port": 80, + "protocol": "HTTP", + "routing_http_request_x_amzn_mtls_clientcert_header_name": null, + "routing_http_request_x_amzn_mtls_clientcert_issuer_header_name": null, + "routing_http_request_x_amzn_mtls_clientcert_leaf_header_name": null, + "routing_http_request_x_amzn_mtls_clientcert_serial_number_header_name": null, + "routing_http_request_x_amzn_mtls_clientcert_subject_header_name": null, + "routing_http_request_x_amzn_mtls_clientcert_validity_header_name": null, + "routing_http_request_x_amzn_tls_cipher_suite_header_name": null, + "routing_http_request_x_amzn_tls_version_header_name": null, + "routing_http_response_access_control_allow_credentials_header_value": "", + "routing_http_response_access_control_allow_headers_header_value": "", + "routing_http_response_access_control_allow_methods_header_value": "", + "routing_http_response_access_control_allow_origin_header_value": "", + "routing_http_response_access_control_expose_headers_header_value": "", + "routing_http_response_access_control_max_age_header_value": "", + "routing_http_response_content_security_policy_header_value": "", + "routing_http_response_server_enabled": true, + "routing_http_response_strict_transport_security_header_value": "", + "routing_http_response_x_content_type_options_header_value": "", + "routing_http_response_x_frame_options_header_value": "", + "ssl_policy": "", + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tcp_idle_timeout_seconds": null, + "timeouts": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDAsInVwZGF0ZSI6MzAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "aws_acm_certificate.cert", + "aws_acm_certificate_validation.cert", + "aws_route53_record.cert_validation", + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.route53_zone_id", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this" + ] + }, + { + "index_key": "https", + "schema_version": 0, + "attributes": { + "alpn_policy": null, + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d", + "certificate_arn": "arn:aws:acm:eu-north-1:597936860210:certificate/9e020264-788b-43da-ba79-5225851bbf1c", + "default_action": [ + { + "authenticate_cognito": [], + "authenticate_oidc": [], + "fixed_response": [], + "forward": [], + "order": 1, + "redirect": [], + "target_group_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/vote-20260919205358500300000006/e0ab9b922a96d772", + "type": "forward" + } + ], + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d", + "load_balancer_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:loadbalancer/app/aca-voting-app-alb/8d56328e3d693d02", + "mutual_authentication": [ + { + "advertise_trust_store_ca_names": "", + "ignore_client_certificate_expiry": false, + "mode": "off", + "trust_store_arn": "" + } + ], + "port": 443, + "protocol": "HTTPS", + "routing_http_request_x_amzn_mtls_clientcert_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_issuer_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_leaf_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_serial_number_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_subject_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_validity_header_name": "", + "routing_http_request_x_amzn_tls_cipher_suite_header_name": "", + "routing_http_request_x_amzn_tls_version_header_name": "", + "routing_http_response_access_control_allow_credentials_header_value": "", + "routing_http_response_access_control_allow_headers_header_value": "", + "routing_http_response_access_control_allow_methods_header_value": "", + "routing_http_response_access_control_allow_origin_header_value": "", + "routing_http_response_access_control_expose_headers_header_value": "", + "routing_http_response_access_control_max_age_header_value": "", + "routing_http_response_content_security_policy_header_value": "", + "routing_http_response_server_enabled": true, + "routing_http_response_strict_transport_security_header_value": "", + "routing_http_response_x_content_type_options_header_value": "", + "routing_http_response_x_frame_options_header_value": "", + "ssl_policy": "ELBSecurityPolicy-TLS13-1-2-Res-2021-06", + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tcp_idle_timeout_seconds": null, + "timeouts": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDAsInVwZGF0ZSI6MzAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "aws_acm_certificate.cert", + "aws_acm_certificate_validation.cert", + "aws_route53_record.cert_validation", + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.route53_zone_id", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this" + ] + }, + { + "index_key": "result_https", + "schema_version": 0, + "attributes": { + "alpn_policy": null, + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/293854faf4ae49db", + "certificate_arn": "arn:aws:acm:eu-north-1:597936860210:certificate/9e020264-788b-43da-ba79-5225851bbf1c", + "default_action": [ + { + "authenticate_cognito": [], + "authenticate_oidc": [], + "fixed_response": [], + "forward": [], + "order": 1, + "redirect": [], + "target_group_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/rslt-20260919205358523300000007/dff490837c2471ad", + "type": "forward" + } + ], + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/293854faf4ae49db", + "load_balancer_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:loadbalancer/app/aca-voting-app-alb/8d56328e3d693d02", + "mutual_authentication": [ + { + "advertise_trust_store_ca_names": "", + "ignore_client_certificate_expiry": false, + "mode": "off", + "trust_store_arn": "" + } + ], + "port": 8443, + "protocol": "HTTPS", + "routing_http_request_x_amzn_mtls_clientcert_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_issuer_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_leaf_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_serial_number_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_subject_header_name": "", + "routing_http_request_x_amzn_mtls_clientcert_validity_header_name": "", + "routing_http_request_x_amzn_tls_cipher_suite_header_name": "", + "routing_http_request_x_amzn_tls_version_header_name": "", + "routing_http_response_access_control_allow_credentials_header_value": "", + "routing_http_response_access_control_allow_headers_header_value": "", + "routing_http_response_access_control_allow_methods_header_value": "", + "routing_http_response_access_control_allow_origin_header_value": "", + "routing_http_response_access_control_expose_headers_header_value": "", + "routing_http_response_access_control_max_age_header_value": "", + "routing_http_response_content_security_policy_header_value": "", + "routing_http_response_server_enabled": true, + "routing_http_response_strict_transport_security_header_value": "", + "routing_http_response_x_content_type_options_header_value": "", + "routing_http_response_x_frame_options_header_value": "", + "ssl_policy": "ELBSecurityPolicy-TLS13-1-2-Res-2021-06", + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tcp_idle_timeout_seconds": null, + "timeouts": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDAsInVwZGF0ZSI6MzAwMDAwMDAwMDAwfX0=", + "dependencies": [ + "aws_acm_certificate.cert", + "aws_acm_certificate_validation.cert", + "aws_route53_record.cert_validation", + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.route53_zone_id", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lb_listener_certificate", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lb_listener_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "https/result_host", + "schema_version": 0, + "attributes": { + "action": [ + { + "authenticate_cognito": [], + "authenticate_oidc": [], + "fixed_response": [], + "forward": [], + "order": 1, + "redirect": [], + "target_group_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/rslt-20260919205358523300000007/dff490837c2471ad", + "type": "forward" + } + ], + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener-rule/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d/f8c4fe49ed8f241a", + "condition": [ + { + "host_header": [ + { + "values": [ + "result.sergey.c-loud.am" + ] + } + ], + "http_header": [], + "http_request_method": [], + "path_pattern": [], + "query_string": [], + "source_ip": [] + } + ], + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener-rule/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d/f8c4fe49ed8f241a", + "listener_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d", + "priority": 1, + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_acm_certificate.cert", + "aws_acm_certificate_validation.cert", + "aws_route53_record.cert_validation", + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.route53_zone_id", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_lb_listener.this", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this" + ] + }, + { + "index_key": "https/vote_host", + "schema_version": 0, + "attributes": { + "action": [ + { + "authenticate_cognito": [], + "authenticate_oidc": [], + "fixed_response": [], + "forward": [], + "order": 1, + "redirect": [], + "target_group_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/vote-20260919205358500300000006/e0ab9b922a96d772", + "type": "forward" + } + ], + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener-rule/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d/f03aa45234da2913", + "condition": [ + { + "host_header": [ + { + "values": [ + "vote.sergey.c-loud.am" + ] + } + ], + "http_header": [], + "http_request_method": [], + "path_pattern": [], + "query_string": [], + "source_ip": [] + } + ], + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener-rule/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d/f03aa45234da2913", + "listener_arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:listener/app/aca-voting-app-alb/8d56328e3d693d02/16e6e9cff4a2207d", + "priority": 2, + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "aws_acm_certificate.cert", + "aws_acm_certificate_validation.cert", + "aws_route53_record.cert_validation", + "data.aws_ssm_parameter.public_subnet_ids", + "data.aws_ssm_parameter.route53_zone_id", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_lb.this", + "module.alb.aws_lb_listener.this", + "module.alb.aws_lb_target_group.this", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lb_target_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "result", + "schema_version": 0, + "attributes": { + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/rslt-20260919205358523300000007/dff490837c2471ad", + "arn_suffix": "targetgroup/rslt-20260919205358523300000007/dff490837c2471ad", + "connection_termination": null, + "deregistration_delay": "300", + "health_check": [ + { + "enabled": true, + "healthy_threshold": 3, + "interval": 15, + "matcher": "200-399", + "path": "/", + "port": "traffic-port", + "protocol": "HTTP", + "timeout": 5, + "unhealthy_threshold": 3 + } + ], + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/rslt-20260919205358523300000007/dff490837c2471ad", + "ip_address_type": "ipv4", + "lambda_multi_value_headers_enabled": false, + "load_balancer_arns": [ + "arn:aws:elasticloadbalancing:eu-north-1:597936860210:loadbalancer/app/aca-voting-app-alb/8d56328e3d693d02" + ], + "load_balancing_algorithm_type": "round_robin", + "load_balancing_anomaly_mitigation": "off", + "load_balancing_cross_zone_enabled": "use_load_balancer_configuration", + "name": "rslt-20260919205358523300000007", + "name_prefix": "rslt-", + "port": 80, + "preserve_client_ip": null, + "protocol": "HTTP", + "protocol_version": "HTTP1", + "proxy_protocol_v2": false, + "slow_start": 0, + "stickiness": [ + { + "cookie_duration": 86400, + "cookie_name": "", + "enabled": false, + "type": "lb_cookie" + } + ], + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "target_failover": [ + { + "on_deregistration": null, + "on_unhealthy": null + } + ], + "target_group_health": [ + { + "dns_failover": [ + { + "minimum_healthy_targets_count": "1", + "minimum_healthy_targets_percentage": "off" + } + ], + "unhealthy_state_routing": [ + { + "minimum_healthy_targets_count": 1, + "minimum_healthy_targets_percentage": "off" + } + ] + } + ], + "target_health_state": [ + { + "enable_unhealthy_connection_termination": null, + "unhealthy_draining_interval": null + } + ], + "target_type": "ip", + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "data.aws_ssm_parameter.vpc_id" + ], + "create_before_destroy": true + }, + { + "index_key": "vote", + "schema_version": 0, + "attributes": { + "arn": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/vote-20260919205358500300000006/e0ab9b922a96d772", + "arn_suffix": "targetgroup/vote-20260919205358500300000006/e0ab9b922a96d772", + "connection_termination": null, + "deregistration_delay": "300", + "health_check": [ + { + "enabled": true, + "healthy_threshold": 3, + "interval": 15, + "matcher": "200-399", + "path": "/", + "port": "traffic-port", + "protocol": "HTTP", + "timeout": 5, + "unhealthy_threshold": 3 + } + ], + "id": "arn:aws:elasticloadbalancing:eu-north-1:597936860210:targetgroup/vote-20260919205358500300000006/e0ab9b922a96d772", + "ip_address_type": "ipv4", + "lambda_multi_value_headers_enabled": false, + "load_balancer_arns": [ + "arn:aws:elasticloadbalancing:eu-north-1:597936860210:loadbalancer/app/aca-voting-app-alb/8d56328e3d693d02" + ], + "load_balancing_algorithm_type": "round_robin", + "load_balancing_anomaly_mitigation": "off", + "load_balancing_cross_zone_enabled": "use_load_balancer_configuration", + "name": "vote-20260919205358500300000006", + "name_prefix": "vote-", + "port": 80, + "preserve_client_ip": null, + "protocol": "HTTP", + "protocol_version": "HTTP1", + "proxy_protocol_v2": false, + "slow_start": 0, + "stickiness": [ + { + "cookie_duration": 86400, + "cookie_name": "", + "enabled": false, + "type": "lb_cookie" + } + ], + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "target_failover": [ + { + "on_deregistration": null, + "on_unhealthy": null + } + ], + "target_group_health": [ + { + "dns_failover": [ + { + "minimum_healthy_targets_count": "1", + "minimum_healthy_targets_percentage": "off" + } + ], + "unhealthy_state_routing": [ + { + "minimum_healthy_targets_count": 1, + "minimum_healthy_targets_percentage": "off" + } + ] + } + ], + "target_health_state": [ + { + "enable_unhealthy_connection_termination": null, + "unhealthy_draining_interval": null + } + ], + "target_type": "ip", + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "data.aws_ssm_parameter.vpc_id" + ], + "create_before_destroy": true + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lb_target_group_attachment", + "name": "additional", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_lb_target_group_attachment", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_route53_record", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_security_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group/sg-076b09be76e6ac155", + "description": "Security group for aca-voting-app-alb application load balancer", + "egress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "", + "from_port": 0, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "-1", + "security_groups": [], + "self": false, + "to_port": 0 + } + ], + "id": "sg-076b09be76e6ac155", + "ingress": [ + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "HTTP traffic for redirect to HTTPS", + "from_port": 80, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 80 + }, + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "HTTPS secondary port for Result app", + "from_port": 8443, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 8443 + }, + { + "cidr_blocks": [ + "0.0.0.0/0" + ], + "description": "HTTPS secure web traffic for Vote and Result apps", + "from_port": 80, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [], + "self": false, + "to_port": 443 + } + ], + "name": "aca-voting-app-alb-20260919205358477800000005", + "name_prefix": "aca-voting-app-alb-", + "owner_id": "597936860210", + "revoke_rules_on_delete": false, + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "timeouts": null, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", + "dependencies": [ + "data.aws_ssm_parameter.vpc_id" + ], + "create_before_destroy": true + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_vpc_security_group_egress_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "all", + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group-rule/sgr-079f23af0e36ba1a7", + "cidr_ipv4": "0.0.0.0/0", + "cidr_ipv6": null, + "description": null, + "from_port": null, + "id": "sgr-079f23af0e36ba1a7", + "ip_protocol": "-1", + "prefix_list_id": null, + "referenced_security_group_id": null, + "security_group_id": "sg-076b09be76e6ac155", + "security_group_rule_id": "sgr-079f23af0e36ba1a7", + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "to_port": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "dependencies": [ + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_vpc_security_group_ingress_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "http_80", + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group-rule/sgr-0ec18ada1813bc428", + "cidr_ipv4": "0.0.0.0/0", + "cidr_ipv6": null, + "description": "HTTP traffic for redirect to HTTPS", + "from_port": 80, + "id": "sgr-0ec18ada1813bc428", + "ip_protocol": "tcp", + "prefix_list_id": null, + "referenced_security_group_id": null, + "security_group_id": "sg-076b09be76e6ac155", + "security_group_rule_id": "sgr-0ec18ada1813bc428", + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "to_port": 80 + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "dependencies": [ + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + }, + { + "index_key": "https_443", + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group-rule/sgr-0bdf43c28c4a2d003", + "cidr_ipv4": "0.0.0.0/0", + "cidr_ipv6": null, + "description": "HTTPS secure web traffic for Vote and Result apps", + "from_port": 80, + "id": "sgr-0bdf43c28c4a2d003", + "ip_protocol": "tcp", + "prefix_list_id": null, + "referenced_security_group_id": null, + "security_group_id": "sg-076b09be76e6ac155", + "security_group_rule_id": "sgr-0bdf43c28c4a2d003", + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "to_port": 443 + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "dependencies": [ + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + }, + { + "index_key": "https_8443", + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group-rule/sgr-0116fe45353b9b782", + "cidr_ipv4": "0.0.0.0/0", + "cidr_ipv6": null, + "description": "HTTPS secondary port for Result app", + "from_port": 8443, + "id": "sgr-0116fe45353b9b782", + "ip_protocol": "tcp", + "prefix_list_id": null, + "referenced_security_group_id": null, + "security_group_id": "sg-076b09be76e6ac155", + "security_group_rule_id": "sgr-0116fe45353b9b782", + "tags": { + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-alb", + "terraform-aws-modules": "alb" + }, + "to_port": 8443 + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "dependencies": [ + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this" + ] + } + ] + }, + { + "module": "module.alb", + "mode": "managed", + "type": "aws_wafv2_web_acl_association", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "monitoring_rds_assume_role", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "data", + "type": "aws_partition", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "dns_suffix": "amazonaws.com", + "id": "aws", + "partition": "aws", + "reverse_dns_prefix": "com.amazonaws" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_appautoscaling_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_appautoscaling_target", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_db_parameter_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_db_subnet_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:rds:eu-north-1:597936860210:subgrp:aca-voting-app-aurora", + "description": "For Aurora cluster aca-voting-app-aurora", + "id": "aca-voting-app-aurora", + "name": "aca-voting-app-aurora", + "name_prefix": "", + "subnet_ids": [ + "subnet-098835d1205ea3fd4", + "subnet-0caa8bd66179f08a2" + ], + "supported_network_types": [ + "IPV4" + ], + "tags": { + "Name": "aca-voting-app-aurora" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-aurora" + }, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "subnet_ids" + } + ] + ], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "data.aws_ssm_parameter.private_subnet_ids" + ], + "create_before_destroy": true + } + ] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_iam_role", + "name": "rds_enhanced_monitoring", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "rds_enhanced_monitoring", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_rds_cluster", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 1, + "attributes": { + "allocated_storage": 1, + "allow_major_version_upgrade": false, + "apply_immediately": null, + "arn": "arn:aws:rds:eu-north-1:597936860210:cluster:aca-voting-app-aurora", + "availability_zones": [ + "eu-north-1a", + "eu-north-1b", + "eu-north-1c" + ], + "backtrack_window": 0, + "backup_retention_period": 1, + "ca_certificate_identifier": null, + "ca_certificate_valid_till": null, + "cluster_identifier": "aca-voting-app-aurora", + "cluster_identifier_prefix": "", + "cluster_members": [ + "aca-voting-app-aurora-1" + ], + "cluster_resource_id": "cluster-ZNXMDDWBXRUQTQTXF44MCMXZBI", + "cluster_scalability_type": "", + "copy_tags_to_snapshot": false, + "database_insights_mode": "standard", + "database_name": "postgres", + "db_cluster_instance_class": "", + "db_cluster_parameter_group_name": "default.aurora-postgresql15", + "db_instance_parameter_group_name": null, + "db_subnet_group_name": "aca-voting-app-aurora", + "db_system_id": "", + "delete_automated_backups": true, + "deletion_protection": false, + "domain": "", + "domain_iam_role_name": "", + "enable_global_write_forwarding": false, + "enable_http_endpoint": false, + "enable_local_write_forwarding": false, + "enabled_cloudwatch_logs_exports": [], + "endpoint": "aca-voting-app-aurora.cluster-c5is6oakwkpa.eu-north-1.rds.amazonaws.com", + "engine": "aurora-postgresql", + "engine_lifecycle_support": "open-source-rds-extended-support", + "engine_mode": "provisioned", + "engine_version": "15.14", + "engine_version_actual": "15.14", + "final_snapshot_identifier": null, + "global_cluster_identifier": "", + "hosted_zone_id": "Z3MPDEQW7KHUGY", + "iam_database_authentication_enabled": false, + "iam_roles": [], + "id": "aca-voting-app-aurora", + "iops": 0, + "kms_key_id": "arn:aws:kms:eu-north-1:597936860210:key/cef696e0-5cdb-4f31-b6b5-a9f183dcc048", + "manage_master_user_password": null, + "master_password": "Iwli79t4sbtD4cl8", + "master_password_wo": null, + "master_password_wo_version": null, + "master_user_secret": [], + "master_user_secret_kms_key_id": null, + "master_username": "postgres", + "monitoring_interval": 0, + "monitoring_role_arn": "", + "network_type": "IPV4", + "performance_insights_enabled": false, + "performance_insights_kms_key_id": "", + "performance_insights_retention_period": 0, + "port": 5432, + "preferred_backup_window": "02:00-03:00", + "preferred_maintenance_window": "sun:05:00-sun:06:00", + "reader_endpoint": "aca-voting-app-aurora.cluster-ro-c5is6oakwkpa.eu-north-1.rds.amazonaws.com", + "replication_source_identifier": "", + "restore_to_point_in_time": [], + "s3_import": [], + "scaling_configuration": [], + "serverlessv2_scaling_configuration": [ + { + "max_capacity": 1, + "min_capacity": 0.5, + "seconds_until_auto_pause": 0 + } + ], + "skip_final_snapshot": true, + "snapshot_identifier": null, + "source_region": null, + "storage_encrypted": true, + "storage_type": "", + "tags": { + "Name": "aca-voting-app-aurora" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-aurora" + }, + "timeouts": { + "create": null, + "delete": null, + "update": null + }, + "vpc_security_group_ids": [ + "sg-0a25291ebe1147318" + ] + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "master_password" + } + ], + [ + { + "type": "get_attr", + "value": "master_password_wo" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo3MjAwMDAwMDAwMDAwLCJkZWxldGUiOjcyMDAwMDAwMDAwMDAsInVwZGF0ZSI6NzIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMSJ9", + "dependencies": [ + "data.aws_ssm_parameter.private_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.aurora.aws_cloudwatch_log_group.this", + "module.aurora.aws_db_subnet_group.this", + "module.aurora.aws_iam_role.rds_enhanced_monitoring", + "module.aurora.aws_rds_cluster_parameter_group.this", + "module.aurora.aws_security_group.this", + "module.aurora.data.aws_iam_policy_document.monitoring_rds_assume_role", + "random_password.postgres_password" + ], + "create_before_destroy": true + } + ] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_rds_cluster_activity_stream", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_rds_cluster_endpoint", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_rds_cluster_instance", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "1", + "schema_version": 0, + "attributes": { + "apply_immediately": null, + "arn": "arn:aws:rds:eu-north-1:597936860210:db:aca-voting-app-aurora-1", + "auto_minor_version_upgrade": true, + "availability_zone": "eu-north-1a", + "ca_cert_identifier": "rds-ca-rsa2048-g1", + "cluster_identifier": "aca-voting-app-aurora", + "copy_tags_to_snapshot": false, + "custom_iam_instance_profile": "", + "db_parameter_group_name": "default.aurora-postgresql15", + "db_subnet_group_name": "aca-voting-app-aurora", + "dbi_resource_id": "db-M6CSH3KPZJF3NNAKVDUJH5H5LY", + "endpoint": "aca-voting-app-aurora-1.c5is6oakwkpa.eu-north-1.rds.amazonaws.com", + "engine": "aurora-postgresql", + "engine_version": "15.14", + "engine_version_actual": "15.14", + "force_destroy": false, + "id": "aca-voting-app-aurora-1", + "identifier": "aca-voting-app-aurora-1", + "identifier_prefix": "", + "instance_class": "db.serverless", + "kms_key_id": "arn:aws:kms:eu-north-1:597936860210:key/cef696e0-5cdb-4f31-b6b5-a9f183dcc048", + "monitoring_interval": 0, + "monitoring_role_arn": "", + "network_type": "IPV4", + "performance_insights_enabled": false, + "performance_insights_kms_key_id": "", + "performance_insights_retention_period": 0, + "port": 5432, + "preferred_backup_window": "02:00-03:00", + "preferred_maintenance_window": "sun:05:00-sun:06:00", + "promotion_tier": 0, + "publicly_accessible": false, + "storage_encrypted": true, + "tags": { + "Name": "aca-voting-app-aurora" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-aurora" + }, + "timeouts": { + "create": null, + "delete": null, + "update": null + }, + "writer": true + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo1NDAwMDAwMDAwMDAwLCJkZWxldGUiOjU0MDAwMDAwMDAwMDAsInVwZGF0ZSI6NTQwMDAwMDAwMDAwMH19", + "dependencies": [ + "data.aws_ssm_parameter.private_subnet_ids", + "data.aws_ssm_parameter.vpc_id", + "module.aurora.aws_cloudwatch_log_group.this", + "module.aurora.aws_db_parameter_group.this", + "module.aurora.aws_db_subnet_group.this", + "module.aurora.aws_iam_role.rds_enhanced_monitoring", + "module.aurora.aws_rds_cluster.this", + "module.aurora.aws_rds_cluster_parameter_group.this", + "module.aurora.aws_security_group.this", + "module.aurora.data.aws_iam_policy_document.monitoring_rds_assume_role", + "random_password.postgres_password" + ], + "create_before_destroy": true + } + ] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_rds_cluster_parameter_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_rds_cluster_role_association", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_rds_shard_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_secretsmanager_secret_rotation", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_security_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 1, + "attributes": { + "arn": "arn:aws:ec2:eu-north-1:597936860210:security-group/sg-0a25291ebe1147318", + "description": "Control traffic to/from RDS Aurora aca-voting-app-aurora", + "egress": [], + "id": "sg-0a25291ebe1147318", + "ingress": [ + { + "cidr_blocks": [], + "description": "Allow PostgreSQL traffic from Result task", + "from_port": 5432, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [ + "sg-098f0586d57367f0a" + ], + "self": false, + "to_port": 5432 + }, + { + "cidr_blocks": [], + "description": "Allow PostgreSQL traffic from Worker task", + "from_port": 5432, + "ipv6_cidr_blocks": [], + "prefix_list_ids": [], + "protocol": "tcp", + "security_groups": [ + "sg-0d2fde29138deb496" + ], + "self": false, + "to_port": 5432 + } + ], + "name": "aca-voting-app-aurora-20260919205358251600000003", + "name_prefix": "aca-voting-app-aurora-", + "owner_id": "597936860210", + "revoke_rules_on_delete": false, + "tags": { + "Name": "aca-voting-app-aurora" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-aurora" + }, + "timeouts": null, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "vpc_id" + } + ] + ], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsImRlbGV0ZSI6OTAwMDAwMDAwMDAwfSwic2NoZW1hX3ZlcnNpb24iOiIxIn0=", + "dependencies": [ + "data.aws_ssm_parameter.vpc_id" + ], + "create_before_destroy": true + } + ] + }, + { + "module": "module.aurora", + "mode": "managed", + "type": "aws_security_group_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "result_ingress", + "schema_version": 2, + "attributes": { + "cidr_blocks": null, + "description": "Allow PostgreSQL traffic from Result task", + "from_port": 5432, + "id": "sgrule-3815097738", + "ipv6_cidr_blocks": null, + "prefix_list_ids": null, + "protocol": "tcp", + "security_group_id": "sg-0a25291ebe1147318", + "security_group_rule_id": "sgr-023bcc65b70fa317a", + "self": false, + "source_security_group_id": "sg-098f0586d57367f0a", + "timeouts": null, + "to_port": 5432, + "type": "ingress" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjIifQ==", + "dependencies": [ + "aws_security_group.result_task", + "aws_security_group.worker_task", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this", + "module.aurora.aws_security_group.this" + ] + }, + { + "index_key": "worker_ingress", + "schema_version": 2, + "attributes": { + "cidr_blocks": null, + "description": "Allow PostgreSQL traffic from Worker task", + "from_port": 5432, + "id": "sgrule-1091764110", + "ipv6_cidr_blocks": null, + "prefix_list_ids": null, + "protocol": "tcp", + "security_group_id": "sg-0a25291ebe1147318", + "security_group_rule_id": "sgr-0cdce97f8ce1c9607", + "self": false, + "source_security_group_id": "sg-0d2fde29138deb496", + "timeouts": null, + "to_port": 5432, + "type": "ingress" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjIifQ==", + "dependencies": [ + "aws_security_group.result_task", + "aws_security_group.worker_task", + "data.aws_ssm_parameter.vpc_id", + "module.alb.aws_security_group.this", + "module.aurora.aws_security_group.this" + ] + } + ] + }, + { + "module": "module.ecr_result", + "mode": "data", + "type": "aws_caller_identity", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "account_id": "597936860210", + "arn": "arn:aws:iam::597936860210:root", + "id": "597936860210", + "user_id": "597936860210" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_result", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "repository", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "4286884915", + "json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"PrivateReadOnly\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ecr:ListTagsForResource\",\n \"ecr:ListImages\",\n \"ecr:GetRepositoryPolicy\",\n \"ecr:GetLifecyclePolicyPreview\",\n \"ecr:GetLifecyclePolicy\",\n \"ecr:GetDownloadUrlForLayer\",\n \"ecr:GetAuthorizationToken\",\n \"ecr:DescribeRepositories\",\n \"ecr:DescribeImages\",\n \"ecr:DescribeImageScanFindings\",\n \"ecr:BatchGetImage\",\n \"ecr:BatchCheckLayerAvailability\"\n ],\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::597936860210:root\"\n }\n }\n ]\n}", + "minified_json": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"PrivateReadOnly\",\"Effect\":\"Allow\",\"Action\":[\"ecr:ListTagsForResource\",\"ecr:ListImages\",\"ecr:GetRepositoryPolicy\",\"ecr:GetLifecyclePolicyPreview\",\"ecr:GetLifecyclePolicy\",\"ecr:GetDownloadUrlForLayer\",\"ecr:GetAuthorizationToken\",\"ecr:DescribeRepositories\",\"ecr:DescribeImages\",\"ecr:DescribeImageScanFindings\",\"ecr:BatchGetImage\",\"ecr:BatchCheckLayerAvailability\"],\"Principal\":{\"AWS\":\"arn:aws:iam::597936860210:root\"}}]}", + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, + "statement": [ + { + "actions": [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:DescribeImageScanFindings", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetAuthorizationToken", + "ecr:GetDownloadUrlForLayer", + "ecr:GetLifecyclePolicy", + "ecr:GetLifecyclePolicyPreview", + "ecr:GetRepositoryPolicy", + "ecr:ListImages", + "ecr:ListTagsForResource" + ], + "condition": [], + "effect": "Allow", + "not_actions": [], + "not_principals": [], + "not_resources": [], + "principals": [ + { + "identifiers": [ + "arn:aws:iam::597936860210:root" + ], + "type": "AWS" + } + ], + "resources": [], + "sid": "PrivateReadOnly" + } + ], + "version": "2012-10-17" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_result", + "mode": "data", + "type": "aws_partition", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "dns_suffix": "amazonaws.com", + "id": "aws", + "partition": "aws", + "reverse_dns_prefix": "com.amazonaws" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecr_lifecycle_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-result", + "policy": "{\"rules\":[{\"action\":{\"type\":\"expire\"},\"description\":\"Keep last 5 images for cost optimization\",\"rulePriority\":1,\"selection\":{\"countNumber\":5,\"countType\":\"imageCountMoreThan\",\"tagStatus\":\"any\"}}]}", + "registry_id": "597936860210", + "repository": "aca-voting-app-result" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecr_result.aws_ecr_repository.this" + ] + } + ] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecr_pull_through_cache_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecr_registry_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecr_registry_scanning_configuration", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecr_replication_configuration", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecr_repository", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ecr:eu-north-1:597936860210:repository/aca-voting-app-result", + "encryption_configuration": [ + { + "encryption_type": "AES256", + "kms_key": "" + } + ], + "force_delete": true, + "id": "aca-voting-app-result", + "image_scanning_configuration": [ + { + "scan_on_push": true + } + ], + "image_tag_mutability": "MUTABLE", + "name": "aca-voting-app-result", + "registry_id": "597936860210", + "repository_url": "597936860210.dkr.ecr.eu-north-1.amazonaws.com/aca-voting-app-result", + "tags": { + "Name": "aca-voting-app-result" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-result" + }, + "timeouts": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiZGVsZXRlIjoxMjAwMDAwMDAwMDAwfX0=" + } + ] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecr_repository_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-result", + "policy": "{\"Statement\":[{\"Action\":[\"ecr:ListTagsForResource\",\"ecr:ListImages\",\"ecr:GetRepositoryPolicy\",\"ecr:GetLifecyclePolicyPreview\",\"ecr:GetLifecyclePolicy\",\"ecr:GetDownloadUrlForLayer\",\"ecr:GetAuthorizationToken\",\"ecr:DescribeRepositories\",\"ecr:DescribeImages\",\"ecr:DescribeImageScanFindings\",\"ecr:BatchGetImage\",\"ecr:BatchCheckLayerAvailability\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::597936860210:root\"},\"Sid\":\"PrivateReadOnly\"}],\"Version\":\"2012-10-17\"}", + "registry_id": "597936860210", + "repository": "aca-voting-app-result" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecr_result.aws_ecr_repository.this", + "module.ecr_result.data.aws_caller_identity.current", + "module.ecr_result.data.aws_iam_policy_document.repository", + "module.ecr_result.data.aws_partition.current" + ] + } + ] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecrpublic_repository", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_result", + "mode": "managed", + "type": "aws_ecrpublic_repository_policy", + "name": "example", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_vote", + "mode": "data", + "type": "aws_caller_identity", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "account_id": "597936860210", + "arn": "arn:aws:iam::597936860210:root", + "id": "597936860210", + "user_id": "597936860210" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_vote", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "repository", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "4286884915", + "json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"PrivateReadOnly\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ecr:ListTagsForResource\",\n \"ecr:ListImages\",\n \"ecr:GetRepositoryPolicy\",\n \"ecr:GetLifecyclePolicyPreview\",\n \"ecr:GetLifecyclePolicy\",\n \"ecr:GetDownloadUrlForLayer\",\n \"ecr:GetAuthorizationToken\",\n \"ecr:DescribeRepositories\",\n \"ecr:DescribeImages\",\n \"ecr:DescribeImageScanFindings\",\n \"ecr:BatchGetImage\",\n \"ecr:BatchCheckLayerAvailability\"\n ],\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::597936860210:root\"\n }\n }\n ]\n}", + "minified_json": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"PrivateReadOnly\",\"Effect\":\"Allow\",\"Action\":[\"ecr:ListTagsForResource\",\"ecr:ListImages\",\"ecr:GetRepositoryPolicy\",\"ecr:GetLifecyclePolicyPreview\",\"ecr:GetLifecyclePolicy\",\"ecr:GetDownloadUrlForLayer\",\"ecr:GetAuthorizationToken\",\"ecr:DescribeRepositories\",\"ecr:DescribeImages\",\"ecr:DescribeImageScanFindings\",\"ecr:BatchGetImage\",\"ecr:BatchCheckLayerAvailability\"],\"Principal\":{\"AWS\":\"arn:aws:iam::597936860210:root\"}}]}", + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, + "statement": [ + { + "actions": [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:DescribeImageScanFindings", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetAuthorizationToken", + "ecr:GetDownloadUrlForLayer", + "ecr:GetLifecyclePolicy", + "ecr:GetLifecyclePolicyPreview", + "ecr:GetRepositoryPolicy", + "ecr:ListImages", + "ecr:ListTagsForResource" + ], + "condition": [], + "effect": "Allow", + "not_actions": [], + "not_principals": [], + "not_resources": [], + "principals": [ + { + "identifiers": [ + "arn:aws:iam::597936860210:root" + ], + "type": "AWS" + } + ], + "resources": [], + "sid": "PrivateReadOnly" + } + ], + "version": "2012-10-17" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_vote", + "mode": "data", + "type": "aws_partition", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "dns_suffix": "amazonaws.com", + "id": "aws", + "partition": "aws", + "reverse_dns_prefix": "com.amazonaws" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecr_lifecycle_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-vote", + "policy": "{\"rules\":[{\"action\":{\"type\":\"expire\"},\"description\":\"Keep last 5 images for cost optimization\",\"rulePriority\":1,\"selection\":{\"countNumber\":5,\"countType\":\"imageCountMoreThan\",\"tagStatus\":\"any\"}}]}", + "registry_id": "597936860210", + "repository": "aca-voting-app-vote" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecr_vote.aws_ecr_repository.this" + ] + } + ] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecr_pull_through_cache_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecr_registry_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecr_registry_scanning_configuration", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecr_replication_configuration", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecr_repository", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ecr:eu-north-1:597936860210:repository/aca-voting-app-vote", + "encryption_configuration": [ + { + "encryption_type": "AES256", + "kms_key": "" + } + ], + "force_delete": true, + "id": "aca-voting-app-vote", + "image_scanning_configuration": [ + { + "scan_on_push": true + } + ], + "image_tag_mutability": "MUTABLE", + "name": "aca-voting-app-vote", + "registry_id": "597936860210", + "repository_url": "597936860210.dkr.ecr.eu-north-1.amazonaws.com/aca-voting-app-vote", + "tags": { + "Name": "aca-voting-app-vote" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-vote" + }, + "timeouts": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiZGVsZXRlIjoxMjAwMDAwMDAwMDAwfX0=" + } + ] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecr_repository_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-vote", + "policy": "{\"Statement\":[{\"Action\":[\"ecr:ListTagsForResource\",\"ecr:ListImages\",\"ecr:GetRepositoryPolicy\",\"ecr:GetLifecyclePolicyPreview\",\"ecr:GetLifecyclePolicy\",\"ecr:GetDownloadUrlForLayer\",\"ecr:GetAuthorizationToken\",\"ecr:DescribeRepositories\",\"ecr:DescribeImages\",\"ecr:DescribeImageScanFindings\",\"ecr:BatchGetImage\",\"ecr:BatchCheckLayerAvailability\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::597936860210:root\"},\"Sid\":\"PrivateReadOnly\"}],\"Version\":\"2012-10-17\"}", + "registry_id": "597936860210", + "repository": "aca-voting-app-vote" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecr_vote.aws_ecr_repository.this", + "module.ecr_vote.data.aws_caller_identity.current", + "module.ecr_vote.data.aws_iam_policy_document.repository", + "module.ecr_vote.data.aws_partition.current" + ] + } + ] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecrpublic_repository", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_vote", + "mode": "managed", + "type": "aws_ecrpublic_repository_policy", + "name": "example", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_worker", + "mode": "data", + "type": "aws_caller_identity", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "account_id": "597936860210", + "arn": "arn:aws:iam::597936860210:root", + "id": "597936860210", + "user_id": "597936860210" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_worker", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "repository", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "4286884915", + "json": "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Sid\": \"PrivateReadOnly\",\n \"Effect\": \"Allow\",\n \"Action\": [\n \"ecr:ListTagsForResource\",\n \"ecr:ListImages\",\n \"ecr:GetRepositoryPolicy\",\n \"ecr:GetLifecyclePolicyPreview\",\n \"ecr:GetLifecyclePolicy\",\n \"ecr:GetDownloadUrlForLayer\",\n \"ecr:GetAuthorizationToken\",\n \"ecr:DescribeRepositories\",\n \"ecr:DescribeImages\",\n \"ecr:DescribeImageScanFindings\",\n \"ecr:BatchGetImage\",\n \"ecr:BatchCheckLayerAvailability\"\n ],\n \"Principal\": {\n \"AWS\": \"arn:aws:iam::597936860210:root\"\n }\n }\n ]\n}", + "minified_json": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"PrivateReadOnly\",\"Effect\":\"Allow\",\"Action\":[\"ecr:ListTagsForResource\",\"ecr:ListImages\",\"ecr:GetRepositoryPolicy\",\"ecr:GetLifecyclePolicyPreview\",\"ecr:GetLifecyclePolicy\",\"ecr:GetDownloadUrlForLayer\",\"ecr:GetAuthorizationToken\",\"ecr:DescribeRepositories\",\"ecr:DescribeImages\",\"ecr:DescribeImageScanFindings\",\"ecr:BatchGetImage\",\"ecr:BatchCheckLayerAvailability\"],\"Principal\":{\"AWS\":\"arn:aws:iam::597936860210:root\"}}]}", + "override_json": null, + "override_policy_documents": null, + "policy_id": null, + "source_json": null, + "source_policy_documents": null, + "statement": [ + { + "actions": [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:DescribeImageScanFindings", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetAuthorizationToken", + "ecr:GetDownloadUrlForLayer", + "ecr:GetLifecyclePolicy", + "ecr:GetLifecyclePolicyPreview", + "ecr:GetRepositoryPolicy", + "ecr:ListImages", + "ecr:ListTagsForResource" + ], + "condition": [], + "effect": "Allow", + "not_actions": [], + "not_principals": [], + "not_resources": [], + "principals": [ + { + "identifiers": [ + "arn:aws:iam::597936860210:root" + ], + "type": "AWS" + } + ], + "resources": [], + "sid": "PrivateReadOnly" + } + ], + "version": "2012-10-17" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_worker", + "mode": "data", + "type": "aws_partition", + "name": "current", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "schema_version": 0, + "attributes": { + "dns_suffix": "amazonaws.com", + "id": "aws", + "partition": "aws", + "reverse_dns_prefix": "com.amazonaws" + }, + "sensitive_attributes": [], + "identity_schema_version": 0 + } + ] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecr_lifecycle_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-worker", + "policy": "{\"rules\":[{\"action\":{\"type\":\"expire\"},\"description\":\"Keep last 5 images for cost optimization\",\"rulePriority\":1,\"selection\":{\"countNumber\":5,\"countType\":\"imageCountMoreThan\",\"tagStatus\":\"any\"}}]}", + "registry_id": "597936860210", + "repository": "aca-voting-app-worker" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecr_worker.aws_ecr_repository.this" + ] + } + ] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecr_pull_through_cache_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecr_registry_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecr_registry_scanning_configuration", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecr_replication_configuration", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecr_repository", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ecr:eu-north-1:597936860210:repository/aca-voting-app-worker", + "encryption_configuration": [ + { + "encryption_type": "AES256", + "kms_key": "" + } + ], + "force_delete": true, + "id": "aca-voting-app-worker", + "image_scanning_configuration": [ + { + "scan_on_push": true + } + ], + "image_tag_mutability": "MUTABLE", + "name": "aca-voting-app-worker", + "registry_id": "597936860210", + "repository_url": "597936860210.dkr.ecr.eu-north-1.amazonaws.com/aca-voting-app-worker", + "tags": { + "Name": "aca-voting-app-worker" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-worker" + }, + "timeouts": null + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiZGVsZXRlIjoxMjAwMDAwMDAwMDAwfX0=" + } + ] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecr_repository_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "id": "aca-voting-app-worker", + "policy": "{\"Statement\":[{\"Action\":[\"ecr:ListTagsForResource\",\"ecr:ListImages\",\"ecr:GetRepositoryPolicy\",\"ecr:GetLifecyclePolicyPreview\",\"ecr:GetLifecyclePolicy\",\"ecr:GetDownloadUrlForLayer\",\"ecr:GetAuthorizationToken\",\"ecr:DescribeRepositories\",\"ecr:DescribeImages\",\"ecr:DescribeImageScanFindings\",\"ecr:BatchGetImage\",\"ecr:BatchCheckLayerAvailability\"],\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::597936860210:root\"},\"Sid\":\"PrivateReadOnly\"}],\"Version\":\"2012-10-17\"}", + "registry_id": "597936860210", + "repository": "aca-voting-app-worker" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecr_worker.aws_ecr_repository.this", + "module.ecr_worker.data.aws_caller_identity.current", + "module.ecr_worker.data.aws_iam_policy_document.repository", + "module.ecr_worker.data.aws_partition.current" + ] + } + ] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecrpublic_repository", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecr_worker", + "mode": "managed", + "type": "aws_ecrpublic_repository_policy", + "name": "example", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecs.module.cluster", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "task_exec", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecs.module.cluster", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "task_exec_assume", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:logs:eu-north-1:597936860210:log-group:/aws/ecs/aca-voting-app-cluster", + "id": "/aws/ecs/aca-voting-app-cluster", + "kms_key_id": "", + "log_group_class": "STANDARD", + "name": "/aws/ecs/aca-voting-app-cluster", + "name_prefix": "", + "retention_in_days": 90, + "skip_destroy": false, + "tags": { + "Name": "aca-voting-app-cluster" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-cluster" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_ecs_capacity_provider", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_ecs_cluster", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:ecs:eu-north-1:597936860210:cluster/aca-voting-app-cluster", + "configuration": [ + { + "execute_command_configuration": [ + { + "kms_key_id": "", + "log_configuration": [ + { + "cloud_watch_encryption_enabled": false, + "cloud_watch_log_group_name": "/aws/ecs/aca-voting-app-cluster", + "s3_bucket_encryption_enabled": false, + "s3_bucket_name": "", + "s3_key_prefix": "" + } + ], + "logging": "OVERRIDE" + } + ], + "managed_storage_configuration": [] + } + ], + "id": "arn:aws:ecs:eu-north-1:597936860210:cluster/aca-voting-app-cluster", + "name": "aca-voting-app-cluster", + "service_connect_defaults": [], + "setting": [ + { + "name": "containerInsights", + "value": "enabled" + } + ], + "tags": { + "Name": "aca-voting-app-cluster" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-cluster" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecs.module.cluster.aws_cloudwatch_log_group.this" + ] + } + ] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_ecs_cluster_capacity_providers", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "capacity_providers": [ + "FARGATE" + ], + "cluster_name": "aca-voting-app-cluster", + "default_capacity_provider_strategy": [ + { + "base": 0, + "capacity_provider": "FARGATE", + "weight": 100 + } + ], + "id": "aca-voting-app-cluster" + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.ecs.module.cluster.aws_cloudwatch_log_group.this", + "module.ecs.module.cluster.aws_ecs_capacity_provider.this", + "module.ecs.module.cluster.aws_ecs_cluster.this" + ] + } + ] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_iam_policy", + "name": "task_exec", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_iam_role", + "name": "task_exec", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "task_exec", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.ecs.module.cluster", + "mode": "managed", + "type": "aws_iam_role_policy_attachment", + "name": "task_exec_additional", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_postgres", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_postgres", + "mode": "managed", + "type": "aws_secretsmanager_secret", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh", + "description": "PostgreSQL credentials for aca-voting-app", + "force_overwrite_replica_secret": false, + "id": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh", + "kms_key_id": "", + "name": "aca-voting-app-postgres-credentials", + "name_prefix": "", + "policy": "", + "recovery_window_in_days": 0, + "replica": [], + "tags": { + "Name": "aca-voting-app-postgres-credentials" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-postgres-credentials" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "module": "module.secrets_manager_postgres", + "mode": "managed", + "type": "aws_secretsmanager_secret_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_postgres", + "mode": "managed", + "type": "aws_secretsmanager_secret_rotation", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_postgres", + "mode": "managed", + "type": "aws_secretsmanager_secret_version", + "name": "ignore_changes", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_postgres", + "mode": "managed", + "type": "aws_secretsmanager_secret_version", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh", + "has_secret_string_wo": null, + "id": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh|terraform-20260919205359863400000008", + "secret_binary": "", + "secret_id": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-postgres-credentials-F6E1Dh", + "secret_string": "{\"password\":\"Iwli79t4sbtD4cl8\",\"username\":\"postgres\"}", + "secret_string_wo": null, + "secret_string_wo_version": null, + "version_id": "terraform-20260919205359863400000008", + "version_stages": [ + "AWSCURRENT" + ] + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "secret_binary" + } + ], + [ + { + "type": "get_attr", + "value": "secret_string" + } + ], + [ + { + "type": "get_attr", + "value": "secret_string_wo" + } + ] + ], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.secrets_manager_postgres.aws_secretsmanager_secret.this", + "module.secrets_manager_postgres.random_password.this", + "random_password.postgres_password" + ] + } + ] + }, + { + "module": "module.secrets_manager_postgres", + "mode": "managed", + "type": "random_password", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_valkey", + "mode": "data", + "type": "aws_iam_policy_document", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_valkey", + "mode": "managed", + "type": "aws_secretsmanager_secret", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-valkey-credentials-VdZ3KO", + "description": "Valkey RBAC user and password credentials for aca-voting-app", + "force_overwrite_replica_secret": false, + "id": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-valkey-credentials-VdZ3KO", + "kms_key_id": "", + "name": "aca-voting-app-valkey-credentials", + "name_prefix": "", + "policy": "", + "recovery_window_in_days": 0, + "replica": [], + "tags": { + "Name": "aca-voting-app-valkey-credentials" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-valkey-credentials" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "module": "module.secrets_manager_valkey", + "mode": "managed", + "type": "aws_secretsmanager_secret_policy", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_valkey", + "mode": "managed", + "type": "aws_secretsmanager_secret_rotation", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_valkey", + "mode": "managed", + "type": "aws_secretsmanager_secret_version", + "name": "ignore_changes", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.secrets_manager_valkey", + "mode": "managed", + "type": "aws_secretsmanager_secret_version", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-valkey-credentials-VdZ3KO", + "has_secret_string_wo": null, + "id": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-valkey-credentials-VdZ3KO|terraform-2026091920540002370000000a", + "secret_binary": "", + "secret_id": "arn:aws:secretsmanager:eu-north-1:597936860210:secret:aca-voting-app-valkey-credentials-VdZ3KO", + "secret_string": "{\"password\":\"xMNn7wXRQDlIYxC6\",\"username\":\"valkeyuser\"}", + "secret_string_wo": null, + "secret_string_wo_version": null, + "version_id": "terraform-2026091920540002370000000a", + "version_stages": [ + "AWSCURRENT" + ] + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "secret_binary" + } + ], + [ + { + "type": "get_attr", + "value": "secret_string" + } + ], + [ + { + "type": "get_attr", + "value": "secret_string_wo" + } + ] + ], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "module.secrets_manager_valkey.aws_secretsmanager_secret.this", + "module.secrets_manager_valkey.random_password.this", + "random_password.valkey_password" + ] + } + ] + }, + { + "module": "module.secrets_manager_valkey", + "mode": "managed", + "type": "random_password", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_cloudwatch_log_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": "slow-log", + "schema_version": 0, + "attributes": { + "arn": "arn:aws:logs:eu-north-1:597936860210:log-group:/aws/elasticache/aca-voting-app-valkey", + "id": "/aws/elasticache/aca-voting-app-valkey", + "kms_key_id": "", + "log_group_class": "STANDARD", + "name": "/aws/elasticache/aca-voting-app-valkey", + "name_prefix": "", + "retention_in_days": 14, + "skip_destroy": false, + "tags": { + "Name": "aca-voting-app-valkey", + "terraform-aws-modules": "elasticache" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-valkey", + "terraform-aws-modules": "elasticache" + } + }, + "sensitive_attributes": [], + "identity_schema_version": 0, + "private": "bnVsbA==" + } + ] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_elasticache_global_replication_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_elasticache_parameter_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_elasticache_replication_group", + "name": "global", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_elasticache_subnet_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [ + { + "index_key": 0, + "schema_version": 0, + "attributes": { + "arn": "arn:aws:elasticache:eu-north-1:597936860210:subnetgroup:aca-voting-app-valkey", + "description": "ElastiCache subnet group", + "id": "aca-voting-app-valkey", + "name": "aca-voting-app-valkey", + "subnet_ids": [ + "subnet-098835d1205ea3fd4", + "subnet-0caa8bd66179f08a2" + ], + "tags": { + "Name": "aca-voting-app-valkey", + "terraform-aws-modules": "elasticache" + }, + "tags_all": { + "Application": "aca-voting-app", + "Environment": "production", + "ManagedBy": "Terraform", + "Name": "aca-voting-app-valkey", + "terraform-aws-modules": "elasticache" + }, + "vpc_id": "vpc-0b0fce02ab20d23a4" + }, + "sensitive_attributes": [ + [ + { + "type": "get_attr", + "value": "subnet_ids" + } + ] + ], + "identity_schema_version": 0, + "private": "bnVsbA==", + "dependencies": [ + "data.aws_ssm_parameter.private_subnet_ids" + ], + "create_before_destroy": true + } + ] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_security_group", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_vpc_security_group_egress_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "aws_vpc_security_group_ingress_rule", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]", + "instances": [] + }, + { + "module": "module.valkey", + "mode": "managed", + "type": "random_id", + "name": "this", + "provider": "provider[\"registry.terraform.io/hashicorp/random\"]", + "instances": [] + } + ], + "check_results": null +} diff --git a/terraform/graph.dot b/terraform/graph.dot new file mode 100644 index 0000000000..8e7ab30268 --- /dev/null +++ b/terraform/graph.dot @@ -0,0 +1,63 @@ +digraph G { + compound = "true" + newrank = "true" + subgraph "root" { + "[root] data.aws_route53_zone.primary (expand)" [label = "data.aws_route53_zone.primary", shape = "box"] + "[root] data.aws_ssm_parameter.vpc_id (expand)" [label = "data.aws_ssm_parameter.vpc_id", shape = "box"] + "[root] data.aws_subnets.private (expand)" [label = "data.aws_subnets.private", shape = "box"] + "[root] data.aws_subnets.public (expand)" [label = "data.aws_subnets.public", shape = "box"] + "[root] data.aws_vpc.shared (expand)" [label = "data.aws_vpc.shared", shape = "box"] + "[root] data.terraform_remote_state.common (expand)" [label = "data.terraform_remote_state.common", shape = "box"] + + "[root] module.alb" [label = "module.alb", shape = "component"] + "[root] module.aurora" [label = "module.aurora", shape = "component"] + "[root] module.ecr_result" [label = "module.ecr_result", shape = "component"] + "[root] module.ecr_vote" [label = "module.ecr_vote", shape = "component"] + "[root] module.ecr_worker" [label = "module.ecr_worker", shape = "component"] + "[root] module.ecs" [label = "module.ecs", shape = "component"] + + "[root] aws_elasticache_replication_group.valkey (expand)" [label = "aws_elasticache_replication_group.valkey", shape = "box"] + "[root] aws_elasticache_subnet_group.valkey (expand)" [label = "aws_elasticache_subnet_group.valkey", shape = "box"] + "[root] aws_security_group.vote_task (expand)" [label = "aws_security_group.vote_task", shape = "box"] + "[root] aws_security_group.result_task (expand)" [label = "aws_security_group.result_task", shape = "box"] + "[root] aws_security_group.worker_task (expand)" [label = "aws_security_group.worker_task", shape = "box"] + "[root] aws_security_group.valkey (expand)" [label = "aws_security_group.valkey", shape = "box"] + "[root] aws_secretsmanager_secret.valkey (expand)" [label = "aws_secretsmanager_secret.valkey", shape = "box"] + "[root] aws_secretsmanager_secret.postgres (expand)" [label = "aws_secretsmanager_secret.postgres", shape = "box"] + "[root] aws_ecs_task_definition.vote (expand)" [label = "aws_ecs_task_definition.vote", shape = "box"] + "[root] aws_ecs_task_definition.result (expand)" [label = "aws_ecs_task_definition.result", shape = "box"] + "[root] aws_ecs_task_definition.worker (expand)" [label = "aws_ecs_task_definition.worker", shape = "box"] + "[root] aws_ecs_service.vote (expand)" [label = "aws_ecs_service.vote", shape = "box"] + "[root] aws_ecs_service.result (expand)" [label = "aws_ecs_service.result", shape = "box"] + "[root] aws_ecs_service.worker (expand)" [label = "aws_ecs_service.worker", shape = "box"] + "[root] aws_route53_record.vote (expand)" [label = "aws_route53_record.vote", shape = "box"] + "[root] aws_route53_record.result (expand)" [label = "aws_route53_record.result", shape = "box"] + + # Edges + "[root] data.aws_vpc.shared (expand)" -> "[root] data.aws_ssm_parameter.vpc_id (expand)" + "[root] data.aws_subnets.public (expand)" -> "[root] data.aws_vpc.shared (expand)" + "[root] data.aws_subnets.private (expand)" -> "[root] data.aws_vpc.shared (expand)" + + "[root] aws_security_group.vote_task (expand)" -> "[root] module.alb" + "[root] aws_security_group.result_task (expand)" -> "[root] module.alb" + "[root] aws_security_group.valkey (expand)" -> "[root] aws_security_group.vote_task (expand)" + "[root] aws_security_group.valkey (expand)" -> "[root] aws_security_group.worker_task (expand)" + + "[root] module.aurora" -> "[root] aws_security_group.result_task (expand)" + "[root] module.aurora" -> "[root] aws_security_group.worker_task (expand)" + "[root] aws_elasticache_replication_group.valkey (expand)" -> "[root] aws_security_group.valkey (expand)" + "[root] aws_elasticache_replication_group.valkey (expand)" -> "[root] aws_elasticache_subnet_group.valkey (expand)" + + "[root] aws_ecs_service.vote (expand)" -> "[root] aws_ecs_task_definition.vote (expand)" + "[root] aws_ecs_service.vote (expand)" -> "[root] aws_security_group.vote_task (expand)" + "[root] aws_ecs_service.result (expand)" -> "[root] aws_ecs_task_definition.result (expand)" + "[root] aws_ecs_service.result (expand)" -> "[root] aws_security_group.result_task (expand)" + "[root] aws_ecs_service.worker (expand)" -> "[root] aws_ecs_task_definition.worker (expand)" + "[root] aws_ecs_service.worker (expand)" -> "[root] aws_security_group.worker_task (expand)" + + "[root] aws_route53_record.vote (expand)" -> "[root] module.alb" + "[root] aws_route53_record.vote (expand)" -> "[root] data.aws_route53_zone.primary (expand)" + "[root] aws_route53_record.result (expand)" -> "[root] module.alb" + "[root] aws_route53_record.result (expand)" -> "[root] data.aws_route53_zone.primary (expand)" + } +} diff --git a/terraform/iam.tf b/terraform/iam.tf new file mode 100644 index 0000000000..04426cbedb --- /dev/null +++ b/terraform/iam.tf @@ -0,0 +1,67 @@ +# ECS Execution Role +resource "aws_iam_role" "ecs_execution_role" { + name = "${var.app_name}-ecs-execution-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "ecs_execution_role_policy" { + role = aws_iam_role.ecs_execution_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} + +# Policy allowing ECS Execution Role to fetch secrets from AWS Secrets Manager +resource "aws_iam_policy" "ecs_secrets_policy" { + name = "${var.app_name}-ecs-secrets-policy" + description = "Allows ECS Execution Role to fetch secrets from AWS Secrets Manager" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "secretsmanager:GetSecretValue" + ] + Resource = [ + module.secrets_manager_valkey.secret_arn, + module.secrets_manager_postgres.secret_arn + ] + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "ecs_execution_secrets" { + role = aws_iam_role.ecs_execution_role.name + policy_arn = aws_iam_policy.ecs_secrets_policy.arn +} + +# ECS Task Role +resource "aws_iam_role" "ecs_task_role" { + name = "${var.app_name}-ecs-task-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + } + ] + }) +} diff --git a/terraform/logs.tf b/terraform/logs.tf new file mode 100644 index 0000000000..8068e0410c --- /dev/null +++ b/terraform/logs.tf @@ -0,0 +1,20 @@ +# CloudWatch Log Groups for Container Logging +resource "aws_cloudwatch_log_group" "valkey" { + name = "/ecs/${var.app_name}/valkey" + retention_in_days = 7 +} + +resource "aws_cloudwatch_log_group" "vote" { + name = "/ecs/${var.app_name}/vote" + retention_in_days = 7 +} + +resource "aws_cloudwatch_log_group" "result" { + name = "/ecs/${var.app_name}/result" + retention_in_days = 7 +} + +resource "aws_cloudwatch_log_group" "worker" { + name = "/ecs/${var.app_name}/worker" + retention_in_days = 7 +} diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000000..a2d7bd9049 --- /dev/null +++ b/terraform/outputs.tf @@ -0,0 +1,112 @@ +output "shared_vpc_id" { + description = "The ID of the shared VPC reused from aca-terraform-common" + value = local.vpc_id + sensitive = true +} + +output "public_subnet_ids" { + description = "Public subnet IDs used by ALB" + value = local.public_subnet_ids + sensitive = true +} + +output "private_subnet_ids" { + description = "Private subnet IDs used by ECS Tasks, Aurora, and ElastiCache" + value = local.private_subnet_ids + sensitive = true +} + +output "alb_dns_name" { + description = "DNS name of the Application Load Balancer" + value = module.alb.dns_name +} + +output "vote_url" { + description = "HTTP redirect URL for vote web app" + value = "http://${module.alb.dns_name}" +} + +output "result_url" { + description = "HTTPS URL to access result app directly on port 8443" + value = "https://${module.alb.dns_name}:8443" +} + +output "vote_domain_url" { + description = "Secure HTTPS domain URL for vote app" + value = "https://${var.vote_subdomain}.${var.domain_name}" +} + +output "result_domain_url" { + description = "Secure HTTPS domain URL for result app" + value = "https://${var.result_subdomain}.${var.domain_name}" +} + +output "acm_certificate_arn" { + description = "ARN of the validated ACM Certificate" + value = aws_acm_certificate_validation.cert.certificate_arn +} + +output "aurora_cluster_endpoint" { + description = "Aurora Serverless v2 PostgreSQL cluster writer endpoint" + value = module.aurora.cluster_endpoint +} + +output "elasticache_valkey_endpoint" { + description = "Primary endpoint for AWS ElastiCache Valkey" + value = module.valkey.replication_group_primary_endpoint_address +} + +output "secrets_manager_valkey_arn" { + description = "Secrets Manager ARN storing Valkey credentials" + value = module.secrets_manager_valkey.secret_arn +} + +output "secrets_manager_postgres_arn" { + description = "Secrets Manager ARN storing PostgreSQL credentials" + value = module.secrets_manager_postgres.secret_arn +} + +output "ecr_repository_vote_url" { + description = "URL of the ECR repository for the vote service" + value = module.ecr_vote.repository_url +} + +output "ecr_repository_result_url" { + description = "URL of the ECR repository for the result service" + value = module.ecr_result.repository_url +} + +output "ecr_repository_worker_url" { + description = "URL of the ECR repository for the worker service" + value = module.ecr_worker.repository_url +} + +output "ecs_cluster_name" { + description = "Name of the ECS Cluster" + value = module.ecs.cluster_name +} + +output "ecs_service_vote_name" { + description = "Name of the vote ECS Service" + value = aws_ecs_service.vote.name +} + +output "ecs_service_result_name" { + description = "Name of the result ECS Service" + value = aws_ecs_service.result.name +} + +output "ecs_service_worker_name" { + description = "Name of the worker ECS Service" + value = aws_ecs_service.worker.name +} + +output "cloudwatch_dashboard_name" { + description = "Name of the CloudWatch Custom Dashboard" + value = aws_cloudwatch_dashboard.main.dashboard_name +} + +output "cloudwatch_dashboard_url" { + description = "Direct AWS Console link to the CloudWatch Custom Dashboard" + value = "https://${var.aws_region}.console.aws.amazon.com/cloudwatch/home?region=${var.aws_region}#dashboards:name=${aws_cloudwatch_dashboard.main.dashboard_name}" +} diff --git a/terraform/route53.tf b/terraform/route53.tf new file mode 100644 index 0000000000..147117b0ad --- /dev/null +++ b/terraform/route53.tf @@ -0,0 +1,25 @@ +# Route 53 DNS Record for Vote Web App +resource "aws_route53_record" "vote" { + zone_id = local.route53_zone_id + name = "${var.vote_subdomain}.${var.domain_name}" + type = "A" + + alias { + name = module.alb.dns_name + zone_id = module.alb.zone_id + evaluate_target_health = true + } +} + +# Route 53 DNS Record for Result Web App +resource "aws_route53_record" "result" { + zone_id = local.route53_zone_id + name = "${var.result_subdomain}.${var.domain_name}" + type = "A" + + alias { + name = module.alb.dns_name + zone_id = module.alb.zone_id + evaluate_target_health = true + } +} diff --git a/terraform/secrets.tf b/terraform/secrets.tf new file mode 100644 index 0000000000..9d518462cf --- /dev/null +++ b/terraform/secrets.tf @@ -0,0 +1,52 @@ +# Random Passwords for Valkey and PostgreSQL +resource "random_password" "valkey_password" { + length = 16 + special = false +} + +resource "random_password" "postgres_password" { + length = 16 + special = false +} + +# ========================================== +# SECRETS MANAGER FOR VALKEY (Community Module) +# ========================================== +module "secrets_manager_valkey" { + source = "terraform-aws-modules/secrets-manager/aws" + version = "~> 1.1" + + name = "${var.app_name}-valkey-credentials" + description = "Valkey RBAC user and password credentials for ${var.app_name}" + recovery_window_in_days = 0 + + secret_string = jsonencode({ + username = var.valkey_username + password = random_password.valkey_password.result + }) + + tags = { + Name = "${var.app_name}-valkey-credentials" + } +} + +# ========================================== +# SECRETS MANAGER FOR POSTGRES (Community Module) +# ========================================== +module "secrets_manager_postgres" { + source = "terraform-aws-modules/secrets-manager/aws" + version = "~> 1.1" + + name = "${var.app_name}-postgres-credentials" + description = "PostgreSQL credentials for ${var.app_name}" + recovery_window_in_days = 0 + + secret_string = jsonencode({ + username = var.aurora_master_username + password = random_password.postgres_password.result + }) + + tags = { + Name = "${var.app_name}-postgres-credentials" + } +} diff --git a/terraform/sg.tf b/terraform/sg.tf new file mode 100644 index 0000000000..54d64c9eea --- /dev/null +++ b/terraform/sg.tf @@ -0,0 +1,111 @@ +# ========================================== +# DEDICATED SECURITY GROUPS PER ECS TASK +# ========================================== + +# 1. Vote Task Security Group +resource "aws_security_group" "vote_task" { + name = "${var.app_name}-vote-task-sg" + description = "Allow inbound traffic from ALB to Vote service and outbound access" + vpc_id = local.vpc_id + + ingress { + description = "HTTP inbound traffic from ALB" + from_port = 80 + to_port = 80 + protocol = "tcp" + security_groups = [module.alb.security_group_id] + } + + egress { + description = "Allow all outbound traffic for Valkey and AWS API access" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.app_name}-vote-task-sg" + } +} + +# 2. Result Task Security Group +resource "aws_security_group" "result_task" { + name = "${var.app_name}-result-task-sg" + description = "Allow inbound traffic from ALB to Result service and outbound access" + vpc_id = local.vpc_id + + ingress { + description = "HTTP inbound traffic from ALB" + from_port = 80 + to_port = 80 + protocol = "tcp" + security_groups = [module.alb.security_group_id] + } + + egress { + description = "Allow all outbound traffic for Aurora PostgreSQL and AWS API access" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.app_name}-result-task-sg" + } +} + +# 3. Worker Task Security Group (No Inbound Required) +resource "aws_security_group" "worker_task" { + name = "${var.app_name}-worker-task-sg" + description = "Background worker service security group (No inbound access required)" + vpc_id = local.vpc_id + + egress { + description = "Allow all outbound traffic for Valkey, Aurora PostgreSQL, and AWS API access" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.app_name}-worker-task-sg" + } +} + +# 4. ElastiCache Valkey Security Group +resource "aws_security_group" "valkey" { + name = "${var.app_name}-valkey-sg" + description = "Security group for AWS ElastiCache Valkey (Allows Vote and Worker tasks)" + vpc_id = local.vpc_id + + ingress { + description = "Valkey traffic from Vote task" + from_port = 6379 + to_port = 6379 + protocol = "tcp" + security_groups = [aws_security_group.vote_task.id] + } + + ingress { + description = "Valkey traffic from Worker task" + from_port = 6379 + to_port = 6379 + protocol = "tcp" + security_groups = [aws_security_group.worker_task.id] + } + + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.app_name}-valkey-sg" + } +} diff --git a/terraform/terraform.tfvars.example b/terraform/terraform.tfvars.example new file mode 100644 index 0000000000..0869636156 --- /dev/null +++ b/terraform/terraform.tfvars.example @@ -0,0 +1,27 @@ +aws_region = "eu-north-1" +app_name = "aca-voting-app" +environment = "production" + +common_state_bucket = "tfstate-597936860210-eu-north-1-an" +common_state_key = "aca-terraform-common/terraform.tfstate" +common_state_region = "eu-north-1" + +domain_name = "sergey.c-loud.am" +vote_subdomain = "vote" +result_subdomain = "result" + +vote_image_tag = "latest" +result_image_tag = "latest" +worker_image_tag = "latest" + +container_cpu = 256 +container_memory = 512 + +vote_desired_count = 1 +result_desired_count = 1 +worker_desired_count = 1 + +redis_password = "redispassword" +postgres_db = "postgres" +postgres_user = "postgres" +postgres_password = "postgres" diff --git a/terraform/valkey.tf b/terraform/valkey.tf new file mode 100644 index 0000000000..5d1fc81df8 --- /dev/null +++ b/terraform/valkey.tf @@ -0,0 +1,67 @@ +# Default ElastiCache User required for RBAC User Group +resource "aws_elasticache_user" "default" { + user_id = "${var.app_name}-default-user" + user_name = "default" + engine = "valkey" + passwords = [random_password.valkey_password.result] + access_string = "on ~* +@all" + + tags = { + Name = "${var.app_name}-default-user" + } +} + +# Application Valkey RBAC User +resource "aws_elasticache_user" "valkey_user" { + user_id = "${var.app_name}-valkey-user" + user_name = var.valkey_username + engine = "valkey" + passwords = [random_password.valkey_password.result] + access_string = "on ~* +@all" + + tags = { + Name = "${var.app_name}-valkey-user" + } +} + +# ElastiCache RBAC User Group for Valkey +resource "aws_elasticache_user_group" "valkey" { + engine = "valkey" + user_group_id = "${var.app_name}-valkey-ug" + user_ids = [aws_elasticache_user.default.user_id, aws_elasticache_user.valkey_user.user_id] + + tags = { + Name = "${var.app_name}-valkey-ug" + } +} + +# AWS ElastiCache Valkey Cluster using AWS Community Module +module "valkey" { + source = "terraform-aws-modules/elasticache/aws" + version = "~> 1.3" + + cluster_id = "${var.app_name}-valkey" + create_cluster = false + create_replication_group = true + replication_group_id = "${var.app_name}-valkey" + description = "AWS ElastiCache Valkey cluster for ${var.app_name}" + + engine = "valkey" + engine_version = "7.2" + node_type = "cache.t4g.micro" + num_cache_clusters = 1 + port = 6379 + parameter_group_name = "default.valkey7" + + vpc_id = local.vpc_id + subnet_ids = local.private_subnet_ids + create_subnet_group = true + create_security_group = false + security_group_ids = [aws_security_group.valkey.id] + user_group_ids = [aws_elasticache_user_group.valkey.user_group_id] + transit_encryption_enabled = true + + tags = { + Name = "${var.app_name}-valkey" + } +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000000..a460a3b978 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,171 @@ +variable "aws_region" { + description = "AWS region for resources (matches aca-terraform-common)" + type = string + default = "eu-north-1" +} + +variable "app_name" { + description = "Application name identifier" + type = string + default = "aca-voting-app" +} + +variable "environment" { + description = "Environment name (e.g. production, staging, dev)" + type = string + default = "production" +} + +# ========================================== +# SHARED RESOURCES CONFIGURATION +# ========================================== + +variable "common_environment" { + description = "Environment identifier of common base infrastructure (matches aca-terraform-common environment)" + type = string + default = "core" +} + +variable "domain_name" { + description = "Domain name for Route 53 zone hosted in aca-terraform-common" + type = string + default = "sergey.c-loud.am" +} + +variable "vote_subdomain" { + description = "Subdomain prefix for vote web app" + type = string + default = "vote" +} + +variable "result_subdomain" { + description = "Subdomain prefix for result web app" + type = string + default = "result" +} + +# ========================================== +# APP CONTAINER CONFIGURATION +# ========================================== + +variable "vote_image_tag" { + description = "Image tag to deploy for the vote service" + type = string + default = "latest" +} + +variable "result_image_tag" { + description = "Image tag to deploy for the result service" + type = string + default = "latest" +} + +variable "worker_image_tag" { + description = "Image tag to deploy for the worker service" + type = string + default = "latest" +} + +variable "container_cpu" { + description = "CPU unit allocation for each ECS container task" + type = number + default = 256 +} + +variable "container_memory" { + description = "Memory allocation (MB) for each ECS container task" + type = number + default = 512 +} + +variable "vote_desired_count" { + description = "Number of desired instances for the vote service" + type = number + default = 1 +} + +variable "result_desired_count" { + description = "Number of desired instances for the result service" + type = number + default = 1 +} + +variable "worker_desired_count" { + description = "Number of desired instances for the worker service" + type = number + default = 1 +} + +# ========================================== +# ECS AUTO SCALING CONFIGURATION +# ========================================== + +variable "ecs_max_capacity" { + description = "Maximum number of tasks to scale up to" + type = number + default = 2 +} + +variable "ecs_cpu_target_value" { + description = "Target average CPU utilization percentage for scaling" + type = number + default = 70.0 +} + +variable "ecs_alb_request_target_value" { + description = "Target ALB request count per target for scaling" + type = number + default = 1000.0 +} + +# ========================================== +# VALKEY (REDIS FORK) CONFIGURATION +# ========================================== + +variable "valkey_username" { + description = "Username for Valkey RBAC authentication" + type = string + default = "valkeyuser" +} + +variable "valkey_password" { + description = "Password for Valkey in-memory data store" + type = string + default = "valkeypassword" + sensitive = true +} + +# ========================================== +# AURORA SERVERLESS POSTGRES CONFIGURATION +# ========================================== + +variable "aurora_db_name" { + description = "Database name for Aurora Serverless PostgreSQL" + type = string + default = "postgres" +} + +variable "aurora_master_username" { + description = "Master username for Aurora Serverless PostgreSQL" + type = string + default = "postgres" +} + +variable "aurora_master_password" { + description = "Master password for Aurora Serverless PostgreSQL" + type = string + default = "postgrespassword" + sensitive = true +} + +variable "aurora_min_capacity" { + description = "Minimum ACU for Aurora Serverless v2 (cost-optimized minimum)" + type = number + default = 0.5 +} + +variable "aurora_max_capacity" { + description = "Maximum ACU for Aurora Serverless v2 (cost-optimized maximum)" + type = number + default = 1.0 +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 0000000000..a4c4a8dca0 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,25 @@ +terraform { + required_version = ">= 1.0.0" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "aws" { + region = var.aws_region + + default_tags { + tags = { + Application = var.app_name + Environment = var.environment + ManagedBy = "Terraform" + } + } +} diff --git a/vote/app.py b/vote/app.py index 596546612a..c86e51a203 100644 --- a/vote/app.py +++ b/vote/app.py @@ -1,5 +1,6 @@ from flask import Flask, render_template, request, make_response, g from redis import Redis +import ssl import os import socket import random @@ -18,7 +19,27 @@ def get_redis(): if not hasattr(g, 'redis'): - g.redis = Redis(host="redis", db=0, socket_timeout=5) + redis_host = os.getenv('REDIS_HOST', 'redis') + redis_port = int(os.getenv('REDIS_PORT', 6379)) + # Read username and password from environment variables + redis_username = os.getenv('REDIS_USERNAME', 'default') + redis_password = os.getenv('REDIS_PASSWORD', None) + use_ssl = os.getenv('REDIS_SSL', 'false').lower() in ('true', '1', 't') + + ssl_cert_reqs = None + if use_ssl: + # ElastiCache in-transit encryption requires TLS + ssl_cert_reqs = ssl.CERT_REQUIRED + + g.redis = Redis( + host=redis_host, + port=redis_port, + password=redis_password, + ssl=use_ssl, + ssl_cert_reqs=ssl_cert_reqs, + db=0, + socket_timeout=5 + ) return g.redis @app.route("/", methods=['POST','GET']) diff --git a/vote/requirements.txt b/vote/requirements.txt index 430bfdcd89..84902410b8 100644 --- a/vote/requirements.txt +++ b/vote/requirements.txt @@ -1,3 +1,3 @@ Flask -Redis +redis>=4.0.0 gunicorn diff --git a/worker/Dockerfile b/worker/Dockerfile index a3f92d7e94..048dd8ceb4 100644 --- a/worker/Dockerfile +++ b/worker/Dockerfile @@ -1,27 +1,10 @@ -# because of dotnet, we always build on amd64, and target platforms in cli -# dotnet doesn't support QEMU for building or running. -# (errors common in arm/v7 32bit) https://github.com/dotnet/dotnet-docker/issues/1537 -# https://hub.docker.com/_/microsoft-dotnet -# hadolint ignore=DL3029 -# to build for a different platform than your host, use --platform= -# for example, if you were on Intel (amd64) and wanted to build for ARM, you would use: -# docker buildx build --platform "linux/arm64/v8" . - -# build compiles the program for the builder's local platform -FROM --platform=${BUILDPLATFORM} mcr.microsoft.com/dotnet/sdk:7.0 AS build -ARG TARGETPLATFORM -ARG TARGETARCH -ARG BUILDPLATFORM -RUN echo "I am running on $BUILDPLATFORM, building for $TARGETPLATFORM" - +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build WORKDIR /source -COPY *.csproj . -RUN dotnet restore -a $TARGETARCH - -COPY . . -RUN dotnet publish -c release -o /app -a $TARGETARCH --self-contained false --no-restore +COPY *.csproj ./ +RUN dotnet restore +COPY . ./ +RUN dotnet publish -c Release -o /app -# app image FROM mcr.microsoft.com/dotnet/runtime:7.0 WORKDIR /app COPY --from=build /app . diff --git a/worker/Program.cs b/worker/Program.cs index 9b5fb74d1a..fcf45effbb 100644 --- a/worker/Program.cs +++ b/worker/Program.cs @@ -1,7 +1,6 @@ using System; using System.Data.Common; using System.Linq; -using System.Net; using System.Net.Sockets; using System.Threading; using Newtonsoft.Json; @@ -16,46 +15,35 @@ public static int Main(string[] args) { try { - var pgsql = OpenDbConnection("Server=db;Username=postgres;Password=postgres;"); - var redisConn = OpenRedisConnection("redis"); + var pgsql = OpenDbConnection(); + var redisConn = OpenRedisConnection(); var redis = redisConn.GetDatabase(); - // Keep alive is not implemented in Npgsql yet. This workaround was recommended: - // https://github.com/npgsql/npgsql/issues/1214#issuecomment-235828359 - var keepAliveCommand = pgsql.CreateCommand(); - keepAliveCommand.CommandText = "SELECT 1"; - + // Keep definition of command and sleep alive var definition = new { vote = "", voter_id = "" }; + while (true) { - // Slow down to prevent CPU spike, only query each 100ms - Thread.Sleep(100); - - // Reconnect redis if down - if (redisConn == null || !redisConn.IsConnected) { - Console.WriteLine("Reconnecting Redis"); - redisConn = OpenRedisConnection("redis"); - redis = redisConn.GetDatabase(); + // Slow down if not connected to database or redis + if (pgsql.State != System.Data.ConnectionState.Open) + { + Console.Error.WriteLine("Reconnecting to DB"); + pgsql = OpenDbConnection(); } - string json = redis.ListLeftPopAsync("votes").Result; + + string json = redis.ListRightPopLeftPush("votes", "processing"); if (json != null) { var vote = JsonConvert.DeserializeAnonymousType(json, definition); Console.WriteLine($"Processing vote for '{vote.vote}' by '{vote.voter_id}'"); - // Reconnect DB if down - if (!pgsql.State.Equals(System.Data.ConnectionState.Open)) - { - Console.WriteLine("Reconnecting DB"); - pgsql = OpenDbConnection("Server=db;Username=postgres;Password=postgres;"); - } - else - { // Normal +1 vote requested - UpdateVote(pgsql, vote.voter_id, vote.vote); - } + + // Execute update or insert + UpdateVote(pgsql, vote.voter_id, vote.vote); + redis.ListRemove("processing", json); } else { - keepAliveCommand.ExecuteNonQuery(); + Thread.Sleep(100); } } } @@ -66,69 +54,132 @@ public static int Main(string[] args) } } - private static NpgsqlConnection OpenDbConnection(string connectionString) + private static NpgsqlConnection OpenDbConnection() { - NpgsqlConnection connection; + NpgsqlConnection connection = null; - while (true) + while (connection == null) { try { - connection = new NpgsqlConnection(connectionString); + var host = Environment.GetEnvironmentVariable("POSTGRES_HOST") ?? "db"; + var port = int.TryParse(Environment.GetEnvironmentVariable("POSTGRES_PORT"), out var p) ? p : 5432; + var user = Environment.GetEnvironmentVariable("POSTGRES_USER") ?? "postgres"; + var password = Environment.GetEnvironmentVariable("POSTGRES_PASSWORD") ?? "postgres"; + var database = Environment.GetEnvironmentVariable("POSTGRES_DB") ?? "postgres"; + + var sslEnv = Environment.GetEnvironmentVariable("POSTGRES_SSL"); + bool useSsl = sslEnv == "true" || sslEnv == "1" || (host != "db" && !string.IsNullOrEmpty(host)); + + var builder = new NpgsqlConnectionStringBuilder + { + Host = host, + Port = port, + Username = user, + Password = password, + Database = database, + SslMode = useSsl ? SslMode.Require : SslMode.Disable, + TrustServerCertificate = true, // Required for AWS RDS self-signed root CAs + Timeout = 15 + }; + + connection = new NpgsqlConnection(builder.ConnectionString); connection.Open(); - break; + + var command = connection.CreateCommand(); + command.CommandText = @"CREATE TABLE IF NOT EXISTS votes ( + id VARCHAR(255) NOT NULL UNIQUE, + vote VARCHAR(255) NOT NULL + )"; + command.ExecuteNonQuery(); } catch (SocketException) { - Console.Error.WriteLine("Waiting for db"); + Console.Error.WriteLine("Waiting for DB connection (SocketException)..."); Thread.Sleep(1000); } - catch (DbException) + catch (DbException ex) { - Console.Error.WriteLine("Waiting for db"); + Console.Error.WriteLine($"Waiting for DB connection (DbException): {ex.Message}"); Thread.Sleep(1000); } } - Console.Error.WriteLine("Connected to db"); - - var command = connection.CreateCommand(); - command.CommandText = @"CREATE TABLE IF NOT EXISTS votes ( - id VARCHAR(255) NOT NULL UNIQUE, - vote VARCHAR(255) NOT NULL - )"; - command.ExecuteNonQuery(); - + Console.WriteLine("Connected to PostgreSQL"); return connection; } - private static ConnectionMultiplexer OpenRedisConnection(string hostname) + private static ConnectionMultiplexer OpenRedisConnection() { - // Use IP address to workaround https://github.com/StackExchange/StackExchange.Redis/issues/410 - var ipAddress = GetIp(hostname); - Console.WriteLine($"Found redis at {ipAddress}"); + ConnectionMultiplexer connection = null; - while (true) + while (connection == null) { try { - Console.Error.WriteLine("Connecting to redis"); - return ConnectionMultiplexer.Connect(ipAddress); + var host = Environment.GetEnvironmentVariable("REDIS_HOST") ?? "redis"; + var port = int.TryParse(Environment.GetEnvironmentVariable("REDIS_PORT"), out var p) ? p : 6379; + var user = Environment.GetEnvironmentVariable("REDIS_USERNAME") ?? Environment.GetEnvironmentVariable("VALKEY_USER"); + var password = Environment.GetEnvironmentVariable("REDIS_PASSWORD") ?? Environment.GetEnvironmentVariable("VALKEY_PASSWORD"); + + + // Diagnostic logging + Console.WriteLine("----- REDIS/VALKEY CREDENTIAL DIAGNOSTICS -----"); + Console.WriteLine($"Host: {host}:{port}"); + Console.WriteLine($"Raw User: '{(user ?? "NULL")}' (Length: {user?.Length ?? 0})"); + Console.WriteLine($"Raw Password: '{(password ?? "NULL")}' (Length: {password?.Length ?? 0})"); + Console.WriteLine("------------------------------------------------"); + + user = user?.Trim(); + password = password?.Trim(); + + var sslEnv = Environment.GetEnvironmentVariable("REDIS_SSL"); + bool useSsl = sslEnv == "true" || sslEnv == "1" || (host != "redis" && !string.IsNullOrEmpty(host)); + + if (string.IsNullOrEmpty(password)) + { + throw new InvalidOperationException("REDIS_PASSWORD is null or empty."); + } + + var config = new ConfigurationOptions + { + EndPoints = { { host, port } }, + Password = password, + Ssl = useSsl, + SslHost = host, // Required: forces TLS SNI header to match the cluster DNS name + AbortOnConnectFail = true, // Fail fast so the catch block handles bad auth/timeout + ConnectTimeout = 10000, + SyncTimeout = 10000 + }; + +// If the user is empty or "default", StackExchange.Redis expects User to be null + if (!string.IsNullOrEmpty(user) && user != "default") + { + config.User = user; + } + else + { + config.User = null; + } + // AWS ElastiCache Serverless / Valkey TLS certs + if (useSsl) + { + config.CertificateValidation += (sender, certificate, chain, errors) => true; + } + + connection = ConnectionMultiplexer.Connect(config); } - catch (RedisConnectionException) + catch (Exception ex) { - Console.Error.WriteLine("Waiting for redis"); + Console.Error.WriteLine($"Waiting for Redis/Valkey: {ex.Message}"); + connection = null; Thread.Sleep(1000); } } - } - private static string GetIp(string hostname) - => Dns.GetHostEntryAsync(hostname) - .Result - .AddressList - .First(a => a.AddressFamily == AddressFamily.InterNetwork) - .ToString(); + Console.WriteLine("Connected to Redis/Valkey"); + return connection; + } private static void UpdateVote(NpgsqlConnection connection, string voterId, string vote) { @@ -136,8 +187,8 @@ private static void UpdateVote(NpgsqlConnection connection, string voterId, stri try { command.CommandText = "INSERT INTO votes (id, vote) VALUES (@id, @vote)"; - command.Parameters.AddWithValue("@id", voterId); - command.Parameters.AddWithValue("@vote", vote); + AddNamedParameter(command, "id", voterId); + AddNamedParameter(command, "vote", vote); command.ExecuteNonQuery(); } catch (DbException) @@ -145,10 +196,15 @@ private static void UpdateVote(NpgsqlConnection connection, string voterId, stri command.CommandText = "UPDATE votes SET vote = @vote WHERE id = @id"; command.ExecuteNonQuery(); } - finally - { - command.Dispose(); - } + } + + private static void AddNamedParameter(DbCommand command, string name, string value) + { + var parameter = command.CreateParameter(); + parameter.ParameterName = name; + parameter.Value = value; + command.Parameters.Add(parameter); } } -} \ No newline at end of file +} +