diff --git a/infra/analytics/app-config/env-config/outputs.tf b/infra/analytics/app-config/env-config/outputs.tf index df881da4f..893e95143 100644 --- a/infra/analytics/app-config/env-config/outputs.tf +++ b/infra/analytics/app-config/env-config/outputs.tf @@ -23,3 +23,7 @@ output "service_config" { secrets = toset(local.secrets) } } + +output "domain" { + value = var.domain +} diff --git a/infra/analytics/app-config/env-config/variables.tf b/infra/analytics/app-config/env-config/variables.tf index f4550a116..125955294 100644 --- a/infra/analytics/app-config/env-config/variables.tf +++ b/infra/analytics/app-config/env-config/variables.tf @@ -17,6 +17,12 @@ variable "has_database" { default = true } +variable "domain" { + type = string + description = "DNS domain of the website managed by HHS" + default = null +} + variable "database_instance_count" { description = "Number of database instances. Should be 2+ for production environments." type = number diff --git a/infra/analytics/service/.terraform.lock.hcl b/infra/analytics/service/.terraform.lock.hcl new file mode 100644 index 000000000..f7a91d873 --- /dev/null +++ b/infra/analytics/service/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.34.0" + constraints = "~> 5.34.0" + hashes = [ + "h1:1Y1JgV1z99QqAK06+atyfNqreZxyGZKbm4mZO4VhhT8=", + "zh:01bb20ae12b8c66f0cacec4f417a5d6741f018009f3a66077008e67cce127aa4", + "zh:3b0c9bdbbf846beef2c9573fc27898ceb71b69cf9d2f4b1dd2d0c2b539eab114", + "zh:5226ecb9c21c2f6fbf1d662ac82459ffcd4ad058a9ea9c6200750a21a80ca009", + "zh:6021b905d9b3cd3d7892eb04d405c6fa20112718de1d6ef7b9f1db0b0c97721a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9e61b8e0ccf923979cd2dc1f1140dbcb02f92248578e10c1996f560b6306317c", + "zh:ad6bf62cdcf531f2f92f6416822918b7ba2af298e4a0065c6baf44991fda982d", + "zh:b698b041ef38837753bbe5265dddbc70b76e8b8b34c5c10876e6aab0eb5eaf63", + "zh:bb799843c534f6a3f072a99d93a3b53ff97c58a96742be15518adf8127706784", + "zh:cebee0d942c37cd3b21e9050457cceb26d0a6ea886b855dab64bb67d78f863d1", + "zh:e061fdd1cb99e7c81fb4485b41ae000c6792d38f73f9f50aed0d3d5c2ce6dcfb", + "zh:eeb4943f82734946362696928336357cd1d36164907ae5905da0316a67e275e1", + "zh:ef09b6ad475efa9300327a30cbbe4373d817261c8e41e5b7391750b16ef4547d", + "zh:f01aab3881cd90b3f56da7c2a75f83da37fd03cc615fc5600a44056a7e0f9af7", + "zh:fcd0f724ebc4b56a499eb6c0fc602de609af18a0d578befa2f7a8df155c55550", + ] +} diff --git a/infra/analytics/service/dev.s3.tfbackend b/infra/analytics/service/dev.s3.tfbackend new file mode 100644 index 000000000..c1d0783cb --- /dev/null +++ b/infra/analytics/service/dev.s3.tfbackend @@ -0,0 +1,4 @@ +bucket = "simpler-grants-gov-315341936575-us-east-1-tf" +key = "infra/analytics/service/dev.tfstate" +dynamodb_table = "simpler-grants-gov-315341936575-us-east-1-tf-state-locks" +region = "us-east-1" diff --git a/infra/analytics/service/image_tag.tf b/infra/analytics/service/image_tag.tf new file mode 100644 index 000000000..ce1bc75c2 --- /dev/null +++ b/infra/analytics/service/image_tag.tf @@ -0,0 +1,56 @@ +# Make the "image_tag" variable optional so that "terraform plan" +# and "terraform apply" work without any required variables. +# +# This works as follows: + +# 1. Accept an optional variable during a terraform plan/apply. (see "image_tag" variable in variables.tf) + +# 2. Read the output used from the last terraform state using "terraform_remote_state". +# Get the backend config by parsing the backend config file +locals { + backend_config_file_path = "${path.module}/${var.environment_name}.s3.tfbackend" + backend_config_file = file("${path.module}/${var.environment_name}.s3.tfbackend") + + # Use regex to parse backend config file to get a map of variables to their + # defined values since there is no built-in terraform function that does that + # + # The backend config file consists of lines that look like + # = " match[1] } + tfstate_bucket = local.backend_config["bucket"] + tfstate_key = local.backend_config["key"] +} +data "terraform_remote_state" "current_image_tag" { + # Don't do a lookup if image_tag is provided explicitly. + # This saves some time and also allows us to do a first deploy, + # where the tfstate file does not yet exist. + count = var.image_tag == null ? 1 : 0 + backend = "s3" + + config = { + bucket = local.tfstate_bucket + key = local.tfstate_key + region = local.service_config.region + } + + defaults = { + image_tag = null + } +} + +# 3. Prefer the given variable if provided, otherwise default to the value from last time. +locals { + image_tag = (var.image_tag == null + ? data.terraform_remote_state.current_image_tag[0].outputs.image_tag + : var.image_tag) +} + +# 4. Store the final value used as a terraform output for next time. +output "image_tag" { + value = local.image_tag +} diff --git a/infra/analytics/service/main.tf b/infra/analytics/service/main.tf new file mode 100644 index 000000000..1191d3f44 --- /dev/null +++ b/infra/analytics/service/main.tf @@ -0,0 +1,137 @@ +# docs: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc +data "aws_vpc" "network" { + filter { + name = "tag:Name" + values = [module.project_config.network_configs[var.environment_name].vpc_name] + } +} + +# docs: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet +data "aws_subnets" "private" { + filter { + name = "vpc-id" + values = [data.aws_vpc.network.id] + } + filter { + name = "tag:subnet_type" + values = ["private"] + } +} + +# docs: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet +data "aws_subnets" "public" { + filter { + name = "vpc-id" + values = [data.aws_vpc.network.id] + } + filter { + name = "tag:subnet_type" + values = ["public"] + } +} + +locals { + # The prefix key/value pair is used for Terraform Workspaces, which is useful for projects with multiple infrastructure developers. + # By default, Terraform creates a workspace named “default.” If a non-default workspace is not created this prefix will equal “default”, + # if you choose not to use workspaces set this value to "dev" + prefix = terraform.workspace == "default" ? "" : "${terraform.workspace}-" + + # Add environment specific tags + tags = merge(module.project_config.default_tags, { + environment = var.environment_name + description = "Application resources created in ${var.environment_name} environment" + }) + + service_name = "${local.prefix}${module.app_config.app_name}-${var.environment_name}" + + is_temporary = startswith(terraform.workspace, "t-") + + environment_config = module.app_config.environment_configs[var.environment_name] + service_config = local.environment_config.service_config + database_config = local.environment_config.database_config + domain = local.environment_config.domain +} + +terraform { + required_version = ">= 1.2.0, < 2.0.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.34.0" + } + } + + backend "s3" { + encrypt = "true" + } +} + +provider "aws" { + region = local.service_config.region + default_tags { + tags = local.tags + } +} + +module "project_config" { + source = "../../project-config" +} + +module "app_config" { + source = "../app-config" +} + +data "aws_rds_cluster" "db_cluster" { + count = 1 + cluster_identifier = local.database_config.cluster_name +} + +data "aws_acm_certificate" "cert" { + count = local.domain != null ? 1 : 0 + domain = local.domain +} + +data "aws_iam_policy" "app_db_access_policy" { + count = 1 + name = local.database_config.app_access_policy_name +} + +data "aws_iam_policy" "migrator_db_access_policy" { + count = 1 + name = local.database_config.migrator_access_policy_name +} + +module "service" { + source = "../../modules/task-service" + service_name = local.service_name + is_temporary = false + image_repository_name = module.app_config.image_repository_name + image_tag = local.image_tag + vpc_id = data.aws_vpc.network.id + public_subnet_ids = data.aws_subnets.public.ids + private_subnet_ids = data.aws_subnets.private.ids + cpu = 1024 + memory = 2048 + + # This is a task based service, not a web server, so we don't need to run any instances of the service at rest. + desired_instance_count = 0 + + cert_arn = local.domain != null ? data.aws_acm_certificate.cert[0].arn : null + + db_vars = { + security_group_ids = data.aws_rds_cluster.db_cluster[0].vpc_security_group_ids + app_access_policy_arn = data.aws_iam_policy.app_db_access_policy[0].arn + migrator_access_policy_arn = data.aws_iam_policy.migrator_db_access_policy[0].arn + connection_info = { + host = data.aws_rds_cluster.db_cluster[0].endpoint + port = data.aws_rds_cluster.db_cluster[0].port + user = local.database_config.app_username + db_name = data.aws_rds_cluster.db_cluster[0].database_name + schema_name = local.database_config.schema_name + } + } + + extra_environment_variables = local.service_config.extra_environment_variables + secrets = local.service_config.secrets +} diff --git a/infra/analytics/service/outputs.tf b/infra/analytics/service/outputs.tf new file mode 100644 index 000000000..6dc8af91e --- /dev/null +++ b/infra/analytics/service/outputs.tf @@ -0,0 +1,19 @@ +output "service_cluster_name" { + value = module.service.cluster_name +} + +output "service_name" { + value = local.service_name +} + +output "application_log_group" { + value = module.service.application_log_group +} + +output "application_log_stream_prefix" { + value = module.service.application_log_stream_prefix +} + +output "migrator_role_arn" { + value = module.service.migrator_role_arn +} diff --git a/infra/analytics/service/variables.tf b/infra/analytics/service/variables.tf new file mode 100644 index 000000000..19a5f312f --- /dev/null +++ b/infra/analytics/service/variables.tf @@ -0,0 +1,10 @@ +variable "environment_name" { + type = string + description = "name of the application environment" +} + +variable "image_tag" { + type = string + description = "image tag to deploy to the environment" + default = null +} diff --git a/infra/modules/task-service/README.md b/infra/modules/task-service/README.md new file mode 100644 index 000000000..ef85aef4d --- /dev/null +++ b/infra/modules/task-service/README.md @@ -0,0 +1,6 @@ +# modules/task-service + +This module is functionally the same module as `modules/service`, but with the load balancer and associated networking components removed. + +This module (eg. `modules/task-service`) is meant for use with services that composed of individually run tasks. The modules it was based off of +(eg. `modules/service`) is meant for use with web servers. diff --git a/infra/modules/task-service/access-control.tf b/infra/modules/task-service/access-control.tf new file mode 100644 index 000000000..1aedc9b4b --- /dev/null +++ b/infra/modules/task-service/access-control.tf @@ -0,0 +1,161 @@ +#---------------- +# Access Control +#---------------- + +resource "aws_iam_role" "task_executor" { + name = local.task_executor_role_name + assume_role_policy = data.aws_iam_policy_document.ecs_tasks_assume_role_policy.json +} + +resource "aws_iam_role" "app_service" { + name = "${var.service_name}-app" + assume_role_policy = data.aws_iam_policy_document.ecs_tasks_assume_role_policy.json +} + +resource "aws_iam_role" "migrator_task" { + count = var.db_vars != null ? 1 : 0 + + name = "${var.service_name}-migrator" + assume_role_policy = data.aws_iam_policy_document.ecs_tasks_assume_role_policy.json +} + +data "aws_iam_policy_document" "ecs_tasks_assume_role_policy" { + statement { + sid = "ECSTasksAssumeRole" + actions = [ + "sts:AssumeRole" + ] + principals { + type = "Service" + identifiers = ["ecs-tasks.amazonaws.com", "states.amazonaws.com", "scheduler.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "task_executor" { + # checkov:skip=CKV_AWS_111:Ignore some IAM policy checks for the task executor role + + # Allow ECS to log to Cloudwatch. + statement { + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ] + resources = [ + "${aws_cloudwatch_log_group.service_logs.arn}:*", + ] + } + + # via https://docs.aws.amazon.com/step-functions/latest/dg/cw-logs.html + statement { + sid = "UnscopeLogsPermissions" + actions = [ + "logs:CreateLogDelivery", + "logs:CreateLogStream", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + "logs:PutLogEvents", + "logs:PutResourcePolicy", + "logs:DescribeResourcePolicies", + "logs:DescribeLogGroups", + ] + resources = ["*"] + } + + # via https://docs.aws.amazon.com/step-functions/latest/dg/xray-iam.html + statement { + sid = "StepFunctionsXRay" + actions = [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords", + "xray:GetSamplingRules", + "xray:GetSamplingTargets" + ] + resources = ["*"] + } + + statement { + sid = "StepFunctionsRunTask" + actions = [ + "ecs:RunTask", + "ecs:StopTask", + "ecs:DescribeTasks", + ] + resources = ["*"] + } + + statement { + sid = "StepFunctionsPassRole" + actions = [ + "iam:PassRole", + ] + resources = [ + aws_iam_role.app_service.arn, + aws_iam_role.task_executor.arn, + ] + } + + statement { + sid = "StepFunctionsEvents" + actions = [ + "events:PutTargets", + "events:PutRule", + "events:DescribeRule", + ] + resources = ["*"] + } + + statement { + sid = "StepFunctionsStartExecution" + actions = [ + "states:StartExecution", + ] + resources = ["arn:aws:states:*:*:stateMachine:*"] + } + + # Allow ECS to authenticate with ECR + statement { + sid = "ECRAuth" + actions = [ + "ecr:GetAuthorizationToken", + ] + resources = ["*"] + } + + # Allow ECS to download images. + statement { + sid = "ECRPullAccess" + actions = [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:GetDownloadUrlForLayer", + ] + resources = [data.aws_ecr_repository.app.arn] + } + + dynamic "statement" { + for_each = length(var.secrets) > 0 ? [1] : [] + content { + sid = "SecretsAccess" + actions = ["ssm:GetParameters"] + resources = local.secret_arn_patterns + } + } +} + +resource "aws_iam_role_policy" "task_executor" { + name = "${var.service_name}-task-executor-role-policy" + role = aws_iam_role.task_executor.id + policy = data.aws_iam_policy_document.task_executor.json +} + + +resource "aws_iam_role_policy_attachment" "extra_policies" { + for_each = var.extra_policies + + role = aws_iam_role.app_service.name + policy_arn = each.value +} diff --git a/infra/modules/task-service/application-logs.tf b/infra/modules/task-service/application-logs.tf new file mode 100644 index 000000000..b0a8de471 --- /dev/null +++ b/infra/modules/task-service/application-logs.tf @@ -0,0 +1,15 @@ +#------ +# Logs +#------ + +# Cloudwatch log group to for streaming ECS application logs. +resource "aws_cloudwatch_log_group" "service_logs" { + name = local.log_group_name + + # Conservatively retain logs for 5 years. + # Looser requirements may allow shorter retention periods + retention_in_days = 1827 + + # TODO(https://github.com/navapbc/template-infra/issues/164) Encrypt with customer managed KMS key + # checkov:skip=CKV_AWS_158:Encrypt service logs with customer key in future work +} diff --git a/infra/modules/task-service/database-access.tf b/infra/modules/task-service/database-access.tf new file mode 100644 index 000000000..a8a7b9186 --- /dev/null +++ b/infra/modules/task-service/database-access.tf @@ -0,0 +1,29 @@ +#----------------- +# Database Access +#----------------- + +resource "aws_vpc_security_group_ingress_rule" "db_ingress_from_service" { + count = var.db_vars != null ? length(var.db_vars.security_group_ids) : 0 + + security_group_id = var.db_vars.security_group_ids[count.index] + description = "Allow inbound requests to database from ${var.service_name} service" + + from_port = tonumber(var.db_vars.connection_info.port) + to_port = tonumber(var.db_vars.connection_info.port) + ip_protocol = "tcp" + referenced_security_group_id = aws_security_group.app.id +} + +resource "aws_iam_role_policy_attachment" "app_service_db_access" { + count = var.db_vars != null ? 1 : 0 + + role = aws_iam_role.app_service.name + policy_arn = var.db_vars.app_access_policy_arn +} + +resource "aws_iam_role_policy_attachment" "migrator_db_access" { + count = var.db_vars != null ? 1 : 0 + + role = aws_iam_role.migrator_task[0].name + policy_arn = var.db_vars.migrator_access_policy_arn +} diff --git a/infra/modules/task-service/main.tf b/infra/modules/task-service/main.tf new file mode 100644 index 000000000..d097416dd --- /dev/null +++ b/infra/modules/task-service/main.tf @@ -0,0 +1,112 @@ +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} +data "aws_ecr_repository" "app" { + name = var.image_repository_name +} + +locals { + alb_name = var.service_name + cluster_name = var.service_name + log_group_name = "service/${var.service_name}" + log_stream_prefix = var.service_name + task_executor_role_name = "${var.service_name}-task-executor" + image_url = "${data.aws_ecr_repository.app.repository_url}:${var.image_tag}" + hostname = var.hostname != null ? [{ name = "HOSTNAME", value = var.hostname }] : [] + + base_environment_variables = concat([ + { name : "PORT", value : tostring(var.container_port) }, + { name : "AWS_REGION", value : data.aws_region.current.name }, + { name : "API_AUTH_TOKEN", value : var.api_auth_token }, + ], local.hostname) + db_environment_variables = var.db_vars == null ? [] : [ + { name : "DB_HOST", value : var.db_vars.connection_info.host }, + { name : "DB_PORT", value : var.db_vars.connection_info.port }, + { name : "DB_USER", value : var.db_vars.connection_info.user }, + { name : "DB_NAME", value : var.db_vars.connection_info.db_name }, + { name : "DB_SCHEMA", value : var.db_vars.connection_info.schema_name }, + ] + environment_variables = concat( + local.base_environment_variables, + local.db_environment_variables, + [ + for name, value in var.extra_environment_variables : + { name : name, value : value } + ], + ) +} + +#------------------- +# Service Execution +#------------------- + +resource "aws_ecs_service" "app" { + name = var.service_name + cluster = aws_ecs_cluster.cluster.arn + launch_type = "FARGATE" + task_definition = aws_ecs_task_definition.app.arn + desired_count = var.desired_instance_count + + # Allow changes to the desired_count without differences in terraform plan. + # This allows autoscaling to manage the desired count for us. + lifecycle { + ignore_changes = [desired_count] + } + + network_configuration { + assign_public_ip = false + subnets = var.private_subnet_ids + security_groups = [aws_security_group.app.id] + } +} + +resource "aws_ecs_task_definition" "app" { + family = var.service_name + execution_role_arn = aws_iam_role.task_executor.arn + task_role_arn = aws_iam_role.app_service.arn + + container_definitions = jsonencode([ + { + name = var.service_name, + image = local.image_url, + memory = var.memory, + cpu = var.cpu, + networkMode = "awsvpc", + essential = true, + readonlyRootFilesystem = true, + + environment = local.environment_variables, + secrets = local.secrets, + linuxParameters = { + capabilities = { + drop = ["ALL"] + }, + initProcessEnabled = true + }, + logConfiguration = { + logDriver = "awslogs", + options = { + "awslogs-group" = aws_cloudwatch_log_group.service_logs.name, + "awslogs-region" = data.aws_region.current.name, + "awslogs-stream-prefix" = local.log_stream_prefix + } + } + } + ]) + + cpu = var.cpu + memory = var.memory + + requires_compatibilities = ["FARGATE"] + + # Reference https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-networking.html + network_mode = "awsvpc" +} + +resource "aws_ecs_cluster" "cluster" { + name = local.cluster_name + + setting { + name = "containerInsights" + value = "enabled" + } +} diff --git a/infra/modules/task-service/networking.tf b/infra/modules/task-service/networking.tf new file mode 100644 index 000000000..6a8c09811 --- /dev/null +++ b/infra/modules/task-service/networking.tf @@ -0,0 +1,24 @@ +#----------------------- +# Network Configuration +#----------------------- + +# Security group to allow access to Fargate tasks +resource "aws_security_group" "app" { + # Specify name_prefix instead of name because when a change requires creating a new + # security group, sometimes the change requires the new security group to be created + # before the old one is destroyed. In this situation, the new one needs a unique name + name_prefix = "${var.service_name}-app" + description = "Allow all outgoing traffic from application" + vpc_id = var.vpc_id + lifecycle { + create_before_destroy = true + } + + egress { + description = "Allow all outgoing traffic from application" + protocol = "-1" + from_port = 0 + to_port = 0 + cidr_blocks = ["0.0.0.0/0"] + } +} diff --git a/infra/modules/task-service/outputs.tf b/infra/modules/task-service/outputs.tf new file mode 100644 index 000000000..e11a72d30 --- /dev/null +++ b/infra/modules/task-service/outputs.tf @@ -0,0 +1,36 @@ +output "cluster_name" { + value = aws_ecs_cluster.cluster.name +} + +output "application_log_group" { + value = local.log_group_name +} + +output "application_log_stream_prefix" { + value = local.log_stream_prefix +} + +output "migrator_role_arn" { + description = "ARN for role to use for migration" + value = length(aws_iam_role.migrator_task) > 0 ? aws_iam_role.migrator_task[0].arn : null +} + +output "cluster_arn" { + value = aws_ecs_cluster.cluster.arn +} + +output "task_definition_arn" { + value = aws_ecs_task_definition.app.arn +} + +output "task_role_arn" { + value = aws_iam_role.task_executor.arn +} + +output "app_security_group_id" { + value = aws_security_group.app.id +} + +output "service_logs_arn" { + value = aws_cloudwatch_log_group.service_logs.arn +} diff --git a/infra/modules/task-service/secrets.tf b/infra/modules/task-service/secrets.tf new file mode 100644 index 000000000..29a276a48 --- /dev/null +++ b/infra/modules/task-service/secrets.tf @@ -0,0 +1,14 @@ +locals { + secrets = [ + for secret in var.secrets : + { + name = secret.name, + valueFrom = secret.ssm_param_name + } + ] + + secret_arn_patterns = [ + for secret in var.secrets : + "arn:aws:ssm:*:*:parameter/${trimprefix(secret.ssm_param_name, "/")}" + ] +} diff --git a/infra/modules/task-service/variables.tf b/infra/modules/task-service/variables.tf new file mode 100644 index 000000000..a92871ecb --- /dev/null +++ b/infra/modules/task-service/variables.tf @@ -0,0 +1,136 @@ +variable "service_name" { + description = "name of the service, to be used for infra structure resource naming" + validation { + condition = can(regex("^[-_\\da-z]+$", var.service_name)) + error_message = "use only lower case letters, numbers, dashes, and underscores" + } +} + +variable "image_tag" { + type = string + description = "The tag of the image to deploy" +} + +variable "image_repository_name" { + type = string + description = "The name of the container image repository" +} + +variable "desired_instance_count" { + type = number + description = "Number of instances of the task definition to place and keep running." + default = 1 +} + +variable "cpu" { + type = number + default = 256 + description = "Number of cpu units used by the task, expessed as an integer value, e.g 512 " +} + +variable "memory" { + type = number + default = 512 + description = "Amount (in MiB) of memory used by the task. e.g. 2048" +} + + +variable "container_port" { + type = number + description = "The port number on the container that's bound to the user-specified" + default = 8000 +} + +variable "hostname" { + type = string + description = "The hostname to override the default AWS configuration" + default = null +} + +variable "vpc_id" { + type = string + description = "Uniquely identifies the VPC." +} + +variable "public_subnet_ids" { + type = list(any) + description = "Public subnet ids in VPC" +} + +variable "private_subnet_ids" { + type = list(any) + description = "Private subnet ids in VPC" +} + +variable "extra_environment_variables" { + type = map(string) + description = "Additional environment variables to pass to the service container. Map from environment variable name to the value." + default = {} +} + +variable "secrets" { + type = set(object({ + name = string + ssm_param_name = string + })) + description = "List of configurations for defining environment variables that pull from SSM parameter store" + default = [] +} + +variable "db_vars" { + description = "Variables for integrating the app service with a database" + type = object({ + security_group_ids = list(string) + app_access_policy_arn = string + migrator_access_policy_arn = string + connection_info = object({ + host = string + port = string + user = string + db_name = string + schema_name = string + }) + }) + default = null +} + +variable "extra_policies" { + description = "Map of extra IAM policies to attach to the service's task role. The map's keys define the resource name in terraform." + type = map(string) + default = {} +} + +variable "cert_arn" { + description = "The ARN for the TLS certificate passed in from the app service layer" + type = string + default = null +} + +variable "enable_autoscaling" { + description = "Flag to enable or disable auto-scaling" + type = bool + default = false +} + +variable "max_capacity" { + description = "Maximum number of tasks for autoscaling" + type = number + default = 4 +} + +variable "min_capacity" { + description = "Minimum number of tasks for autoscaling" + type = number + default = 2 +} + +variable "api_auth_token" { + type = string + default = null + description = "Auth token for connecting to the API" +} + +variable "is_temporary" { + description = "Whether the service is meant to be spun up temporarily (e.g. for automated infra tests). This is used to disable deletion protection for the load balancer." + type = bool +}