From d8784e8aba003f6ed9a34dcbde11a68454776105 Mon Sep 17 00:00:00 2001 From: Marcel Gleeson Date: Mon, 23 Dec 2024 13:31:27 +0100 Subject: [PATCH] ci: build and deploy signaling --- .github/workflows/backend.yml | 84 +++++++++ .github/workflows/ci.yml | 163 ------------------ .github/workflows/frontend.yml | 93 ++++++++++ .github/workflows/infrastructure.yml | 81 +++++++++ .gitignore | 1 + .terraformignore | 3 + backend/infrastructure/instance.tf | 110 ++++++++++++ backend/infrastructure/task/main.tf | 80 +++++++++ backend/infrastructure/variables.tf | 9 + backend/signaling/Dockerfile | 19 ++ backend/signaling/infrastructure/gateway.tf | 13 ++ backend/signaling/infrastructure/records.tf | 16 ++ backend/signaling/infrastructure/repo.tf | 57 ++++++ backend/signaling/infrastructure/task.tf | 9 + backend/signaling/infrastructure/variables.tf | 29 ++++ frontend/infrastructure/cdn.tf | 3 +- infrastructure/global/certificate.tf | 2 + infrastructure/global/outputs.tf | 1 + infrastructure/main.tf | 21 ++- infrastructure/outputs.tf | 10 +- infrastructure/variables.tf | 2 +- 21 files changed, 639 insertions(+), 167 deletions(-) create mode 100644 .github/workflows/backend.yml delete mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/frontend.yml create mode 100644 .github/workflows/infrastructure.yml create mode 100644 .terraformignore create mode 100644 backend/infrastructure/instance.tf create mode 100644 backend/infrastructure/task/main.tf create mode 100644 backend/infrastructure/variables.tf create mode 100644 backend/signaling/Dockerfile create mode 100644 backend/signaling/infrastructure/gateway.tf create mode 100644 backend/signaling/infrastructure/records.tf create mode 100644 backend/signaling/infrastructure/repo.tf create mode 100644 backend/signaling/infrastructure/task.tf create mode 100644 backend/signaling/infrastructure/variables.tf diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml new file mode 100644 index 0000000..d68b35c --- /dev/null +++ b/.github/workflows/backend.yml @@ -0,0 +1,84 @@ +name: "Backend" + +on: + push: + branches: [master, next, feat/yrs-signalling-reloaded] + paths: + - 'backend/**' + pull_request: + branches: [master, next] + paths: + - 'backend/**' + +jobs: + build: + name: "🧦 Build" + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend/signaling + steps: + - uses: actions/checkout@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build and export + uses: docker/build-push-action@v5 + with: + context: backend/signaling + tags: signaling:latest + outputs: type=docker,dest=/tmp/signaling.tar + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: signaling + path: /tmp/signaling.tar + + infrastructure: + needs: + - build + uses: ./.github/workflows/infrastructure.yml + secrets: inherit + + deploy: + name: "🏃‍♂️ Deploy" + runs-on: ubuntu-latest + if: github.event_name == 'push' + needs: + - build + - infrastructure + permissions: + id-token: write + contents: read + + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: us-east-1 + role-to-assume: ${{ needs.infrastructure.outputs.deploy_role }} + role-session-name: DeploySession + - name: Login to Amazon ECR + uses: aws-actions/amazon-ecr-login@v1 + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: signaling + path: /tmp + - name: Tag and upload image to ECR + env: + ECR_REPOSITORY: ${{ needs.infrastructure.outputs.signaling_ecr_url }} + IMAGE_TAG: ${{ github.sha }} + run: | + docker load --input /tmp/signaling.tar + docker tag signaling:latest $ECR_REPOSITORY:$IMAGE_TAG + docker tag signaling:latest $ECR_REPOSITORY:latest + docker push $ECR_REPOSITORY --all-tags + - name: Deploy image to EC2 + run: | + aws ssm send-command \ + --document-name "sobaka-signaling-sobaka-next-deploy" \ + --instance-id "${{ needs.infrastructure.outputs.instance_id }}" + +concurrency: + group: "${{ github.ref }}-backend" + cancel-in-progress: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 86a48c3..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,163 +0,0 @@ -name: "CI" - -on: - push: - branches: [master, next] - pull_request: - branches: [master, next] - -jobs: - build-frontend: - name: "🧦 Build Frontend" - runs-on: macos-latest - defaults: - run: - working-directory: frontend - steps: - - uses: actions/checkout@v3 - - name: Install latest nightly - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - components: rust-src - target: wasm32-unknown-unknown - override: true - # Cache somehow breaks wasm_opt - # https://github.com/Marcel-G/xtask-wasm/blob/main/src/wasm_opt.rs#L38 - # - uses: Swatinem/rust-cache@v2 - # with: - # workspaces: sobaka-dsp - - uses: actions/setup-node@v3 - with: - node-version: 16.14 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - run: npm ci - - run: npm run build - - uses: actions/upload-artifact@v3 - with: - name: frontend-build-output - path: frontend/build - - build-signaling: - name: "⛳️ Build Signaling" - runs-on: ubuntu-latest - defaults: - run: - working-directory: backend/signaling - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16.14 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - run: npm ci - - run: npm run build - - run: npm run pack - - uses: actions/upload-artifact@v3 - with: - name: signaling-build-output - path: backend/signaling/signaling.zip - - terraform: - name: "👟 Terraform" - runs-on: ubuntu-latest - if: github.event_name == 'push' - environment: - name: ${{ fromJSON('{"refs/heads/master":"production","refs/heads/next":"next"}')[github.ref] }} - url: ${{ fromJSON('{"refs/heads/master":"https://sobaka.marcelgleeson.com","refs/heads/next":"https://next.sobaka.marcelgleeson.com"}')[github.ref] }} - env: - TF_WORKSPACE: ${{ fromJSON('{"refs/heads/master":"sobaka-prod","refs/heads/next":"sobaka-next"}')[github.ref] }} - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - outputs: - deploy_bucket: ${{ steps.collect-outputs.outputs.deploy_bucket }} - deploy_role: ${{ steps.collect-outputs.outputs.deploy_role }} - cdn_distribution_id: ${{ steps.collect-outputs.outputs.cdn_distribution_id }} - needs: - - build-frontend - - build-signaling - defaults: - run: - working-directory: infrastructure - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v1 - with: - # https://github.com/hashicorp/setup-terraform/issues/20#issuecomment-679424701 - terraform_wrapper: false - # terraform_version: 0.13.0: - cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} - - - uses: actions/download-artifact@v2 - with: - name: signaling-build-output - # should contain `signaling.zip`` lambda src package - path: backend/signaling - - - name: Terraform Init - run: terraform init -upgrade - - - name: Terraform Plan - id: plan - run: terraform plan -no-color -var-file="$TF_WORKSPACE.tfvars" - continue-on-error: true - - - name: Terraform Plan Status - if: steps.plan.outcome == 'failure' - run: exit 1 - - - name: Terraform Apply - run: terraform apply -auto-approve -var-file="$TF_WORKSPACE.tfvars" - - - name: Terraform Outputs - id: collect-outputs - run: | - echo "deploy_bucket=$(terraform output -raw deploy_bucket)" >> $GITHUB_OUTPUT - echo "deploy_role=$(terraform output -raw deploy_role)" >> $GITHUB_OUTPUT - echo "cdn_distribution_id=$(terraform output -raw cdn_distribution_id)" >> $GITHUB_OUTPUT - - deploy: - name: "🏃‍♂️ Deploy" - runs-on: ubuntu-latest - if: github.event_name == 'push' - needs: - - terraform - permissions: - id-token: write - contents: read - - steps: - - uses: actions/download-artifact@v2 - with: - name: frontend-build-output - path: frontend/build - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v2 - with: - aws-region: us-east-1 - role-to-assume: ${{ needs.terraform.outputs.deploy_role }} - role-session-name: DeploySession - - name: "Copy files to S3" - run: | - aws s3 sync frontend/build s3://${{ needs.terraform.outputs.deploy_bucket }} \ - --metadata-directive REPLACE \ - --cache-control 'max-age=31104000' - - aws s3 cp frontend/build/index.html s3://${{ needs.terraform.outputs.deploy_bucket }} \ - --metadata-directive REPLACE \ - --cache-control 'max-age=3600' - - name: "Invalidate CloudFront Cache" - run: | - aws cloudfront create-invalidation \ - --distribution-id ${{ needs.terraform.outputs.cdn_distribution_id }} \ - --paths "/index.html" - - -concurrency: - group: ${{ github.ref }} - cancel-in-progress: true diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml new file mode 100644 index 0000000..82d78e4 --- /dev/null +++ b/.github/workflows/frontend.yml @@ -0,0 +1,93 @@ +name: "Frontend" + +on: + push: + branches: [master, next, feat/yrs-signalling-reloaded] + paths: + - 'frontend/**' + - 'sobaka-dsp/**' + pull_request: + branches: [master, next] + paths: + - 'frontend/**' + - 'sobaka-dsp/**' + +jobs: + build: + name: "🧦 Build" + runs-on: macos-latest + defaults: + run: + working-directory: frontend + steps: + - uses: actions/checkout@v3 + - name: Install latest nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: rust-src + target: wasm32-unknown-unknown + override: true + # Cache somehow breaks wasm_opt + # https://github.com/Marcel-G/xtask-wasm/blob/main/src/wasm_opt.rs#L38 + # - uses: Swatinem/rust-cache@v2 + # with: + # workspaces: sobaka-dsp + - uses: actions/setup-node@v3 + with: + node-version: 16.14 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - run: npm ci + - run: npm run build + - uses: actions/upload-artifact@v3 + with: + name: frontend-build-output + path: frontend/build + + infrastructure: + needs: + - build + uses: ./.github/workflows/infrastructure.yml + secrets: inherit + + deploy: + name: "🏃‍♂️ Deploy" + runs-on: ubuntu-latest + if: github.event_name == 'push' + needs: + - infrastructure + permissions: + id-token: write + contents: read + + steps: + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-region: us-east-1 + role-to-assume: ${{ needs.infrastructure.outputs.deploy_role }} + role-session-name: DeploySession + - uses: actions/download-artifact@v2 + with: + name: frontend-build-output + path: frontend/build + - name: "Deploy files to S3" + run: | + aws s3 sync frontend/build s3://${{ needs.infrastructure.outputs.deploy_bucket }} \ + --metadata-directive REPLACE \ + --cache-control 'max-age=31104000' + + aws s3 cp frontend/build/index.html s3://${{ needs.infrastructure.outputs.deploy_bucket }} \ + --metadata-directive REPLACE \ + --cache-control 'max-age=3600' + - name: "Invalidate CloudFront Cache" + run: | + aws cloudfront create-invalidation \ + --distribution-id ${{ needs.infrastructure.outputs.cdn_distribution_id }} \ + --paths "/index.html" + + +concurrency: + group: "${{ github.ref }}-frontend" + cancel-in-progress: true diff --git a/.github/workflows/infrastructure.yml b/.github/workflows/infrastructure.yml new file mode 100644 index 0000000..2fd1ebb --- /dev/null +++ b/.github/workflows/infrastructure.yml @@ -0,0 +1,81 @@ +name: "Infrastructure" +on: + workflow_call: + secrets: + TF_API_TOKEN: + required: true + outputs: + deploy_bucket: + description: "S3 bucket to deploy to" + value: ${{ jobs.infrastructure.outputs.deploy_bucket }} + deploy_role: + description: "IAM role to deploy with" + value: ${{ jobs.infrastructure.outputs.deploy_role }} + cdn_distribution_id: + description: "CloudFront distribution ID" + value: ${{ jobs.infrastructure.outputs.cdn_distribution_id }} + signaling_ecr_url: + description: "ECR repository for signaling" + value: ${{ jobs.infrastructure.outputs.signaling_ecr_url }} + instance_id: + description: "EC2 instance ID" + value: ${{ jobs.infrastructure.outputs.instance_id }} + +jobs: + infrastructure: + name: "👟 Terraform" + runs-on: ubuntu-latest + if: github.event_name == 'push' + environment: + name: next + url: https://next.sobaka.marcelgleeson.com + # name: ${{ fromJSON('{"refs/heads/master":"production","refs/heads/next":"next"}')[github.ref] }} + # url: ${{ fromJSON('{"refs/heads/master":"https://sobaka.marcelgleeson.com","refs/heads/next":"https://next.sobaka.marcelgleeson.com"}')[github.ref] }} + env: + # TF_WORKSPACE: ${{ fromJSON('{"refs/heads/master":"sobaka-prod","refs/heads/next":"sobaka-next"}')[github.ref] }} + TF_WORKSPACE: sobaka-next + outputs: + deploy_bucket: ${{ steps.collect-outputs.outputs.deploy_bucket }} + deploy_role: ${{ steps.collect-outputs.outputs.deploy_role }} + cdn_distribution_id: ${{ steps.collect-outputs.outputs.cdn_distribution_id }} + signaling_ecr_url: ${{ steps.collect-outputs.outputs.signaling_ecr_url }} + instance_id: ${{ steps.collect-outputs.outputs.instance_id }} + defaults: + run: + working-directory: infrastructure + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v1 + with: + # https://github.com/hashicorp/setup-terraform/issues/20#issuecomment-679424701 + terraform_wrapper: false + # Pinning to pre 1.6.0 version due to https://github.com/hashicorp/terraform/issues/33976 + terraform_version: 1.5.7 + cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} + + - name: Terraform Init + run: terraform init -upgrade + + - name: Terraform Plan + id: plan + run: terraform plan -no-color + continue-on-error: true + + - name: Terraform Plan Status + if: steps.plan.outcome == 'failure' + run: exit 1 + + - name: Terraform Apply + run: terraform apply -auto-approve + + - name: Terraform Outputs + id: collect-outputs + run: | + echo "deploy_bucket=$(terraform output -raw deploy_bucket)" >> $GITHUB_OUTPUT + echo "deploy_role=$(terraform output -raw deploy_role)" >> $GITHUB_OUTPUT + echo "cdn_distribution_id=$(terraform output -raw cdn_distribution_id)" >> $GITHUB_OUTPUT + echo "signaling_ecr_url=$(terraform output -raw signaling_ecr_url)" >> $GITHUB_OUTPUT + echo "instance_id=$(terraform output -raw instance_id)" >> $GITHUB_OUTPUT diff --git a/.gitignore b/.gitignore index 8f3993b..c869378 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ builds # Local .terraform directories **/.terraform* +!.terraformignore diff --git a/.terraformignore b/.terraformignore new file mode 100644 index 0000000..71c423f --- /dev/null +++ b/.terraformignore @@ -0,0 +1,3 @@ +** +!infrastructure/**/*.tf +!**/infrastructure/**/*.tf diff --git a/backend/infrastructure/instance.tf b/backend/infrastructure/instance.tf new file mode 100644 index 0000000..a6f5bc9 --- /dev/null +++ b/backend/infrastructure/instance.tf @@ -0,0 +1,110 @@ +locals { + name = var.name + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + user_data = <<-EOT + #!/bin/bash + sudo yum update -y + sudo yum install docker jq -y + sudo service docker start + sudo chkconfig docker on + sudo usermod -a -G docker ec2-user + newgrp docker + EOT +} + +data "aws_availability_zones" "available" {} + +module "instance" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "~> 4.0" + + name = "${local.name}-ec2" + + ami = data.aws_ami.amazon_linux.id + instance_type = "t3.micro" + subnet_id = element(module.vpc.public_subnets, 0) + vpc_security_group_ids = [module.security_group.security_group_id] + associate_public_ip_address = true + + create_iam_instance_profile = true + iam_role_description = "IAM role for EC2 instance" + iam_role_policies = { + AccessECRReadOnly = aws_iam_policy.ecr_login.arn + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + user_data_base64 = base64encode(local.user_data) + user_data_replace_on_change = true +} + +module "security_group" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "${local.name}-sg" + description = "Security group for example usage with EC2 instance" + vpc_id = module.vpc.vpc_id + + ingress_with_cidr_blocks = [ + { + from_port = 8080 + to_port = 8080 + protocol = "tcp" + description = "Allow HTTP/WebSocket inbound from API Gateway" + cidr_blocks = "0.0.0.0/0" + } + ] + + egress_rules = ["all-all"] +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 4.0" + + name = "${local.name}-vpc" + cidr = "10.0.0.0/16" + + azs = local.azs + private_subnets = ["10.0.141.0/24"] + public_subnets = ["10.0.142.0/24"] + + enable_nat_gateway = false +} + +data "aws_ami" "amazon_linux" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn2-ami-hvm-*-x86_64-gp2"] + } +} + +data "aws_iam_role" "deploy" { + name = var.global_deploy_role +} + +data "aws_iam_policy_document" "ecr_login" { + statement { + effect = "Allow" + actions = ["ecr:GetAuthorizationToken"] + resources = ["*"] + } +} + +resource "aws_iam_policy" "ecr_login" { + name = "${local.name}-ecr-login-policy" + policy = data.aws_iam_policy_document.ecr_login.json +} + +resource "aws_iam_role_policy_attachment" "deploy_ecr" { + role = data.aws_iam_role.deploy.name + policy_arn = aws_iam_policy.ecr_login.arn +} + +output "instance" { + value = module.instance +} diff --git a/backend/infrastructure/task/main.tf b/backend/infrastructure/task/main.tf new file mode 100644 index 0000000..2627abd --- /dev/null +++ b/backend/infrastructure/task/main.tf @@ -0,0 +1,80 @@ +variable "name" { + description = "Name of the instance" + type = string +} + +variable "repository_url" { + description = "URL of the Docker repository" + type = string +} + +variable "ports" { + description = "Ports to expose in the format 'host_port:container_port'" + type = list(string) +} + +variable "global_deploy_role" { + description = "Deployment role name" + type = string +} + +variable "instance" { + description = "Instance to deploy to" + type = any +} + +locals { + deploy_script = <<-EOT + sudo su ec2-user + aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${var.repository_url} + docker pull ${var.repository_url} + docker rm -f ${var.name} || true + docker run \ + --name ${var.name} \ + ${join(" ", [for port in var.ports : "-p ${port}"])} \ + -d ${var.repository_url}:latest + EOT +} + +resource "aws_ssm_document" "deploy" { + name = "${var.name}-deploy" + document_type = "Command" + + content = jsonencode({ + schemaVersion = "2.2", + description = "Starts a Docker container", + mainSteps = [{ + action = "aws:runShellScript", + name = "runShellScript", + inputs = { + runCommand = compact(split("\n", local.deploy_script)) + } + }] + }) +} + +data "aws_iam_role" "deploy" { + name = var.global_deploy_role +} + +data "aws_iam_policy_document" "deploy_ssm" { + statement { + effect = "Allow" + actions = ["ssm:SendCommand"] + resources = [ + resource.aws_ssm_document.deploy.arn, + var.instance.arn + ] + } +} + +resource "aws_iam_policy" "deploy_ssm" { + name = "${var.name}-deploy-ssm-policy" + policy = data.aws_iam_policy_document.deploy_ssm.json +} + +resource "aws_iam_role_policy_attachment" "deploy_ssm" { + role = data.aws_iam_role.deploy.name + policy_arn = aws_iam_policy.deploy_ssm.arn +} + diff --git a/backend/infrastructure/variables.tf b/backend/infrastructure/variables.tf new file mode 100644 index 0000000..ce9e0f3 --- /dev/null +++ b/backend/infrastructure/variables.tf @@ -0,0 +1,9 @@ +variable "name" { + description = "Resource name" + type = string +} + +variable "global_deploy_role" { + description = "Deployment role name" + type = string +} diff --git a/backend/signaling/Dockerfile b/backend/signaling/Dockerfile new file mode 100644 index 0000000..764e0ca --- /dev/null +++ b/backend/signaling/Dockerfile @@ -0,0 +1,19 @@ +FROM messense/rust-musl-cross:x86_64-musl AS builder + +WORKDIR /app + +COPY ./ . + +# remove the line below when switching to >=rust:1.70.0. sparse mode is planned to be the default in Rust 1.70.0 +ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse + +RUN cargo build \ + --target x86_64-unknown-linux-musl \ + --release + +FROM scratch +COPY --from=builder \ + /app/target/x86_64-unknown-linux-musl/release/signaling \ + /usr/local/bin/signaling + +CMD ["signaling"] diff --git a/backend/signaling/infrastructure/gateway.tf b/backend/signaling/infrastructure/gateway.tf new file mode 100644 index 0000000..5c225e7 --- /dev/null +++ b/backend/signaling/infrastructure/gateway.tf @@ -0,0 +1,13 @@ +module "gateway" { + source = "terraform-aws-modules/apigateway-v2/aws" + version = "~> 4.0" + + name = "${var.name}-websocket" + + domain_name = "${var.subdomain}.${var.domain_name}" + domain_name_certificate_arn = var.global_acm_certificate_arn + + protocol_type = "WEBSOCKET" + route_key = "$default" + target = "http://${var.instance.public_dns}:8080" +} diff --git a/backend/signaling/infrastructure/records.tf b/backend/signaling/infrastructure/records.tf new file mode 100644 index 0000000..d0ee6de --- /dev/null +++ b/backend/signaling/infrastructure/records.tf @@ -0,0 +1,16 @@ +module "route53" { + source = "terraform-aws-modules/route53/aws//modules/records" + version = "~> 2.0" + + zone_name = var.domain_name + records = [ + { + name = "${var.subdomain}.${var.domain_name}" + type = "A" + alias = { + name = module.gateway.apigatewayv2_domain_name_configuration[0].target_domain_name + zone_id = module.gateway.apigatewayv2_domain_name_configuration[0].hosted_zone_id + } + } + ] +} diff --git a/backend/signaling/infrastructure/repo.tf b/backend/signaling/infrastructure/repo.tf new file mode 100644 index 0000000..94ecf25 --- /dev/null +++ b/backend/signaling/infrastructure/repo.tf @@ -0,0 +1,57 @@ +module "container_image_ecr" { + source = "terraform-aws-modules/ecr/aws" + version = "~> 1.6" + + repository_name = "${var.name}-ecr" + + repository_read_write_access_arns = [data.aws_iam_role.deploy.arn] + repository_read_access_arns = [var.instance.iam_role_arn] + + repository_image_tag_mutability = "MUTABLE" + create_lifecycle_policy = true + repository_lifecycle_policy = jsonencode({ + rules = [ + { + rulePriority = 1, + description = "Keep last 3 images", + selection = { + tagStatus = "tagged", + tagPrefixList = ["v"], + countType = "imageCountMoreThan", + countNumber = 3 + }, + action = { + type = "expire" + } + } + ] + }) + + repository_force_delete = true +} + +data "aws_iam_role" "deploy" { + name = var.global_deploy_role +} + +data "aws_iam_policy_document" "ecr_login" { + statement { + effect = "Allow" + actions = ["ecr:GetAuthorizationToken"] + resources = ["*"] + } +} + +resource "aws_iam_policy" "ecr_login" { + name = "${var.name}-ecr-login-policy" + policy = data.aws_iam_policy_document.ecr_login.json +} + +resource "aws_iam_role_policy_attachment" "deploy_ecr" { + role = data.aws_iam_role.deploy.name + policy_arn = aws_iam_policy.ecr_login.arn +} + +output "ecr_url" { + value = module.container_image_ecr.repository_url +} diff --git a/backend/signaling/infrastructure/task.tf b/backend/signaling/infrastructure/task.tf new file mode 100644 index 0000000..58b6ca3 --- /dev/null +++ b/backend/signaling/infrastructure/task.tf @@ -0,0 +1,9 @@ +module "task" { + source = "../../infrastructure/task" + name = var.name + repository_url = module.container_image_ecr.repository_url + instance = var.instance + global_deploy_role = var.global_deploy_role + ports = ["8080:8080"] +} + diff --git a/backend/signaling/infrastructure/variables.tf b/backend/signaling/infrastructure/variables.tf new file mode 100644 index 0000000..cd36315 --- /dev/null +++ b/backend/signaling/infrastructure/variables.tf @@ -0,0 +1,29 @@ +variable "name" { + description = "Resource name" + type = string +} + +variable "subdomain" { + description = "Subdomain to use" + type = string +} + +variable "domain_name" { + description = "Root domain name to use" + type = string +} + +variable "global_deploy_role" { + description = "Deployment role name" + type = string +} + +variable "instance" { + description = "Instance to deploy to" + type = any +} + +variable "global_acm_certificate_arn" { + description = "Global ACM Certificate ARN" + type = string +} diff --git a/frontend/infrastructure/cdn.tf b/frontend/infrastructure/cdn.tf index 0aca4ce..a9fcc26 100644 --- a/frontend/infrastructure/cdn.tf +++ b/frontend/infrastructure/cdn.tf @@ -1,5 +1,6 @@ module "cdn" { - source = "terraform-aws-modules/cloudfront/aws" + source = "terraform-aws-modules/cloudfront/aws" + version = "~> 3.0" aliases = ["${var.subdomain}.${var.domain_name}"] diff --git a/infrastructure/global/certificate.tf b/infrastructure/global/certificate.tf index f14b34b..cdd8677 100644 --- a/infrastructure/global/certificate.tf +++ b/infrastructure/global/certificate.tf @@ -4,6 +4,8 @@ module "acm" { domain_name = "${var.subdomain}.${var.global_domain_zone}" zone_id = data.aws_route53_zone.main.zone_id + validation_method = "DNS" + subject_alternative_names = [ "*.${var.subdomain}.${var.global_domain_zone}", ] diff --git a/infrastructure/global/outputs.tf b/infrastructure/global/outputs.tf index 9f0de3a..787ad19 100644 --- a/infrastructure/global/outputs.tf +++ b/infrastructure/global/outputs.tf @@ -12,3 +12,4 @@ output "global_zone_id" { description = "Global zone ID" value = data.aws_route53_zone.main.zone_id } + diff --git a/infrastructure/main.tf b/infrastructure/main.tf index 0b48ee4..3de7ba2 100644 --- a/infrastructure/main.tf +++ b/infrastructure/main.tf @@ -2,7 +2,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.59.0" + version = "~> 5.30.0" } } @@ -40,6 +40,25 @@ module "global" { subdomain = var.subdomain } +module "backend" { + source = "../backend/infrastructure" + name = "sobaka-instance-${terraform.workspace}" + global_deploy_role = module.global.global_deploy_role.name +} + +module "signaling" { + source = "../backend/signaling/infrastructure" + + name = "sobaka-signaling-${terraform.workspace}" + global_acm_certificate_arn = module.global.global_acm_certificate_arn + global_deploy_role = module.global.global_deploy_role.name + + instance = module.backend.instance + + subdomain = "signaling.${var.subdomain}" + domain_name = var.domain_name +} + module "frontend" { source = "../frontend/infrastructure" diff --git a/infrastructure/outputs.tf b/infrastructure/outputs.tf index 8a1f890..3477fe5 100644 --- a/infrastructure/outputs.tf +++ b/infrastructure/outputs.tf @@ -11,4 +11,12 @@ output "cdn_distribution_id" { output "deploy_role" { description = "AWS role ARN to assume in order to make deployments" value = module.global.global_deploy_role.arn -} \ No newline at end of file +} + +output "signaling_ecr_url" { + value = module.signaling.ecr_url +} + +output "instance_id" { + value = module.backend.instance.id +} diff --git a/infrastructure/variables.tf b/infrastructure/variables.tf index f1f4450..895aef0 100644 --- a/infrastructure/variables.tf +++ b/infrastructure/variables.tf @@ -11,4 +11,4 @@ variable "subdomain" { variable "github_repo" { description = "Github repo name" type = string -} \ No newline at end of file +}