From 3b3d6b30a089f263155b5383fb5617e4a3584b1e Mon Sep 17 00:00:00 2001 From: Stefan Hattrell <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 14:39:08 +1100 Subject: [PATCH 01/12] deploy: container vars refactoring --- deploy/container/vars.yaml | 10 ++ deploy/github/dev.env | 16 ++- deploy/github/production.env | 15 +- deploy/github/staging.env | 13 +- deploy/tf/alb.tf | 2 +- deploy/tf/ecs.tf | 155 +++++++++------------ deploy/tf/get-parameters.tf | 16 --- deploy/tf/{vars-shared.tf => variables.tf} | 44 +++--- deploy/tf/vars-container.tf | 51 ------- deploy/tg/terragrunt.hcl | 22 ++- 10 files changed, 147 insertions(+), 197 deletions(-) create mode 100644 deploy/container/vars.yaml rename deploy/tf/{vars-shared.tf => variables.tf} (59%) delete mode 100644 deploy/tf/vars-container.tf diff --git a/deploy/container/vars.yaml b/deploy/container/vars.yaml new file mode 100644 index 0000000..f6baa9c --- /dev/null +++ b/deploy/container/vars.yaml @@ -0,0 +1,10 @@ +allowed_hosts: "*" +allowed_cidr_nets: "0.0.0.0/0" +django_secret_key: changeme +db_host: db_host +db_name: app_db +db_user: app_db +db_secret_name: /my/db/secret +db_secret_region: ap-southeast-2 +s3_storage_bucket_name: appbucket +s3_storage_bucket_region: ap-southeast-2 diff --git a/deploy/github/dev.env b/deploy/github/dev.env index 43f7c7d..09fc297 100644 --- a/deploy/github/dev.env +++ b/deploy/github/dev.env @@ -1,9 +1,15 @@ -ALB_PARAMETER_NAME=shared-alb-dev-sydney -APP_NAME=sample-django-app-mybranch +# general environment variables for Terragrunt +ALB_PARAMETER_NAME=my-alb-parameter +APP_NAME=sample-django-app AWS_ACCOUNT_ID=123456789012 AWS_REGION=ap-southeast-2 -ECR_PARAMETER_NAME=api ECR_REGISTRY=123456789012.dkr.ecr.ap-southeast-2.amazonaws.com ECR_REPOSITORY=api -ENVIRONMENT=mydev-stack -RDS_PARAMETER_NAME=db01/primary/development +ENVIRONMENT=development + +# container definition variables +DB_HOST=my-rds-db.endpoint +DB_SECRET_NAME=/rds/my-rds-db/secret +DB_SECRET_REGION=ap-southeast-2 +S3_STORAGE_BUCKET_NAME=my-app-bucket +S3_STORAGE_BUCKET_REGION=ap-southeast-2 diff --git a/deploy/github/production.env b/deploy/github/production.env index 9055ce2..74827f9 100644 --- a/deploy/github/production.env +++ b/deploy/github/production.env @@ -1,11 +1,18 @@ +# general environment variables for Terragrunt ALB_PARAMETER_NAME=shared-alb-devops-sydney APP_NAME=sample-django-app AWS_ACCOUNT_ID=450356697252 AWS_REGION=ap-southeast-2 -DB_NAME=api_prod -DB_USER=api_prod -ECR_PARAMETER_NAME=api ECR_REGISTRY=450356697252.dkr.ecr.ap-southeast-2.amazonaws.com ECR_REPOSITORY=api ENVIRONMENT=production -RDS_PARAMETER_NAME=stefan-db/primary/evaluation + +# container definition variables +ALLOWED_CIDR_NETS=10.32.48.0/20,10.32.64.0/20,10.32.80.0/20 +DB_HOST=stefan-db-rds-primary-evaluation.gamma.aodn.org.au +DB_NAME=api_prod +DB_SECRET_NAME=/rds/stefan-db/primary/evaluation/api +DB_SECRET_REGION=ap-southeast-2 +DB_USER=api_prod +S3_STORAGE_BUCKET_NAME=sample-django-app-production-450356697252 +S3_STORAGE_BUCKET_REGION=ap-southeast-2 diff --git a/deploy/github/staging.env b/deploy/github/staging.env index 57741e5..b9613b2 100644 --- a/deploy/github/staging.env +++ b/deploy/github/staging.env @@ -1,9 +1,18 @@ +# general environment variables for Terragrunt ALB_PARAMETER_NAME=shared-alb-devops-sydney APP_NAME=sample-django-app AWS_ACCOUNT_ID=450356697252 AWS_REGION=ap-southeast-2 -ECR_PARAMETER_NAME=api ECR_REGISTRY=450356697252.dkr.ecr.ap-southeast-2.amazonaws.com ECR_REPOSITORY=api ENVIRONMENT=staging -RDS_PARAMETER_NAME=stefan-db/primary/evaluation + +# container definition variables +ALLOWED_CIDR_NETS=10.32.48.0/20,10.32.64.0/20,10.32.80.0/20 +DB_HOST=stefan-db-rds-primary-evaluation.gamma.aodn.org.au +DB_NAME=api +DB_SECRET_NAME=/rds/stefan-db/primary/evaluation/api +DB_SECRET_REGION=ap-southeast-2 +DB_USER=api +S3_STORAGE_BUCKET_NAME=sample-django-app-staging-450356697252 +S3_STORAGE_BUCKET_REGION=ap-southeast-2 diff --git a/deploy/tf/alb.tf b/deploy/tf/alb.tf index 43aa9b2..e88b353 100644 --- a/deploy/tf/alb.tf +++ b/deploy/tf/alb.tf @@ -1,6 +1,6 @@ resource "aws_lb_target_group" "app" { name = "${var.app_name}-${var.environment}" - port = 80 + port = var.nginx_proxy ? var.proxy_port : var.app_port protocol = "HTTP" target_type = "ip" vpc_id = local.vpc_id diff --git a/deploy/tf/ecs.tf b/deploy/tf/ecs.tf index b2ea89a..53fab05 100644 --- a/deploy/tf/ecs.tf +++ b/deploy/tf/ecs.tf @@ -1,33 +1,68 @@ locals { - # set container definition variables with default fallback values from ssm if available - app_vars = { - allowed_hosts = var.allowed_hosts - allowed_cidr_nets = coalesce(var.allowed_cidr_nets, local.private_subnet_cidrs) - django_secret_key = var.django_secret_key - db_host = coalesce(var.db_host, local.rds_url) - db_name = var.db_name - db_user = var.db_user - db_secret_name = var.db_secret_name - db_secret_region = var.db_secret_region - s3_storage_bucket_name = coalesce( - var.s3_storage_bucket_name, - "sample-django-app-${local.bucket_suffix}" - ) - s3_storage_bucket_region = coalesce( - var.s3_storage_bucket_region, - data.aws_region.current.name - ) - } - nginx_vars = { app_host = "127.0.0.1" - app_port = 9000 - listen_port = var.container_port + app_port = var.app_port + listen_port = var.proxy_port } - app_container_vars = [for k, v in local.app_vars : { name = upper(k), value = v }] + app_container_vars = [for k, v in var.container_vars : { name = upper(k), value = v }] nginx_container_vars = [for k, v in local.nginx_vars : { name = upper(k), value = v }] - ecr_registry = split("/", local.ecr_repository_url)[0] + + container_definitions = var.nginx_proxy ? merge(local.app_container_definition, local.nginx_container_definition) : local.app_container_definition + app_container_definition = { + app = { + name = var.app_container_name + image = startswith(var.image, "sha256") ? "${var.ecr_registry}@${var.image}" : "${var.ecr_registry}:${var.image}" + health_check = { + command = ["CMD-SHELL", "uwsgi-is-ready --stats-socket /tmp/statsock > /dev/null 2>&1 || exit 1"] + } + readonly_root_filesystem = false + essential = true + memory_reservation = 256 + environment = local.app_container_vars + port_mappings = [ + { + name = var.app_container_name + containerPort = var.app_port + hostPort = var.app_port + } + ] + mount_points = [ + { + readOnly = false + containerPath = "/vol/web" + sourceVolume = "static" + } + ] + } + } + nginx_container_definition = { + nginx = { + name = "nginx" + image = "${var.ecr_registry}/nginx-proxy:latest" + health_check = { + command = ["CMD-SHELL", "curl -so /dev/null http://localhost/health || exit 1"] + } + readonly_root_filesystem = false + essential = true + memory_reservation = 256 + environment = local.nginx_container_vars + port_mappings = [ + { + name = "nginx" + containerPort = var.proxy_port + hostPort = var.proxy_port + } + ] + mount_points = [ + { + readOnly = false + containerPath = "/vol/static" + sourceVolume = "static" + } + ] + } + } } module "ecs" { @@ -84,58 +119,7 @@ module "ecs" { wait_for_steady_state = true # Container definition(s) - container_definitions = { - app = { - name = var.container_name - image = startswith(var.image, "sha256") ? "${local.ecr_repository_url}@${var.image}" : "${local.ecr_repository_url}:${var.image}" - health_check = { - command = ["CMD-SHELL", "uwsgi-is-ready --stats-socket /tmp/statsock > /dev/null 2>&1 || exit 1"] - } - readonly_root_filesystem = false - essential = true - memory_reservation = 256 - environment = local.app_container_vars - port_mappings = [ - { - name = var.container_name - containerPort = 9000 - hostPort = 9000 - } - ] - mount_points = [ - { - readOnly = false - containerPath = "/vol/web" - sourceVolume = "static" - } - ] - } - nginx = { - name = "nginx" - image = "${local.ecr_registry}/nginx-proxy:latest" - health_check = { - command = ["CMD-SHELL", "curl -so /dev/null http://localhost/health || exit 1"] - } - readonly_root_filesystem = false - essential = true - memory_reservation = 256 - environment = local.nginx_container_vars - port_mappings = [ - { - name = "nginx" - containerPort = var.container_port - hostPort = var.container_port - } - ] - mount_points = [ - { - readOnly = false - containerPath = "/vol/static" - sourceVolume = "static" - } - ] - } - } + container_definitions = local.container_definitions deployment_circuit_breaker = { enable = true @@ -145,8 +129,8 @@ module "ecs" { load_balancer = { service = { target_group_arn = aws_lb_target_group.app.arn - container_name = "nginx" - container_port = var.container_port + container_name = var.nginx_proxy ? "nginx" : "app" + container_port = var.nginx_proxy ? var.proxy_port : var.app_port } } @@ -155,8 +139,8 @@ module "ecs" { security_group_rules = { ingress_vpc = { type = "ingress" - from_port = var.container_port - to_port = var.container_port + from_port = var.nginx_proxy ? var.proxy_port : var.app_port + to_port = var.nginx_proxy ? var.proxy_port : var.app_port protocol = "tcp" cidr_blocks = [local.vpc_cidr] } @@ -179,18 +163,15 @@ module "ecs" { "s3:DeleteObject", "s3:PutObjectAcl" ] - resources = [ - "arn:aws:s3:::${var.s3_storage_bucket_name}", - "arn:aws:s3:::${var.s3_storage_bucket_name}/*" - ] + resources = flatten([for bucket in module.s3.wrapper : + split(",", "arn:aws:s3:::${bucket.s3_bucket_id},arn:aws:s3:::${bucket.s3_bucket_id}/*" + )]) }, { actions = [ "secretsmanager:GetSecretValue" ] - resources = [ - "arn:aws:secretsmanager:${var.db_secret_region}:*:secret:${var.db_secret_name}*" - ] + resources = ["arn:aws:secretsmanager:${data.aws_region.current.name}:*:secret:/rds*"] } ] diff --git a/deploy/tf/get-parameters.tf b/deploy/tf/get-parameters.tf index d335379..d80de7c 100644 --- a/deploy/tf/get-parameters.tf +++ b/deploy/tf/get-parameters.tf @@ -13,12 +13,6 @@ locals { public_subnet_cidrs = nonsensitive(data.aws_ssm_parameter.public_subnet_cidrs.value) private_subnets = split(",", nonsensitive(data.aws_ssm_parameter.private_subnets.value)) private_subnet_cidrs = nonsensitive(data.aws_ssm_parameter.private_subnet_cidrs.value) - - # ecr values - ecr_repository_url = nonsensitive(data.aws_ssm_parameter.ecr_repository_url.value) - - # rds values - rds_url = nonsensitive(data.aws_ssm_parameter.rds_url.value) } # alb parameters @@ -66,13 +60,3 @@ data "aws_ssm_parameter" "zonename" { data "aws_ssm_parameter" "zoneid" { name = "/core/zone_id" } - -# ecr parameters -data "aws_ssm_parameter" "ecr_repository_url" { - name = "/apps/ecr/${var.ecr_parameter_name}/ecr_repository_url" -} - -# rds parameters -data "aws_ssm_parameter" "rds_url" { - name = "/rds/${var.rds_parameter_name}/endpoint" -} diff --git a/deploy/tf/vars-shared.tf b/deploy/tf/variables.tf similarity index 59% rename from deploy/tf/vars-shared.tf rename to deploy/tf/variables.tf index 7365ca4..6e09b4c 100644 --- a/deploy/tf/vars-shared.tf +++ b/deploy/tf/variables.tf @@ -1,40 +1,38 @@ -# ssm parameters variable "alb_parameter_name" { description = "The parameter name to derive the ALB details from." type = string } -variable "ecr_parameter_name" { - description = "The parameter name to derive the ALB details from." - type = string -} - -variable "rds_parameter_name" { - description = "The parameter name to derive the database host from." +variable "app_container_name" { + description = "The name of the primary application container" type = string + default = "app" } -# general variables variable "app_name" { description = "The name of the application e.g. sample-django-app" type = string } +variable "app_port" { + description = "The port to expose to the nginx proxy on the application container." + type = number + default = 9000 +} + variable "app_hostnames" { description = "Hostnames to associate with the application" type = list(string) } -variable "container_name" { - description = "The name of the primary application container" - type = string - default = "app" +variable "container_vars" { + description = "Map of key/pair values to pass to the container definition." + type = map(any) } -variable "container_port" { - description = "The port to expose to the load balancer on the container" - type = number - default = 80 +variable "ecr_registry" { + description = "The registry to pull docker images from." + type = string } variable "environment" { @@ -46,3 +44,15 @@ variable "image" { description = "The digest/tag of the docker image to pull from ECR" type = string } + +variable "nginx_proxy" { + description = "Whether or not to side-load an nginx container in the task definition" + type = bool + default = true +} + +variable "proxy_port" { + description = "The port to expose to the load balancer on the container" + type = number + default = 80 +} diff --git a/deploy/tf/vars-container.tf b/deploy/tf/vars-container.tf deleted file mode 100644 index 145fa69..0000000 --- a/deploy/tf/vars-container.tf +++ /dev/null @@ -1,51 +0,0 @@ -# Container environment variables -variable "allowed_hosts" { - description = "Hosts allowed to access the application container (i.e. 127.0.0.1)" - type = string -} - -variable "allowed_cidr_nets" { - description = "Subnet CIDR's allowed to access the application container" - type = string -} - -variable "django_secret_key" { - description = "The secret key for django app" - type = string -} - -variable "db_host" { - description = "Override variable for database host" - type = string -} - -variable "db_name" { - description = "The name of the database" - type = string -} - -variable "db_user" { - description = "The user to connect to the database with" - type = string -} - -variable "db_secret_name" { - description = "The name of the secret to fetch DB login credentials from" - type = string -} - -variable "db_secret_region" { - description = "The region to fetch the secret from" - type = string - default = "ap-southeast-2" -} - -variable "s3_storage_bucket_name" { - description = "Name of the S3 bucket to use" - type = string -} - -variable "s3_storage_bucket_region" { - description = "The bucket region" - type = string -} diff --git a/deploy/tg/terragrunt.hcl b/deploy/tg/terragrunt.hcl index bc7f9c9..865f92d 100644 --- a/deploy/tg/terragrunt.hcl +++ b/deploy/tg/terragrunt.hcl @@ -9,25 +9,19 @@ inputs = { # fetch the ssm parameter names alb_parameter_name = get_env("ALB_PARAMETER_NAME") - ecr_parameter_name = get_env("ECR_PARAMETER_NAME") - rds_parameter_name = get_env("RDS_PARAMETER_NAME") # DNS hostnames to associate with the container app_hostnames = ["api-${local.global.environment}"] - # get docker environment variable values with default fallback values - allowed_hosts = get_env("ALLOWED_HOSTS", "*") - allowed_cidr_nets = get_env("ALLOWED_CIDR_NETS", "") - django_secret_key = get_env("DJANGO_SECRET_KEY", "changeme") - db_host = get_env("DB_HOST", "") - db_name = get_env("DB_NAME", "api") - db_user = get_env("DB_USER", "api") - db_secret_name = get_env("DB_SECRET_NAME", "/rds/stefan-db/primary/evaluation/api") - db_secret_region = get_env("DB_SECRET_REGION", "ap-southeast-2") - s3_storage_bucket_name = get_env("S3_STORAGE_BUCKET_NAME", "") - s3_storage_bucket_region = get_env("S3_STORAGE_BUCKET_REGION", "") + # container-specific environment variables + container_vars = local.container_vars + + ecr_registry = get_env("ECR_REGISTRY") } locals { - global = include.global.locals + container_var_defaults = yamldecode(file("../container/vars.yaml")) + # get any overrides from the environment (e.g. GitHub deployment variables) + container_vars = { for k, v in local.container_var_defaults : k => can(get_env(upper(k))) ? get_env(upper(k)) : v } + global = include.global.locals } From 932062d92fce014a3f9fd6954dfdbe420864485b Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 21:37:29 +1100 Subject: [PATCH 02/12] github vars: refactor to development.env --- deploy/github/dev.env | 15 --------------- deploy/github/development.env | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 deploy/github/dev.env create mode 100644 deploy/github/development.env diff --git a/deploy/github/dev.env b/deploy/github/dev.env deleted file mode 100644 index 09fc297..0000000 --- a/deploy/github/dev.env +++ /dev/null @@ -1,15 +0,0 @@ -# general environment variables for Terragrunt -ALB_PARAMETER_NAME=my-alb-parameter -APP_NAME=sample-django-app -AWS_ACCOUNT_ID=123456789012 -AWS_REGION=ap-southeast-2 -ECR_REGISTRY=123456789012.dkr.ecr.ap-southeast-2.amazonaws.com -ECR_REPOSITORY=api -ENVIRONMENT=development - -# container definition variables -DB_HOST=my-rds-db.endpoint -DB_SECRET_NAME=/rds/my-rds-db/secret -DB_SECRET_REGION=ap-southeast-2 -S3_STORAGE_BUCKET_NAME=my-app-bucket -S3_STORAGE_BUCKET_REGION=ap-southeast-2 diff --git a/deploy/github/development.env b/deploy/github/development.env new file mode 100644 index 0000000..40e0b79 --- /dev/null +++ b/deploy/github/development.env @@ -0,0 +1,18 @@ +# general environment variables for Terragrunt +ALB_PARAMETER_NAME=shared-alb-devops-sydney +APP_NAME=sample-django-app +AWS_ACCOUNT_ID=450356697252 +AWS_REGION=ap-southeast-2 +ECR_REGISTRY=450356697252.dkr.ecr.ap-southeast-2.amazonaws.com +ECR_REPOSITORY=api +ENVIRONMENT=development + +# container definition variables +ALLOWED_CIDR_NETS=10.32.48.0/20,10.32.64.0/20,10.32.80.0/20 +DB_HOST=stefan-db-rds-primary-evaluation.gamma.aodn.org.au +DB_NAME=api_dev +DB_SECRET_NAME=/rds/stefan-db/primary/evaluation/api +DB_SECRET_REGION=ap-southeast-2 +DB_USER=api_dev +S3_STORAGE_BUCKET_NAME=sample-django-app-development-450356697252 +S3_STORAGE_BUCKET_REGION=ap-southeast-2 From 7f09e32a45bca82cc5152cf74acaccebc1ed43af Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 21:38:19 +1100 Subject: [PATCH 03/12] github actions: add deploy job to dev workflow --- .github/workflows/deploy-development.yml | 131 +++++++++++++++++++++++ .github/workflows/test.yml | 41 ------- 2 files changed, 131 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/deploy-development.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml new file mode 100644 index 0000000..506be22 --- /dev/null +++ b/.github/workflows/deploy-development.yml @@ -0,0 +1,131 @@ +name: Build, Test and Deploy Development + +on: + pull_request: + branches: + - master + +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + +jobs: + build_test_push: + runs-on: ubuntu-latest + environment: development + outputs: + image_tag: ${{ steps.set_image_tag.outputs.image_tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Setup Docker Structure Test + run: > + curl -LO + https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 + && chmod +x container-structure-test-linux-amd64 && sudo mv container-structure-test-linux-amd64 + /usr/local/bin/container-structure-test + + - name: Set Image Tag + id: set_image_tag + run: | + branch_name=${{ github.head_ref || github.ref_name }} + echo "image_tag=${{ env.TAG_PREFIX}}-${BRANCH_NAME//\//-}" >> $GITHUB_OUTPUT + env: + TAG_PREFIX: dev + + - name: Build Docker Image + uses: docker/build-push-action@v5 + with: + context: . + load: true + tags: ${{ vars.ECR_REPOSITORY }}:${{ steps.set_image_tag.outputs.image_tag }} + + - name: Test Docker Image + run: | + container-structure-test test --image ${{ vars.ECR_REPOSITORY }}:${{ steps.set_image_tag.outputs.image_tag }} --config tests/config.yaml + + - name: Login to ECR + uses: docker/login-action@v3 + with: + registry: ${{ vars.ECR_REGISTRY }} + + - name: Build and Push Docker Image + id: build_and_push + uses: docker/build-push-action@v5 + with: + context: . +# Only building for AMD64 for now +# platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ vars.ECR_REGISTRY }}/${{ vars.ECR_REPOSITORY }}:${{ steps.set_image_tag.outputs.image_tag }} + + development_deploy: + runs-on: ubuntu-latest + environment: development + env: + tf_version: '1.5.7' + tg_version: '0.54.0' + tg_dir: './deploy/tg' + needs: [build_test_push] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + audience: sts.amazonaws.com + aws-region: ${{ vars.AWS_REGION }} + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + + - name: Expose github environment as shell variables + env: + SECRETS_CONTEXT: ${{ toJson(secrets) }} + VARS_CONTEXT: ${{ toJson(vars) }} + run: | + EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) + to_envs() { jq -r "to_entries[] | \"\(.key)<<$EOF\n\(.value)\n$EOF\n\""; } + echo "$VARS_CONTEXT" | to_envs >> $GITHUB_ENV + echo "$SECRETS_CONTEXT" | to_envs >> $GITHUB_ENV + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: ${{ env.tf_version }} + + - name: Setup Terragrunt + id: setup_terragrunt + run: | + wget https://github.com/gruntwork-io/terragrunt/releases/download/v${terragrunt_version}/terragrunt_linux_amd64 \ + && mv terragrunt_linux_amd64 terragrunt \ + && chmod +x terragrunt \ + && mv terragrunt /usr/local/bin/terragrunt + env: + terragrunt_version: ${{ env.tg_version }} + + - name: Terragrunt Plan + id: terragrunt_plan + run: terragrunt plan -out=tf.plan + working-directory: ${{ env.tg_dir }} + env: + TF_INPUT: 0 + TF_IN_AUTOMATION: true + # get the image digest from the build job with optional override from vars context + TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} + + - name: Terragrunt Apply + id: terragrunt_apply + run: terragrunt apply -auto-approve tf.plan + working-directory: ${{ env.tg_dir }} + env: + TF_INPUT: 0 + TF_IN_AUTOMATION: true + # get the image digest from the build job with optional override from vars context + TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 9f44abc..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: Build and Test - -on: - pull_request: - branches: - - master - -permissions: - id-token: write # This is required for requesting the JWT - contents: read # This is required for actions/checkout - -jobs: - build_and_test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Setup Docker Structure Test - run: > - curl -LO - https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 - && chmod +x container-structure-test-linux-amd64 && sudo mv container-structure-test-linux-amd64 - /usr/local/bin/container-structure-test - - - name: Build Docker Image - uses: docker/build-push-action@v5 - with: - context: . - load: true - tags: ${{ vars.ECR_REPOSITORY }} - - - name: Test Docker Image - run: | - container-structure-test test --image ${{ vars.ECR_REPOSITORY }} --config tests/config.yaml From 8246d386f078ac64e663fae9c01b70d401d83fa0 Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 21:47:28 +1100 Subject: [PATCH 04/12] deploy-development.yml: add missing aws auth step --- .github/workflows/deploy-development.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml index 506be22..62e7548 100644 --- a/.github/workflows/deploy-development.yml +++ b/.github/workflows/deploy-development.yml @@ -51,6 +51,13 @@ jobs: run: | container-structure-test test --image ${{ vars.ECR_REPOSITORY }}:${{ steps.set_image_tag.outputs.image_tag }} --config tests/config.yaml + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + audience: sts.amazonaws.com + aws-region: ${{ vars.AWS_REGION }} + role-to-assume: ${{ secrets.AWS_ROLE_ARN }} + - name: Login to ECR uses: docker/login-action@v3 with: From 90cbf76ee6680c1eacbbe394510813ef7c43b1df Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 22:07:32 +1100 Subject: [PATCH 05/12] deploy-development.yml: fix tag --- .github/workflows/deploy-development.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml index 62e7548..9c68004 100644 --- a/.github/workflows/deploy-development.yml +++ b/.github/workflows/deploy-development.yml @@ -36,7 +36,9 @@ jobs: id: set_image_tag run: | branch_name=${{ github.head_ref || github.ref_name }} - echo "image_tag=${{ env.TAG_PREFIX}}-${BRANCH_NAME//\//-}" >> $GITHUB_OUTPUT + tag=${{ env.TAG_PREFIX}}-${branch_name//\//-} + echo "$tag" + echo "image_tag=$tag" >> $GITHUB_OUTPUT env: TAG_PREFIX: dev From e91caef9fa1002acb2f4dc04c4b96081116477d6 Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 22:28:30 +1100 Subject: [PATCH 06/12] deploy-development.yml: re-attempt with tg action --- .github/workflows/deploy-development.yml | 66 +++++++++++++++++------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml index 9c68004..349b358 100644 --- a/.github/workflows/deploy-development.yml +++ b/.github/workflows/deploy-development.yml @@ -104,37 +104,63 @@ jobs: echo "$VARS_CONTEXT" | to_envs >> $GITHUB_ENV echo "$SECRETS_CONTEXT" | to_envs >> $GITHUB_ENV - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.tf_version }} - - - name: Setup Terragrunt - id: setup_terragrunt - run: | - wget https://github.com/gruntwork-io/terragrunt/releases/download/v${terragrunt_version}/terragrunt_linux_amd64 \ - && mv terragrunt_linux_amd64 terragrunt \ - && chmod +x terragrunt \ - && mv terragrunt /usr/local/bin/terragrunt - env: - terragrunt_version: ${{ env.tg_version }} +# - name: Setup Terraform +# uses: hashicorp/setup-terraform@v3 +# with: +# terraform_version: ${{ env.tf_version }} +# +# - name: Setup Terragrunt +# id: setup_terragrunt +# run: | +# wget https://github.com/gruntwork-io/terragrunt/releases/download/v${terragrunt_version}/terragrunt_linux_amd64 \ +# && mv terragrunt_linux_amd64 terragrunt \ +# && chmod +x terragrunt \ +# && mv terragrunt /usr/local/bin/terragrunt +# env: +# terragrunt_version: ${{ env.tg_version }} - name: Terragrunt Plan - id: terragrunt_plan - run: terragrunt plan -out=tf.plan - working-directory: ${{ env.tg_dir }} + uses: gruntwork-io/terragrunt-action@v2 + with: + tf_version: ${{ env.tf_version }} + tg_version: ${{ env.tg_version }} + tg_dir: ${{ env.tg_dir }} + tg_command: 'plan -out=tf.plan' env: TF_INPUT: 0 TF_IN_AUTOMATION: true # get the image digest from the build job with optional override from vars context TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} +# - name: Terragrunt Plan +# id: terragrunt_plan +# run: terragrunt plan -out=tf.plan +# working-directory: ${{ env.tg_dir }} +# env: +# TF_INPUT: 0 +# TF_IN_AUTOMATION: true +# # get the image digest from the build job with optional override from vars context +# TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} + - name: Terragrunt Apply - id: terragrunt_apply - run: terragrunt apply -auto-approve tf.plan - working-directory: ${{ env.tg_dir }} + uses: gruntwork-io/terragrunt-action@v2 + with: + tf_version: ${{ env.tf_version }} + tg_version: ${{ env.tg_version }} + tg_dir: ${{ env.tg_dir }} + tg_command: '--terragrunt-non-interactive --terragrunt-log-level info apply -auto-approve tf.plan' env: TF_INPUT: 0 TF_IN_AUTOMATION: true # get the image digest from the build job with optional override from vars context TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} + +# - name: Terragrunt Apply +# id: terragrunt_apply +# run: terragrunt apply -auto-approve tf.plan +# working-directory: ${{ env.tg_dir }} +# env: +# TF_INPUT: 0 +# TF_IN_AUTOMATION: true +# # get the image digest from the build job with optional override from vars context +# TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} From 3565b2005d880ba474ee84df28e4681a68312745 Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:12:14 +1100 Subject: [PATCH 07/12] deploy/tf: fix ecr url for app container add vars for cpu + mem --- deploy/tf/ecs.tf | 24 +++++++++++++----------- deploy/tf/variables.tf | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/deploy/tf/ecs.tf b/deploy/tf/ecs.tf index 53fab05..32e7f8e 100644 --- a/deploy/tf/ecs.tf +++ b/deploy/tf/ecs.tf @@ -11,8 +11,12 @@ locals { container_definitions = var.nginx_proxy ? merge(local.app_container_definition, local.nginx_container_definition) : local.app_container_definition app_container_definition = { app = { - name = var.app_container_name - image = startswith(var.image, "sha256") ? "${var.ecr_registry}@${var.image}" : "${var.ecr_registry}:${var.image}" + name = var.app_container_name + image = ( + startswith(var.image, "sha256") ? + "${var.ecr_registry}/${var.ecr_repository}@${var.image}" : + "${var.ecr_registry}/${var.ecr_repository}:${var.image}" + ) health_check = { command = ["CMD-SHELL", "uwsgi-is-ready --stats-socket /tmp/statsock > /dev/null 2>&1 || exit 1"] } @@ -96,24 +100,22 @@ module "ecs" { "${var.app_name}-${var.environment}" = { capacity_provider_strategy = { - dedicated = { + env_strategy = { base = 0 - capacity_provider = "FARGATE" + capacity_provider = var.environment == "production" ? "FARGATE" : "FARGATE_SPOT" weight = 100 } - # spot = { - # base = 0 - # capacity_provider = "FARGATE_SPOT" - # weight = 100 - # } } # allow ECS exec commands on containers (e.g. to get a shell session) enable_execute_command = true # resources - cpu = 512 - memory = 1024 + cpu = var.cpu + memory = var.memory + + # do not force a new deployment unless the image digest has changed + force_new_deployment = false # wait for service to reach steady state wait_for_steady_state = true diff --git a/deploy/tf/variables.tf b/deploy/tf/variables.tf index 6e09b4c..deee518 100644 --- a/deploy/tf/variables.tf +++ b/deploy/tf/variables.tf @@ -30,11 +30,22 @@ variable "container_vars" { type = map(any) } +variable "cpu" { + description = "The CPU capacity to allocate to the task." + type = number + default = 512 +} + variable "ecr_registry" { description = "The registry to pull docker images from." type = string } +variable "ecr_repository" { + description = "The repository to pull the image from." + type = string +} + variable "environment" { description = "Environment name to prepend/append to resource names" type = string @@ -45,6 +56,12 @@ variable "image" { type = string } +variable "memory" { + description = "The CPU capacity to allocate to the task." + type = number + default = 1024 +} + variable "nginx_proxy" { description = "Whether or not to side-load an nginx container in the task definition" type = bool From 0a71be847f5b25270d8b4b5b58ea28aaf25d65a2 Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:12:37 +1100 Subject: [PATCH 08/12] deploy/tg: add required new input --- deploy/tg/terragrunt.hcl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/tg/terragrunt.hcl b/deploy/tg/terragrunt.hcl index 865f92d..21f81b0 100644 --- a/deploy/tg/terragrunt.hcl +++ b/deploy/tg/terragrunt.hcl @@ -16,7 +16,8 @@ inputs = { # container-specific environment variables container_vars = local.container_vars - ecr_registry = get_env("ECR_REGISTRY") + ecr_registry = get_env("ECR_REGISTRY") + ecr_repository = get_env("ECR_REPOSITORY") } locals { From 00e1567063dbe7f38740f36b8fb4a8f82f990264 Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:13:01 +1100 Subject: [PATCH 09/12] deploy-development.yml: remove obsolete tg steps --- .github/workflows/deploy-development.yml | 35 ------------------------ 1 file changed, 35 deletions(-) diff --git a/.github/workflows/deploy-development.yml b/.github/workflows/deploy-development.yml index 349b358..41e9324 100644 --- a/.github/workflows/deploy-development.yml +++ b/.github/workflows/deploy-development.yml @@ -104,21 +104,6 @@ jobs: echo "$VARS_CONTEXT" | to_envs >> $GITHUB_ENV echo "$SECRETS_CONTEXT" | to_envs >> $GITHUB_ENV -# - name: Setup Terraform -# uses: hashicorp/setup-terraform@v3 -# with: -# terraform_version: ${{ env.tf_version }} -# -# - name: Setup Terragrunt -# id: setup_terragrunt -# run: | -# wget https://github.com/gruntwork-io/terragrunt/releases/download/v${terragrunt_version}/terragrunt_linux_amd64 \ -# && mv terragrunt_linux_amd64 terragrunt \ -# && chmod +x terragrunt \ -# && mv terragrunt /usr/local/bin/terragrunt -# env: -# terragrunt_version: ${{ env.tg_version }} - - name: Terragrunt Plan uses: gruntwork-io/terragrunt-action@v2 with: @@ -132,16 +117,6 @@ jobs: # get the image digest from the build job with optional override from vars context TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} -# - name: Terragrunt Plan -# id: terragrunt_plan -# run: terragrunt plan -out=tf.plan -# working-directory: ${{ env.tg_dir }} -# env: -# TF_INPUT: 0 -# TF_IN_AUTOMATION: true -# # get the image digest from the build job with optional override from vars context -# TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} - - name: Terragrunt Apply uses: gruntwork-io/terragrunt-action@v2 with: @@ -154,13 +129,3 @@ jobs: TF_IN_AUTOMATION: true # get the image digest from the build job with optional override from vars context TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} - -# - name: Terragrunt Apply -# id: terragrunt_apply -# run: terragrunt apply -auto-approve tf.plan -# working-directory: ${{ env.tg_dir }} -# env: -# TF_INPUT: 0 -# TF_IN_AUTOMATION: true -# # get the image digest from the build job with optional override from vars context -# TF_VAR_image: ${{ vars.IMAGE || needs.build_test_push.outputs.image_tag }} From 294600d65ad2363fe66a040f10c6f584d3329cca Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Wed, 13 Dec 2023 23:25:20 +1100 Subject: [PATCH 10/12] .pre-commit-config.yaml: add terragrunt_fmt hook --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9780638..f953723 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,7 @@ repos: rev: v1.83.4 hooks: - id: terraform_fmt + - id: terragrunt_fmt - id: terraform_validate args: - --tf-init-args=-backend=false From eba40dec29daf9cee17a79fc237f4cad0d7b66ae Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Thu, 14 Dec 2023 00:15:37 +1100 Subject: [PATCH 11/12] prod/staging workflows: use pre-canned tg action --- .github/workflows/deploy-production.yml | 33 +++++++++---------------- .github/workflows/deploy-staging.yml | 33 +++++++++---------------- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index f253a1c..027a762 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -83,25 +83,13 @@ jobs: echo "$VARS_CONTEXT" | to_envs >> $GITHUB_ENV echo "$SECRETS_CONTEXT" | to_envs >> $GITHUB_ENV - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.tf_version }} - - - name: Setup Terragrunt - id: setup_terragrunt - run: | - wget https://github.com/gruntwork-io/terragrunt/releases/download/v${terragrunt_version}/terragrunt_linux_amd64 \ - && mv terragrunt_linux_amd64 terragrunt \ - && chmod +x terragrunt \ - && mv terragrunt /usr/local/bin/terragrunt - env: - terragrunt_version: ${{ env.tg_version }} - - name: Terragrunt Plan - id: terragrunt_plan - run: terragrunt plan -out=tf.plan - working-directory: ${{ env.tg_dir }} + uses: gruntwork-io/terragrunt-action@v2 + with: + tf_version: ${{ env.tf_version }} + tg_version: ${{ env.tg_version }} + tg_dir: ${{ env.tg_dir }} + tg_command: 'plan -out=tf.plan' env: TF_INPUT: 0 TF_IN_AUTOMATION: true @@ -109,9 +97,12 @@ jobs: TF_VAR_image: ${{ vars.IMAGE || needs.build_push.outputs.image_digest }} - name: Terragrunt Apply - id: terragrunt_apply - run: terragrunt apply -auto-approve tf.plan - working-directory: ${{ env.tg_dir }} + uses: gruntwork-io/terragrunt-action@v2 + with: + tf_version: ${{ env.tf_version }} + tg_version: ${{ env.tg_version }} + tg_dir: ${{ env.tg_dir }} + tg_command: '--terragrunt-non-interactive --terragrunt-log-level info apply -auto-approve tf.plan' env: TF_INPUT: 0 TF_IN_AUTOMATION: true diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 828f598..0826b34 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -77,25 +77,13 @@ jobs: echo "$VARS_CONTEXT" | to_envs >> $GITHUB_ENV echo "$SECRETS_CONTEXT" | to_envs >> $GITHUB_ENV - - name: Setup Terraform - uses: hashicorp/setup-terraform@v3 - with: - terraform_version: ${{ env.tf_version }} - - - name: Setup Terragrunt - id: setup_terragrunt - run: | - wget https://github.com/gruntwork-io/terragrunt/releases/download/v${terragrunt_version}/terragrunt_linux_amd64 \ - && mv terragrunt_linux_amd64 terragrunt \ - && chmod +x terragrunt \ - && mv terragrunt /usr/local/bin/terragrunt - env: - terragrunt_version: ${{ env.tg_version }} - - name: Terragrunt Plan - id: terragrunt_plan - run: terragrunt plan -out=tf.plan - working-directory: ${{ env.tg_dir }} + uses: gruntwork-io/terragrunt-action@v2 + with: + tf_version: ${{ env.tf_version }} + tg_version: ${{ env.tg_version }} + tg_dir: ${{ env.tg_dir }} + tg_command: 'plan -out=tf.plan' env: TF_INPUT: 0 TF_IN_AUTOMATION: true @@ -103,9 +91,12 @@ jobs: TF_VAR_image: ${{ vars.IMAGE || needs.build_push.outputs.image_digest }} - name: Terragrunt Apply - id: terragrunt_apply - run: terragrunt apply -auto-approve tf.plan - working-directory: ${{ env.tg_dir }} + uses: gruntwork-io/terragrunt-action@v2 + with: + tf_version: ${{ env.tf_version }} + tg_version: ${{ env.tg_version }} + tg_dir: ${{ env.tg_dir }} + tg_command: '--terragrunt-non-interactive --terragrunt-log-level info apply -auto-approve tf.plan' env: TF_INPUT: 0 TF_IN_AUTOMATION: true From 9c8875ab1ef97c5e3845d4a4c07d92dcfc570802 Mon Sep 17 00:00:00 2001 From: digorgonzola <29941279+digorgonzola@users.noreply.github.com> Date: Thu, 14 Dec 2023 02:02:50 +1100 Subject: [PATCH 12/12] define tf operation timeouts --- deploy/tf/ecs.tf | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deploy/tf/ecs.tf b/deploy/tf/ecs.tf index 32e7f8e..6750d12 100644 --- a/deploy/tf/ecs.tf +++ b/deploy/tf/ecs.tf @@ -177,6 +177,12 @@ module "ecs" { } ] + timeouts = { + create = "10m" + update = "5m" + delete = "10m" + } + volume = { static = {} }