From 57244e69abea685f7d45352abc994779b5f6d352 Mon Sep 17 00:00:00 2001 From: Bryant Biggs Date: Fri, 21 Apr 2023 07:33:23 -0400 Subject: [PATCH] feat!: Add support for creating ECS service and container definition (#76) Co-authored-by: Bryant Biggs Co-authored-by: Anton Babenko Co-authored-by: Nathan Ness Co-authored-by: arvindsoni80 --- .pre-commit-config.yaml | 3 +- README.md | 286 ++-- docs/README.md | 289 ++++ docs/images/cluster.png | Bin 0 -> 71466 bytes docs/images/complete.png | Bin 0 -> 188577 bytes docs/images/service.png | Bin 0 -> 40509 bytes docs/images/task.png | Bin 0 -> 12245 bytes examples/README.md | 8 + examples/complete/README.md | 39 +- examples/complete/main.tf | 292 ++-- examples/complete/outputs.tf | 17 +- examples/complete/service-hello-world/main.tf | 38 - .../complete/service-hello-world/variables.tf | 4 - examples/complete/versions.tf | 2 +- examples/ec2-autoscaling/README.md | 94 ++ examples/ec2-autoscaling/main.tf | 288 ++++ examples/ec2-autoscaling/outputs.tf | 132 ++ .../variables.tf} | 0 .../versions.tf | 2 +- examples/fargate/README.md | 47 +- examples/fargate/main.tf | 204 ++- examples/fargate/outputs.tf | 120 +- examples/fargate/versions.tf | 2 +- main.tf | 272 ++-- modules/cluster/README.md | 214 +++ modules/cluster/main.tf | 327 ++++ modules/cluster/outputs.tf | 70 + modules/cluster/variables.tf | 169 +++ modules/cluster/versions.tf | 10 + modules/container-definition/README.md | 200 +++ modules/container-definition/main.tf | 72 + modules/container-definition/outputs.tf | 22 + modules/container-definition/variables.tf | 305 ++++ modules/container-definition/versions.tf | 10 + modules/service/README.md | 328 +++- modules/service/main.tf | 1329 ++++++++++++++++- modules/service/outputs.tf | 152 ++ modules/service/variables.tf | 644 ++++++++ modules/service/versions.tf | 2 +- outputs.tf | 50 +- variables.tf | 129 +- versions.tf | 2 +- wrappers/README.md | 100 ++ wrappers/cluster/README.md | 100 ++ wrappers/cluster/main.tf | 34 + wrappers/cluster/outputs.tf | 5 + wrappers/cluster/variables.tf | 11 + wrappers/cluster/versions.tf | 3 + wrappers/container-definition/README.md | 100 ++ wrappers/container-definition/main.tf | 52 + wrappers/container-definition/outputs.tf | 5 + wrappers/container-definition/variables.tf | 11 + wrappers/container-definition/versions.tf | 3 + wrappers/main.tf | 36 + wrappers/outputs.tf | 5 + wrappers/service/README.md | 100 ++ wrappers/service/main.tf | 125 ++ wrappers/service/outputs.tf | 5 + wrappers/service/variables.tf | 11 + wrappers/service/versions.tf | 3 + wrappers/variables.tf | 11 + wrappers/versions.tf | 3 + 62 files changed, 6373 insertions(+), 524 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/images/cluster.png create mode 100644 docs/images/complete.png create mode 100644 docs/images/service.png create mode 100644 docs/images/task.png create mode 100644 examples/README.md delete mode 100644 examples/complete/service-hello-world/main.tf delete mode 100644 examples/complete/service-hello-world/variables.tf create mode 100644 examples/ec2-autoscaling/README.md create mode 100644 examples/ec2-autoscaling/main.tf create mode 100644 examples/ec2-autoscaling/outputs.tf rename examples/{complete/service-hello-world/outputs.tf => ec2-autoscaling/variables.tf} (100%) rename examples/{complete/service-hello-world => ec2-autoscaling}/versions.tf (82%) create mode 100644 modules/cluster/README.md create mode 100644 modules/cluster/main.tf create mode 100644 modules/cluster/outputs.tf create mode 100644 modules/cluster/variables.tf create mode 100644 modules/cluster/versions.tf create mode 100644 modules/container-definition/README.md create mode 100644 modules/container-definition/main.tf create mode 100644 modules/container-definition/outputs.tf create mode 100644 modules/container-definition/variables.tf create mode 100644 modules/container-definition/versions.tf create mode 100644 wrappers/README.md create mode 100644 wrappers/cluster/README.md create mode 100644 wrappers/cluster/main.tf create mode 100644 wrappers/cluster/outputs.tf create mode 100644 wrappers/cluster/variables.tf create mode 100644 wrappers/cluster/versions.tf create mode 100644 wrappers/container-definition/README.md create mode 100644 wrappers/container-definition/main.tf create mode 100644 wrappers/container-definition/outputs.tf create mode 100644 wrappers/container-definition/variables.tf create mode 100644 wrappers/container-definition/versions.tf create mode 100644 wrappers/main.tf create mode 100644 wrappers/outputs.tf create mode 100644 wrappers/service/README.md create mode 100644 wrappers/service/main.tf create mode 100644 wrappers/service/outputs.tf create mode 100644 wrappers/service/variables.tf create mode 100644 wrappers/service/versions.tf create mode 100644 wrappers/variables.tf create mode 100644 wrappers/versions.tf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d5886a6..e023d29 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,8 +1,9 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.77.0 + rev: v1.77.2 hooks: - id: terraform_fmt + - id: terraform_wrapper_module_for_each - id: terraform_validate - id: terraform_docs args: diff --git a/README.md b/README.md index e09cbf2..7e17741 100644 --- a/README.md +++ b/README.md @@ -2,116 +2,27 @@ Terraform module which creates ECS (Elastic Container Service) resources on AWS. -## Available Features - -- ECS cluster -- Fargate capacity providers -- EC2 AutoScaling Group capacity providers - -## Usage - -### Fargate Capacity Providers - -```hcl -module "ecs" { - source = "terraform-aws-modules/ecs/aws" - - cluster_name = "ecs-fargate" - - cluster_configuration = { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - cloud_watch_log_group_name = "/aws/ecs/aws-ec2" - } - } - } - - fargate_capacity_providers = { - FARGATE = { - default_capacity_provider_strategy = { - weight = 50 - } - } - FARGATE_SPOT = { - default_capacity_provider_strategy = { - weight = 50 - } - } - } - - tags = { - Environment = "Development" - Project = "EcsEc2" - } -} -``` - -### EC2 Autoscaling Capacity Providers - -```hcl -module "ecs" { - source = "terraform-aws-modules/ecs/aws" +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md) - cluster_name = "ecs-ec2" - - cluster_configuration = { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - cloud_watch_log_group_name = "/aws/ecs/aws-ec2" - } - } - } +## Available Features - autoscaling_capacity_providers = { - one = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" - managed_termination_protection = "ENABLED" +- ECS cluster w/ Fargate or EC2 Auto Scaling capacity providers +- ECS Service w/ task definition, task set, and container definition support +- Separate sub-modules or integrated module for ECS cluster and service - managed_scaling = { - maximum_scaling_step_size = 5 - minimum_scaling_step_size = 1 - status = "ENABLED" - target_capacity = 60 - } +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) - default_capacity_provider_strategy = { - weight = 60 - base = 20 - } - } - two = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 15 - minimum_scaling_step_size = 5 - status = "ENABLED" - target_capacity = 90 - } - - default_capacity_provider_strategy = { - weight = 40 - } - } - } +## Usage - tags = { - Environment = "Development" - Project = "EcsEc2" - } -} -``` +This project supports creating resources through individual sub-modules, or through a single module that creates both the cluster and service resources. See the respective sub-module directory for more details and example usage. -### Fargate & EC2 Autoscaling Capacity Providers +### Integrated Cluster w/ Services ```hcl module "ecs" { source = "terraform-aws-modules/ecs/aws" - cluster_name = "ecs-mixed" + cluster_name = "ecs-integrated" cluster_configuration = { execute_command_configuration = { @@ -135,65 +46,112 @@ module "ecs" { } } - autoscaling_capacity_providers = { - one = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 5 - minimum_scaling_step_size = 1 - status = "ENABLED" - target_capacity = 60 + services = { + ecsdemo-frontend = { + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + ecs-sample = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } } - default_capacity_provider_strategy = { - weight = 60 - base = 20 + service_connect_configuration = { + namespace = "example" + service = { + client_alias = { + port = 80 + dns_name = "ecs-sample" + } + port_name = "ecs-sample" + discovery_name = "ecs-sample" + } } - } - two = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 15 - minimum_scaling_step_size = 5 - status = "ENABLED" - target_capacity = 90 + + load_balancer = { + service = { + target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" + container_name = "ecs-sample" + container_port = 80 + } } - default_capacity_provider_strategy = { - weight = 40 + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "Service port" + source_security_group_id = "sg-12345678" + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } } } } tags = { Environment = "Development" - Project = "EcsEc2" + Project = "Example" } } ``` -## Conditional Creation - -The following values are provided to toggle on/off creation of the associated resources as desired: - -```hcl -module "ecs" { - source = "terraform-aws-modules/ecs/aws" - - # Disable creation of cluster and all resources - create = false - - # ... omitted -} -``` - ## Examples -- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) - [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) @@ -202,48 +160,70 @@ module "ecs" { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.6 | +| [aws](#requirement\_aws) | >= 4.55 | ## Providers -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 4.6 | +No providers. ## Modules -No modules. +| Name | Source | Version | +|------|--------|---------| +| [cluster](#module\_cluster) | ./modules/cluster | n/a | +| [service](#module\_service) | ./modules/service | n/a | ## Resources -| Name | Type | -|------|------| -| [aws_ecs_capacity_provider.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_capacity_provider) | resource | -| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | -| [aws_ecs_cluster_capacity_providers.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | +No resources. ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitons to create for the cluster | `any` | `{}` | no | +| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster | `any` | `{}` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events | `number` | `90` | no | +| [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | A map of additional tags to add to the log group created | `map(string)` | `{}` | no | | [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | | [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace | `map(string)` | `{}` | no | | [cluster\_settings](#input\_cluster\_settings) | Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `map(string)` |
{
"name": "containerInsights",
"value": "enabled"
}
| no | +| [cluster\_tags](#input\_cluster\_tags) | A map of additional tags to add to the cluster | `map(string)` | `{}` | no | | [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | | [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | | [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | +| [services](#input\_services) | Map of service definitions to create | `any` | `{}` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | ## Outputs | Name | Description | |------|-------------| | [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | | [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | | [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | | [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | | [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [services](#output\_services) | Map of services created and their attributes | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | ## Authors @@ -252,4 +232,4 @@ Module is maintained by [Anton Babenko](https://github.com/antonbabenko) with he ## License -Apache 2 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/LICENSE) for full details. +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..2d93847 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,289 @@ +# Design + +This document is intended to describe the decisions and assumptions that went into the current design of the ECS module(s). Various concepts of Amazon ECS are covered here to help align readers with the service and its capabilities and how they influence the design provided by this project. + +

+ ECS Design +

+ +## ECS Constructs + +At a high level, an Amazon ECS cluster is comprised of a cluster, services, and tasks. The relationship between these constructs is described below: + +- A cluster may contain one or more services +- A service may contain one or more task definitions/sets +- A task may contain one to ten (max) containers + +Multiple task definition/sets per service is not a common pattern used today. Therefore, this module makes the assumption that a service will contain one task definition/set. All other relationships described above are supported by this project. + +### Cluster + +An Amazon ECS cluster is a logical grouping of compute resources which are consumed by tasks. Compute resources are provided via capacity providers - users can elect to choose an EC2 capacity provider or Fargate capacity providers (spot or on-demand). A capacity provider strategy determines how the tasks will be distributed among capacity providers. A default capacity provider strategy can be defined at cluster level. Note you **can not** mix EC2 based capacity provider with Fargate. + +### Service + +An Amazon ECS service is responsible for managing its associated tasks (via task definition/task set) - the lifecycle of its tasks, the manner in which they are provisioned, their deployment model, network connectivity, etc. As stated previously, Amazon ECS services support one or more task definitions/sets per service. However, this module assumes a service will contain only one task definition/set. Some Amazon ECS service concepts to note: + +- When using an external deployment controller, most of the configurations that are normally specified in the service definition are instead specified in the task set. (For users of this project, this is abstracted away and handled by the module). +- The service IAM role is used to manage the lifecycle of load balancer targets (register/deregister) for the service. If the service does not utilize a load balancer, this role is not required. + +### Task + +Tasks in Amazon ECS can be configured via 3 different methods: + +1. From a task definition +2. From a task set (which also requires a task Definition) +3. From the ECS RunTask API (not supported by this module, but users can use the API to deploy tasks onto cluster resources created by this project) + +Task sets are required when using an external deployment controller (i.e. - something outside of Amazon ECS will manage the lifecycle of updating tasks when changes are made). Task definitions are always required, even when using task sets. + +A task wraps one or more container definitions (up to a max of 10 container definitions per task). You can think of a task as being synonymous to a pod in Kubernetes, and task definition/set being synonymous with a pod spec in Kubernetes. + +Within the construct of tasks, there are two different IAM roles that can be configured: + +1. The task execution IAM role provides permissions for Amazon ECS to access AWS resources specified within the task definition/set. When tasks are created, if a task requires access to SSM parameters and/or secrets in SecretsManager as defined in the task definition/set, the task execution role is used by Amazon ECS to retrieve those values and provide them to the tasks as environment variables. Task execution role permissions are not accessible by the containers within the task during runtime; they are only used during task creation. +2. The tasks IAM role provides permissions for the containers within the task to access AWS resources at runtime. If the application running within the container requires access to AWS resources such as retrieving files from S3 or connecting to RDS using IAM Auth, the tasks IAM role is used to provide those permissions. You can think of the tasks IAM role as being similar to the IAM instance profile used on EC2 instances, or IAM role for service accounts used by pods on Amazon EKS. + +## Module Constructs + +The following constructs are supported by this project. Please see their respective details below for more information on the design considerations, assumptions, and limitations. + +### Cluster + +The cluster sub-module creates an Amazon ECS cluster. With it, users are able to: + +- Create an Amazon ECS cluster +- Enable EC2, Fargate on-demand, and/or Fargate spot capacity providers for the cluster +- Create and manage a CloudWatch log group for the cluster +- Create a task execution IAM role with the ability to extend/add permissions as necessary + +When opting for EC2 capacity provider(s), users can utilize the [`terraform-aws-autoscaling` module](https://github.com/terraform-aws-modules/terraform-aws-autoscaling) to create the necessary autoscaling groups and associate them with the cluster. See the [`ec2-autoscaling` example](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) which demonstrates this configuration. + +This module supports creating a task execution IAM role in two different ways to support two common patterns used by Amazon ECS users: + +1. Users can create one task execution IAM role and re-use, or share, this role across all services within the cluster. This pattern is commonly used when a cluster is used by one team (each team gets their own cluster). When using this pattern, users can elect to create the task execution IAM role from within the cluster module. +2. Users can create one task execution IAM role per service. This pattern is commonly used when a cluster is used by multiple teams (each team gets their own service within the cluster), and access to SSM parameters and/or secrets need to be scoped per service and therefore, per task execution IAM role. When using this pattern, users can elect to create the task execution IAM role from within the service module. + +

+ ECS Cluster +

+ +### Service + +The service sub-module creates one service that can be deployed onto a cluster. The service sub-module allows users to: + +- Create an Amazon ECS service that ignores `desired_count`. This is intended for use when deploying task definition and container definition changes via Terraform +- Create an Amazon ECS service that ignores `desired_count` and `task_definition`. This is intended to support a continuous deployment process that is responsible for updating the `image` and therefore the `task_definition` and `container_definition` while avoiding conflicts with Terraform. +- Amazon ECS task resources with the various configurations detailed below under [ECS Task](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md#ecs-task) + +Since Terraform does not support variables within `lifecycle {}` blocks, its not possible to allow users to dynamically select which arguments they wish to ignore within the resources defined in the modules. Therefore, any arguments that should be ignored are statically set within the module definition. To somewhat mimic the behavior of allowing users to opt in/out of ignoring certain arguments, the module supports two different service definitions; one that ignores the `desired_count`, and one that ignores the `desired_count` and `task_definition`. The motivation and reasoning for these ignored argument configurations is detailed below: + +- `desired_count` is always ignored by the service module. It is very common to have autoscaling enabled for Amazon ECS services, allowing the number of tasks to scale based on the workload requirements. The scaling is managed via the `desired_count` that is managed by application auto scaling. This would directly conflict with Terraform if it was allowed to manage the `desired_count` as well. In addition, users have the ability to disable auto scaling if it does not suit their workload. In this case, the `desired_count` would be initially set by Terraform, and any further changes would need to be managed separately (outside of the service module). Users can make changes to the desired count of the service through the AWS console, AWS CLI, or AWS SDKs. One example workaround using Terraform is provided below, similar to the [EKS equivalent](https://github.com/bryantbiggs/eks-desired-size-hack): + + ```hcl + resource "null_resource" "update_desired_count" { + triggers = { + # Changes to this value will trigger the API call execution below + desired_count = 3 + } + + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + + # Note: this requires the awscli to be installed locally where Terraform is executed + command = <<-EOT + aws ecs update-service \ + --cluster ${module.ecs.cluster_name} \ + --service ${module.ecs_service.name} \ + --desired-count ${null_resource.update_desired_count.triggers.desired_count} + EOT + } + } + ``` + +- In addition to always ignoring `desired_count`, users can elect to ignore changes to task definitions by setting `ignore_task_definition_changes` to `true`. (Note: because of the aforementioned manner in which this psuedo-dynamic ignore change is being managed, changing this value after service creation will cause the entire service to be re-created. Change with caution!) This is primarily intended to support the external deployment controller as well as continuous delivery patterns where an [external process is changing the task definition](https://github.com/aws-actions/amazon-ecs-deploy-task-definition). The entire task definition argument is ignored due to the fact that any changes within the task definition, including changes to the container definition(s), results in a new task definition revision. As an alternative, this module does provide a work around that would support an external party making changes to something like the `image` of the container definition. In a scenario where there the service, task definition, and container definition are all managed by Terraform, the following configuration could be used to allow an external party to change the `image` of the container definition without conflicting with Terraform, provided that the external party is also updating the image tag in a shared location that can be retrieved by Terraform (here, we are using SSM Parameter Store): + + ```hcl + data "aws_ssm_parameter" "app_image_tag" { + name = "/my-app/image-tag" + } + + data "aws_ecr_repository" "app" { + name = "my-app-repository" + } + + data "aws_ecr_image" "app" { + repository_name = data.aws_ecr_repository.app.name + image_tag = data.aws_ssm_parameter.app_image_tag.value + } + + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + name = data.aws_ecr_repository.app.name + image = "${data.aws_ecr_repository.app.repository_url}@${data.aws_ecr_image.app.id}" + # ... + } + } + + # This is the default, but just to clarify on this example that we are NOT ignoring + # task definition changes, and still allowing an external party to modify the image/tag + # without conflicting with Terraform + ignore_task_definition_changes = false + } + ``` + +This could be expanded further to include the entire container definitions argument, provided that external party making changes to the container definitions provides a copy that Terraform can retrieve and use when making updates to task definitions. This is possible due to the use of the `aws_ecs_task_definition` data source in the module - the data source retrieves the task definition currently defined in AWS. Using the `max()` function in Terraform, we can get the latest version via `max(aws_ecs_task_definition.this[0].revision, data.aws_ecs_task_definition.this[0].revision)` and use that as the `task_definition` value through reconstructing the `family:revision` format such as `"${aws_ecs_task_definition.this[0].family}:${local.max_task_def_revision}"`. With this work around, any changes made by Terraform would result in a new task definition revision resulting in Terraform updating the revision and deploying that change into the cluster. Likewise, any external party making changes to the task definition will increment the revision and deploy that into the cluster. Provided that Terraform can refer to the same values of the arguments changed by the external party, Terraform will be able to have a complete view of the current status and only make changes when necessary, without conflicting with the external party. + +

+ ECS Service +

+ +### Task + +ECS tasks are the byproduct of task definitions and task sets. In addition to what has been described above, the service module supports the following task level configurations: + +- Create task definition with support for n-number of container definitions (min 1, max 10 per AWS API) +- Create task execution IAM role and associated permissions. This is one of two routes previously described for creating a task execution role, where creating it at the service level is primarily intended to ensure parameters and secrets are only accessible by the tasks created in the respective service, and not shared across all services. +- Create tasks IAM role & permissions used by the containers during runtime +- Create task set that ignores `scale`; this is equivalent to the service and its ignored `desired_count` +- Create task set that ignores `scale` and `task_definition`; again, equivalent to the service and its ignored `desired_count` and `ignore_task_definition_changes` +- Create an application autoscaling target, policy, and schedule action to autoscale the number of tasks + +The same workaround described above where an external party can modify the task definition without conflicting with Terraform can be used here as well since task sets require a task definition and the module's implementation uses the same one throughout. + +Application auto scaling is enabled by default and can be disabled by setting `enable_autoscaling` to `false`. See the mentioned work around above for adjusting the `desired_count` of the service to adjust the number of tasks. The equivalent for task set using an external deployment controller is provided below: + + ```hcl + resource "null_resource" "update_scale" { + triggers = { + # Changes to this value will trigger the API call execution below + scale = "value=50,unit=PERCENT" + } + + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + + # Note: this requires the awscli to be installed locally where Terraform is executed + command = <<-EOT + aws ecs update-task-set \ + --cluster ${module.ecs.cluster_name} \ + --service ${module.ecs_service.name} \ + --task-set ${module.ecs_service.task_set_arn} \ + --scale ${null_resource.update_scale.triggers.scale} + EOT + } + } + ``` + +

+ ECS Task +

+ +#### Container Definition + +The container definition sub-module provided by the project is intended to be used as a building block for the container definitions argument of the task definition and to fill the gap [until a native Terraform resource is provided](https://github.com/hashicorp/terraform-provider-aws/issues/17988) for container definitions. + +Within a container definition, there are primarily 2 ways in which logs are created/managed: + +1. Via Cloudwatch logs: When users provide a name to `awslogs-group`, if the CloudWatch log group does not exists, ECS will create it. This means it is created outside of IaC and therefore will not be deleted when the task/container definition is deleted, nor tagged, etc. +2. Via Firelens: Firelens allows users to provide a configuration that will forward logs to various locations, including 3rd party locations. When using Firelens though, users will need to add a FluentBit sidecar to forward logs to the Firelens service which will send logs to the final destination based on the `firelensConfiguration` + +In this module we aim to provide support for both, but with the addition of affording users the ability to manage the Cloudwatch log group through Terraform. This is the similar scenario we face with other modules as well - RDS, Lambda, EKS - that will automatically create log groups when logging is enabled. This sub-module provides support for creating the Cloudwatch log groups so that users can tag the log groups, set the retention period, , encrypt the log groups with KMS, ensure logs are destroyed when the resources are, etc. This is the reason why the Cloudwatch log resource is provided in this sub-module; to allow users to control the log group through Terraform/IaC + +The default behavior of the container definition module is to create the CloudWatch log group on behalf of users to ensure it can be fully managed through Terraform. This leads to the following scenarios for logging: + +1. Disable logging entirely + + ```hcl + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + enable_cloudwatch_logging = false + # ... + } + } + } + ``` + +2. Use CloudWatch logging - opt out of Terraform managing the Cloudwatch log group and instead ECS will create the log group: + + ```hcl + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + create_cloudwatch_log_group = false + # ... + } + } + } + ``` + +3. [Default] Use CloudWatch logging - Let Terraform manage the Cloudwatch log group: + + ```hcl + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + default = { + # ... + } + } + } + ``` + +4. Use Firelens for log forwarding: + + ```hcl + data "aws_ssm_parameter" "fluentbit" { + name = "/aws/service/aws-for-fluent-bit/stable" + } + + module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # ... omitted for brevity + + container_definitions = { + # FluentBit sidecar is required for Firelens + fluent-bit = { + image = data.aws_ssm_parameter.fluentbit.value + firelens_configuration = { + type = "fluentbit" + } + # ... + } + + default = { + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + # ... + } + } + # ... + } + } + } + ``` diff --git a/docs/images/cluster.png b/docs/images/cluster.png new file mode 100644 index 0000000000000000000000000000000000000000..baa5862e9a7e98e552e08d90a5c3c79b0d248fd6 GIT binary patch literal 71466 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

tnoyeMi2OSId`vTG+FfAcXv^_e&0 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

|;+BA`Rs~Lw%nwTWI{Xz%ItYd%-}h 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≤BVdq&qB;}vR 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 literal 0 HcmV?d00001 diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f417c0a --- /dev/null +++ b/examples/README.md @@ -0,0 +1,8 @@ +# Examples + +Please note - the examples provided serve two primary means: + +1. Show users working examples of the various ways in which the module can be configured and features supported +2. A means of testing/validating module changes + +Please do not mistake the examples provided as "best practices". It is up to users to consult the AWS service documentation for best practices, usage recommendations, etc. diff --git a/examples/complete/README.md b/examples/complete/README.md index 977980b..cc152c7 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -1,10 +1,13 @@ -# ECS Cluster w/ EC2 Autoscaling +# ECS Cluster Complete Configuration in this directory creates: -- ECS cluster using autoscaling group capacity provider -- Autoscaling groups with IAM instance profile to be used by ECS cluster -- Example ECS service +- ECS cluster using Fargate (on-demand and spot) capacity providers +- Example ECS service that utilizes + - AWS Firelens using FluentBit sidecar container definition + - Service connect configuration + - Load balancer target group attachment + - Security group for access to the example service ## Usage @@ -24,31 +27,32 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.6 | +| [aws](#requirement\_aws) | >= 4.55 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.6 | +| [aws](#provider\_aws) | >= 4.55 | ## Modules | Name | Source | Version | |------|--------|---------| -| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | -| [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 4.0 | -| [ecs](#module\_ecs) | ../.. | n/a | -| [ecs\_disabled](#module\_ecs\_disabled) | ../.. | n/a | -| [hello\_world](#module\_hello\_world) | ./service-hello-world | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 3.0 | +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 8.0 | +| [alb\_sg](#module\_alb\_sg) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [ecs](#module\_ecs) | ../../ | n/a | +| [ecs\_cluster\_disabled](#module\_ecs\_cluster\_disabled) | ../../modules/cluster | n/a | +| [ecs\_disabled](#module\_ecs\_disabled) | ../../ | n/a | +| [service\_disabled](#module\_service\_disabled) | ../../modules/service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0 | ## Resources | Name | Type | |------|------| -| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | -| [aws_ssm_parameter.ecs_optimized_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | +| [aws_service_discovery_http_namespace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_http_namespace) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | ## Inputs @@ -58,9 +62,14 @@ No inputs. | Name | Description | |------|-------------| -| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | | [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | | [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | | [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | | [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [services](#output\_services) | Map of services created and their attributes | + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 9c70e5e..d81da6c 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -2,17 +2,17 @@ provider "aws" { region = local.region } +data "aws_availability_zones" "available" {} + locals { region = "eu-west-1" - name = "ecs-ex-${replace(basename(path.cwd), "_", "-")}" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) - user_data = <<-EOT - #!/bin/bash - cat <<'EOF' >> /etc/ecs/ecs.config - ECS_CLUSTER=${local.name} - ECS_LOGLEVEL=debug - EOF - EOT + container_name = "ecsdemo-frontend" + container_port = 3000 tags = { Name = local.name @@ -22,65 +22,121 @@ locals { } ################################################################################ -# ECS Module +# Cluster ################################################################################ module "ecs" { - source = "../.." + source = "../../" cluster_name = local.name - cluster_configuration = { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - # You can set a simple string and ECS will create the CloudWatch log group for you - # or you can create the resource yourself as shown here to better manage retetion, tagging, etc. - # Embedding it into the module is not trivial and therefore it is externalized - cloud_watch_log_group_name = aws_cloudwatch_log_group.this.name + # Capacity provider + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + base = 20 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 } } } - default_capacity_provider_use_fargate = false - - # Capacity provider - Fargate - fargate_capacity_providers = { - FARGATE = {} - FARGATE_SPOT = {} - } - - # Capacity provider - autoscaling groups - autoscaling_capacity_providers = { - one = { - auto_scaling_group_arn = module.autoscaling["one"].autoscaling_group_arn - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 5 - minimum_scaling_step_size = 1 - status = "ENABLED" - target_capacity = 60 + services = { + ecsdemo-frontend = { + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-observability/aws-for-fluent-bit:2.31.9" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + (local.container_name) = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = local.container_name + containerPort = local.container_port + hostPort = local.container_port + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = local.region + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } } - default_capacity_provider_strategy = { - weight = 60 - base = 20 + service_connect_configuration = { + namespace = aws_service_discovery_http_namespace.this.arn + service = { + client_alias = { + port = local.container_port + dns_name = local.container_name + } + port_name = local.container_name + discovery_name = local.container_name + } } - } - two = { - auto_scaling_group_arn = module.autoscaling["two"].autoscaling_group_arn - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 15 - minimum_scaling_step_size = 5 - status = "ENABLED" - target_capacity = 90 + + load_balancer = { + service = { + target_group_arn = element(module.alb.target_group_arns, 0) + container_name = local.container_name + container_port = local.container_port + } } - default_capacity_provider_strategy = { - weight = 40 + subnet_ids = module.vpc.private_subnets + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = local.container_port + to_port = local.container_port + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb_sg.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } } } } @@ -88,14 +144,20 @@ module "ecs" { tags = local.tags } -module "hello_world" { - source = "./service-hello-world" +module "ecs_disabled" { + source = "../../" - cluster_id = module.ecs.cluster_id + create = false } -module "ecs_disabled" { - source = "../.." +module "ecs_cluster_disabled" { + source = "../../modules/cluster" + + create = false +} + +module "service_disabled" { + source = "../../modules/service" create = false } @@ -104,96 +166,74 @@ module "ecs_disabled" { # Supporting Resources ################################################################################ -# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux -data "aws_ssm_parameter" "ecs_optimized_ami" { - name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +resource "aws_service_discovery_http_namespace" "this" { + name = local.name + description = "CloudMap namespace for ${local.name}" + tags = local.tags } -module "autoscaling" { - source = "terraform-aws-modules/autoscaling/aws" - version = "~> 6.5" - - for_each = { - one = { - instance_type = "t3.micro" - } - two = { - instance_type = "t3.small" - } - } - - name = "${local.name}-${each.key}" - - image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] - instance_type = each.value.instance_type - - security_groups = [module.autoscaling_sg.security_group_id] - user_data = base64encode(local.user_data) - ignore_desired_capacity_changes = true - - create_iam_instance_profile = true - iam_role_name = local.name - iam_role_description = "ECS role for ${local.name}" - iam_role_policies = { - AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" - AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" - } +module "alb_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" - vpc_zone_identifier = module.vpc.private_subnets - health_check_type = "EC2" - min_size = 0 - max_size = 2 - desired_capacity = 1 + name = "${local.name}-service" + description = "Service security group" + vpc_id = module.vpc.vpc_id - # https://github.com/hashicorp/terraform-provider-aws/issues/12582 - autoscaling_group_tags = { - AmazonECSManaged = true - } + ingress_rules = ["http-80-tcp"] + ingress_cidr_blocks = ["0.0.0.0/0"] - # Required for managed_termination_protection = "ENABLED" - protect_from_scale_in = true + egress_rules = ["all-all"] + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks tags = local.tags } -module "autoscaling_sg" { - source = "terraform-aws-modules/security-group/aws" - version = "~> 4.0" - - name = local.name - description = "Autoscaling group security group" - vpc_id = module.vpc.vpc_id +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 8.0" - ingress_cidr_blocks = ["0.0.0.0/0"] - ingress_rules = ["https-443-tcp"] + name = local.name - egress_rules = ["all-all"] + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + security_groups = [module.alb_sg.security_group_id] + + http_tcp_listeners = [ + { + port = 80 + protocol = "HTTP" + target_group_index = 0 + }, + ] + + target_groups = [ + { + name = "${local.name}-${local.container_name}" + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + }, + ] tags = local.tags } module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" + version = "~> 4.0" name = local.name - cidr = "10.99.0.0/18" - - azs = ["${local.region}a", "${local.region}b", "${local.region}c"] - public_subnets = ["10.99.0.0/24", "10.99.1.0/24", "10.99.2.0/24"] - private_subnets = ["10.99.3.0/24", "10.99.4.0/24", "10.99.5.0/24"] + cidr = local.vpc_cidr - enable_nat_gateway = true - single_nat_gateway = true - enable_dns_hostnames = true - map_public_ip_on_launch = false - - tags = local.tags -} + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] -resource "aws_cloudwatch_log_group" "this" { - name = "/aws/ecs/${local.name}" - retention_in_days = 7 + enable_nat_gateway = true + single_nat_gateway = true tags = local.tags } diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index 4436a96..3503169 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -17,20 +17,21 @@ output "cluster_name" { value = module.ecs.cluster_name } -################################################################################ -# Cluster Capacity Providers -################################################################################ - output "cluster_capacity_providers" { description = "Map of cluster capacity providers attributes" value = module.ecs.cluster_capacity_providers } +output "cluster_autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs.autoscaling_capacity_providers +} + ################################################################################ -# Capacity Provider +# Service(s) ################################################################################ -output "autoscaling_capacity_providers" { - description = "Map of capacity providers created and their attributes" - value = module.ecs.autoscaling_capacity_providers +output "services" { + description = "Map of services created and their attributes" + value = module.ecs.services } diff --git a/examples/complete/service-hello-world/main.tf b/examples/complete/service-hello-world/main.tf deleted file mode 100644 index b9661bb..0000000 --- a/examples/complete/service-hello-world/main.tf +++ /dev/null @@ -1,38 +0,0 @@ -resource "aws_cloudwatch_log_group" "this" { - name_prefix = "hello_world-" - retention_in_days = 1 -} - -resource "aws_ecs_task_definition" "this" { - family = "hello_world" - - container_definitions = < +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.55 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 8.0 | +| [alb\_sg](#module\_alb\_sg) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | +| [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | ../../modules/cluster | n/a | +| [ecs\_service](#module\_ecs\_service) | ../../modules/service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_ssm_parameter.ecs_optimized_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | +| [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [service\_autoscaling\_policies](#output\_service\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [service\_autoscaling\_scheduled\_actions](#output\_service\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [service\_container\_definitions](#output\_service\_container\_definitions) | Container definitions | +| [service\_iam\_role\_arn](#output\_service\_iam\_role\_arn) | Service IAM role ARN | +| [service\_iam\_role\_name](#output\_service\_iam\_role\_name) | Service IAM role name | +| [service\_iam\_role\_unique\_id](#output\_service\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [service\_id](#output\_service\_id) | ARN that identifies the service | +| [service\_name](#output\_service\_name) | Name of the service | +| [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [service\_task\_definition\_revision](#output\_service\_task\_definition\_revision) | Revision of the task in a particular family | +| [service\_task\_exec\_iam\_role\_arn](#output\_service\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [service\_task\_exec\_iam\_role\_name](#output\_service\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [service\_task\_exec\_iam\_role\_unique\_id](#output\_service\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [service\_task\_set\_arn](#output\_service\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [service\_task\_set\_id](#output\_service\_task\_set\_id) | The ID of the task set | +| [service\_task\_set\_stability\_status](#output\_service\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [service\_task\_set\_status](#output\_service\_task\_set\_status) | The status of the task set | +| [service\_tasks\_iam\_role\_arn](#output\_service\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [service\_tasks\_iam\_role\_name](#output\_service\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [service\_tasks\_iam\_role\_unique\_id](#output\_service\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/ec2-autoscaling/main.tf b/examples/ec2-autoscaling/main.tf new file mode 100644 index 0000000..5cd2e2c --- /dev/null +++ b/examples/ec2-autoscaling/main.tf @@ -0,0 +1,288 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" {} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + container_name = "ecs-sample" + container_port = 80 + + user_data = <<-EOT + #!/bin/bash + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + EOF + EOT + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Cluster +################################################################################ + +module "ecs_cluster" { + source = "../../modules/cluster" + + cluster_name = local.name + + # Capacity provider - autoscaling groups + default_capacity_provider_use_fargate = false + autoscaling_capacity_providers = { + one = { + auto_scaling_group_arn = module.autoscaling["one"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + two = { + auto_scaling_group_arn = module.autoscaling["two"].autoscaling_group_arn + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = local.tags +} + +################################################################################ +# Service +################################################################################ + +module "ecs_service" { + source = "../../modules/service" + + # Service + name = local.name + cluster_arn = module.ecs_cluster.arn + + # Task Definition + requires_compatibilities = ["EC2"] + launch_type = "EC2" + volume = { + my-vol = {} + } + + # Container definition(s) + container_definitions = { + (local.container_name) = { + image = "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest" + port_mappings = [ + { + name = local.container_name + containerPort = local.container_port + protocol = "tcp" + } + ] + + mount_points = [ + { + sourceVolume = "my-vol", + containerPath = "/var/www/my-vol" + } + ] + + entry_point = ["/usr/sbin/apache2", "-D", "FOREGROUND"] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + } + } + + load_balancer = { + service = { + target_group_arn = element(module.alb.target_group_arns, 0) + container_name = local.container_name + container_port = local.container_port + } + } + + subnet_ids = module.vpc.private_subnets + security_group_rules = { + alb_http_ingress = { + type = "ingress" + from_port = local.container_port + to_port = local.container_port + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb_sg.security_group_id + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" +} + +module "alb_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "${local.name}-service" + description = "Service security group" + vpc_id = module.vpc.vpc_id + + ingress_rules = ["http-80-tcp"] + ingress_cidr_blocks = ["0.0.0.0/0"] + + egress_rules = ["all-all"] + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks + + tags = local.tags +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 8.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + security_groups = [module.alb_sg.security_group_id] + + http_tcp_listeners = [ + { + port = local.container_port + protocol = "HTTP" + target_group_index = 0 + }, + ] + + target_groups = [ + { + name = "${local.name}-${local.container_name}" + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + }, + ] + + tags = local.tags +} + +module "autoscaling" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 6.5" + + for_each = { + one = { + instance_type = "t3.small" + } + two = { + instance_type = "t3.medium" + } + } + + name = "${local.name}-${each.key}" + + image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] + instance_type = each.value.instance_type + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(local.user_data) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + vpc_zone_identifier = module.vpc.private_subnets + health_check_type = "EC2" + min_size = 1 + max_size = 5 + desired_capacity = 2 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + + # Required for managed_termination_protection = "ENABLED" + protect_from_scale_in = true + + tags = local.tags +} + +module "autoscaling_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = local.name + description = "Autoscaling group security group" + vpc_id = module.vpc.vpc_id + + computed_ingress_with_source_security_group_id = [ + { + rule = "http-80-tcp" + source_security_group_id = module.alb_sg.security_group_id + } + ] + number_of_computed_ingress_with_source_security_group_id = 1 + + egress_rules = ["all-all"] + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 4.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + tags = local.tags +} diff --git a/examples/ec2-autoscaling/outputs.tf b/examples/ec2-autoscaling/outputs.tf new file mode 100644 index 0000000..2f6f85a --- /dev/null +++ b/examples/ec2-autoscaling/outputs.tf @@ -0,0 +1,132 @@ +################################################################################ +# Cluster +################################################################################ + +output "cluster_arn" { + description = "ARN that identifies the cluster" + value = module.ecs_cluster.arn +} + +output "cluster_id" { + description = "ID that identifies the cluster" + value = module.ecs_cluster.id +} + +output "cluster_name" { + description = "Name that identifies the cluster" + value = module.ecs_cluster.name +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs_cluster.cluster_capacity_providers +} + +output "cluster_autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs_cluster.autoscaling_capacity_providers +} + +################################################################################ +# Service +################################################################################ + +output "service_id" { + description = "ARN that identifies the service" + value = module.ecs_service.id +} + +output "service_name" { + description = "Name of the service" + value = module.ecs_service.name +} + +output "service_iam_role_name" { + description = "Service IAM role name" + value = module.ecs_service.iam_role_name +} + +output "service_iam_role_arn" { + description = "Service IAM role ARN" + value = module.ecs_service.iam_role_arn +} + +output "service_iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = module.ecs_service.iam_role_unique_id +} + +output "service_container_definitions" { + description = "Container definitions" + value = module.ecs_service.container_definitions +} + +output "service_task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = module.ecs_service.task_definition_arn +} + +output "service_task_definition_revision" { + description = "Revision of the task in a particular family" + value = module.ecs_service.task_definition_revision +} + +output "service_task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_service.task_exec_iam_role_name +} + +output "service_task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_service.task_exec_iam_role_arn +} + +output "service_task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = module.ecs_service.task_exec_iam_role_unique_id +} + +output "service_tasks_iam_role_name" { + description = "Tasks IAM role name" + value = module.ecs_service.tasks_iam_role_name +} + +output "service_tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = module.ecs_service.tasks_iam_role_arn +} + +output "service_tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = module.ecs_service.tasks_iam_role_unique_id +} + +output "service_task_set_id" { + description = "The ID of the task set" + value = module.ecs_service.task_set_id +} + +output "service_task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = module.ecs_service.task_set_arn +} + +output "service_task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = module.ecs_service.task_set_stability_status +} + +output "service_task_set_status" { + description = "The status of the task set" + value = module.ecs_service.task_set_status +} + +output "service_autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = module.ecs_service.autoscaling_policies +} + +output "service_autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = module.ecs_service.autoscaling_scheduled_actions +} diff --git a/examples/complete/service-hello-world/outputs.tf b/examples/ec2-autoscaling/variables.tf similarity index 100% rename from examples/complete/service-hello-world/outputs.tf rename to examples/ec2-autoscaling/variables.tf diff --git a/examples/complete/service-hello-world/versions.tf b/examples/ec2-autoscaling/versions.tf similarity index 82% rename from examples/complete/service-hello-world/versions.tf rename to examples/ec2-autoscaling/versions.tf index 35402be..290d221 100644 --- a/examples/complete/service-hello-world/versions.tf +++ b/examples/ec2-autoscaling/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.6" + version = ">= 4.55" } } } diff --git a/examples/fargate/README.md b/examples/fargate/README.md index 7d912b7..c786209 100644 --- a/examples/fargate/README.md +++ b/examples/fargate/README.md @@ -2,7 +2,12 @@ Configuration in this directory creates: -- ECS cluster using Fargate (on-demand and spot) capacity provider +- ECS cluster using Fargate (on-demand and spot) capacity providers +- Example ECS service that utilizes + - AWS Firelens using FluentBit sidecar container definition + - Service connect configuration + - Load balancer target group attachment + - Security group for access to the example service ## Usage @@ -22,26 +27,30 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.6 | +| [aws](#requirement\_aws) | >= 4.55 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.6 | +| [aws](#provider\_aws) | >= 4.55 | ## Modules | Name | Source | Version | |------|--------|---------| -| [ecs](#module\_ecs) | ../.. | n/a | -| [ecs\_disabled](#module\_ecs\_disabled) | ../.. | n/a | +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 8.0 | +| [alb\_sg](#module\_alb\_sg) | terraform-aws-modules/security-group/aws | ~> 4.0 | +| [ecs\_cluster](#module\_ecs\_cluster) | ../../modules/cluster | n/a | +| [ecs\_service](#module\_ecs\_service) | ../../modules/service | n/a | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 4.0 | ## Resources | Name | Type | |------|------| -| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_service_discovery_http_namespace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/service_discovery_http_namespace) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | ## Inputs @@ -51,9 +60,33 @@ No inputs. | Name | Description | |------|-------------| -| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | | [cluster\_arn](#output\_cluster\_arn) | ARN that identifies the cluster | +| [cluster\_autoscaling\_capacity\_providers](#output\_cluster\_autoscaling\_capacity\_providers) | Map of capacity providers created and their attributes | | [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | | [cluster\_id](#output\_cluster\_id) | ID that identifies the cluster | | [cluster\_name](#output\_cluster\_name) | Name that identifies the cluster | +| [service\_autoscaling\_policies](#output\_service\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [service\_autoscaling\_scheduled\_actions](#output\_service\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [service\_container\_definitions](#output\_service\_container\_definitions) | Container definitions | +| [service\_iam\_role\_arn](#output\_service\_iam\_role\_arn) | Service IAM role ARN | +| [service\_iam\_role\_name](#output\_service\_iam\_role\_name) | Service IAM role name | +| [service\_iam\_role\_unique\_id](#output\_service\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [service\_id](#output\_service\_id) | ARN that identifies the service | +| [service\_name](#output\_service\_name) | Name of the service | +| [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [service\_task\_definition\_revision](#output\_service\_task\_definition\_revision) | Revision of the task in a particular family | +| [service\_task\_exec\_iam\_role\_arn](#output\_service\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [service\_task\_exec\_iam\_role\_name](#output\_service\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [service\_task\_exec\_iam\_role\_unique\_id](#output\_service\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [service\_task\_set\_arn](#output\_service\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [service\_task\_set\_id](#output\_service\_task\_set\_id) | The ID of the task set | +| [service\_task\_set\_stability\_status](#output\_service\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [service\_task\_set\_status](#output\_service\_task\_set\_status) | The status of the task set | +| [service\_tasks\_iam\_role\_arn](#output\_service\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [service\_tasks\_iam\_role\_name](#output\_service\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [service\_tasks\_iam\_role\_unique\_id](#output\_service\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/fargate/main.tf b/examples/fargate/main.tf index f800c3e..8566fdd 100644 --- a/examples/fargate/main.tf +++ b/examples/fargate/main.tf @@ -2,9 +2,17 @@ provider "aws" { region = local.region } +data "aws_availability_zones" "available" {} + locals { region = "eu-west-1" - name = "ecs-ex-${replace(basename(path.cwd), "_", "-")}" + name = "ex-${basename(path.cwd)}" + + vpc_cidr = "10.0.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + container_name = "ecsdemo-frontend" + container_port = 3000 tags = { Name = local.name @@ -14,26 +22,14 @@ locals { } ################################################################################ -# Ecs Module +# Cluster ################################################################################ -module "ecs" { - source = "../.." +module "ecs_cluster" { + source = "../../modules/cluster" cluster_name = local.name - cluster_configuration = { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - # You can set a simple string and ECS will create the CloudWatch log group for you - # or you can create the resource yourself as shown here to better manage retetion, tagging, etc. - # Embedding it into the module is not trivial and therefore it is externalized - cloud_watch_log_group_name = aws_cloudwatch_log_group.this.name - } - } - } - # Capacity provider fargate_capacity_providers = { FARGATE = { @@ -52,19 +48,183 @@ module "ecs" { tags = local.tags } -module "ecs_disabled" { - source = "../.." +################################################################################ +# Service +################################################################################ + +module "ecs_service" { + source = "../../modules/service" + + name = local.name + cluster_arn = module.ecs_cluster.arn + + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-observability/aws-for-fluent-bit:2.31.9" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + (local.container_name) = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = local.container_name + containerPort = local.container_port + hostPort = local.container_port + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = local.region + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } + } + + service_connect_configuration = { + namespace = aws_service_discovery_http_namespace.this.arn + service = { + client_alias = { + port = local.container_port + dns_name = local.container_name + } + port_name = local.container_name + discovery_name = local.container_name + } + } + + load_balancer = { + service = { + target_group_arn = element(module.alb.target_group_arns, 0) + container_name = local.container_name + container_port = local.container_port + } + } + + subnet_ids = module.vpc.private_subnets + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = local.container_port + to_port = local.container_port + protocol = "tcp" + description = "Service port" + source_security_group_id = module.alb_sg.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } - create = false + tags = local.tags } ################################################################################ # Supporting Resources ################################################################################ -resource "aws_cloudwatch_log_group" "this" { - name = "/aws/ecs/${local.name}" - retention_in_days = 7 +resource "aws_service_discovery_http_namespace" "this" { + name = local.name + description = "CloudMap namespace for ${local.name}" + tags = local.tags +} + +module "alb_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 4.0" + + name = "${local.name}-service" + description = "Service security group" + vpc_id = module.vpc.vpc_id + + ingress_rules = ["http-80-tcp"] + ingress_cidr_blocks = ["0.0.0.0/0"] + + egress_rules = ["all-all"] + egress_cidr_blocks = module.vpc.private_subnets_cidr_blocks + + tags = local.tags +} + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 8.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + security_groups = [module.alb_sg.security_group_id] + + http_tcp_listeners = [ + { + port = 80 + protocol = "HTTP" + target_group_index = 0 + }, + ] + + target_groups = [ + { + name = "${local.name}-${local.container_name}" + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + }, + ] + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 4.0" + + name = local.name + cidr = local.vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true tags = local.tags } diff --git a/examples/fargate/outputs.tf b/examples/fargate/outputs.tf index 4436a96..2f6f85a 100644 --- a/examples/fargate/outputs.tf +++ b/examples/fargate/outputs.tf @@ -4,33 +4,129 @@ output "cluster_arn" { description = "ARN that identifies the cluster" - value = module.ecs.cluster_arn + value = module.ecs_cluster.arn } output "cluster_id" { description = "ID that identifies the cluster" - value = module.ecs.cluster_id + value = module.ecs_cluster.id } output "cluster_name" { description = "Name that identifies the cluster" - value = module.ecs.cluster_name + value = module.ecs_cluster.name } -################################################################################ -# Cluster Capacity Providers -################################################################################ - output "cluster_capacity_providers" { description = "Map of cluster capacity providers attributes" - value = module.ecs.cluster_capacity_providers + value = module.ecs_cluster.cluster_capacity_providers +} + +output "cluster_autoscaling_capacity_providers" { + description = "Map of capacity providers created and their attributes" + value = module.ecs_cluster.autoscaling_capacity_providers } ################################################################################ -# Capacity Provider +# Service ################################################################################ -output "autoscaling_capacity_providers" { - description = "Map of capacity providers created and their attributes" - value = module.ecs.autoscaling_capacity_providers +output "service_id" { + description = "ARN that identifies the service" + value = module.ecs_service.id +} + +output "service_name" { + description = "Name of the service" + value = module.ecs_service.name +} + +output "service_iam_role_name" { + description = "Service IAM role name" + value = module.ecs_service.iam_role_name +} + +output "service_iam_role_arn" { + description = "Service IAM role ARN" + value = module.ecs_service.iam_role_arn +} + +output "service_iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = module.ecs_service.iam_role_unique_id +} + +output "service_container_definitions" { + description = "Container definitions" + value = module.ecs_service.container_definitions +} + +output "service_task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = module.ecs_service.task_definition_arn +} + +output "service_task_definition_revision" { + description = "Revision of the task in a particular family" + value = module.ecs_service.task_definition_revision +} + +output "service_task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_service.task_exec_iam_role_name +} + +output "service_task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_service.task_exec_iam_role_arn +} + +output "service_task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = module.ecs_service.task_exec_iam_role_unique_id +} + +output "service_tasks_iam_role_name" { + description = "Tasks IAM role name" + value = module.ecs_service.tasks_iam_role_name +} + +output "service_tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = module.ecs_service.tasks_iam_role_arn +} + +output "service_tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = module.ecs_service.tasks_iam_role_unique_id +} + +output "service_task_set_id" { + description = "The ID of the task set" + value = module.ecs_service.task_set_id +} + +output "service_task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = module.ecs_service.task_set_arn +} + +output "service_task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = module.ecs_service.task_set_stability_status +} + +output "service_task_set_status" { + description = "The status of the task set" + value = module.ecs_service.task_set_status +} + +output "service_autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = module.ecs_service.autoscaling_policies +} + +output "service_autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = module.ecs_service.autoscaling_scheduled_actions } diff --git a/examples/fargate/versions.tf b/examples/fargate/versions.tf index 35402be..290d221 100644 --- a/examples/fargate/versions.tf +++ b/examples/fargate/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.6" + version = ">= 4.55" } } } diff --git a/main.tf b/main.tf index 0d53949..f182907 100644 --- a/main.tf +++ b/main.tf @@ -2,111 +2,191 @@ # Cluster ################################################################################ -resource "aws_ecs_cluster" "this" { - count = var.create ? 1 : 0 - - name = var.cluster_name - - dynamic "configuration" { - for_each = try([var.cluster_configuration], []) - - content { - dynamic "execute_command_configuration" { - for_each = try([configuration.value.execute_command_configuration], [{}]) - - content { - kms_key_id = try(execute_command_configuration.value.kms_key_id, null) - logging = try(execute_command_configuration.value.logging, "DEFAULT") - - dynamic "log_configuration" { - for_each = try([execute_command_configuration.value.log_configuration], []) - - content { - cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) - cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) - s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) - s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) - s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) - } - } - } - } - } - } - - dynamic "setting" { - for_each = [var.cluster_settings] - - content { - name = setting.value.name - value = setting.value.value - } - } - - tags = var.tags +module "cluster" { + source = "./modules/cluster" + + create = var.create + + # Cluster + cluster_name = var.cluster_name + cluster_configuration = var.cluster_configuration + cluster_settings = var.cluster_settings + cluster_service_connect_defaults = var.cluster_service_connect_defaults + + # Cluster Cloudwatch log group + create_cloudwatch_log_group = var.create_cloudwatch_log_group + cloudwatch_log_group_retention_in_days = var.cloudwatch_log_group_retention_in_days + cloudwatch_log_group_kms_key_id = var.cloudwatch_log_group_kms_key_id + cloudwatch_log_group_tags = var.cloudwatch_log_group_tags + + # Cluster capacity providers + default_capacity_provider_use_fargate = var.default_capacity_provider_use_fargate + fargate_capacity_providers = var.fargate_capacity_providers + autoscaling_capacity_providers = var.autoscaling_capacity_providers + + # Task execution IAM role + create_task_exec_iam_role = var.create_task_exec_iam_role + task_exec_iam_role_name = var.task_exec_iam_role_name + task_exec_iam_role_use_name_prefix = var.task_exec_iam_role_use_name_prefix + task_exec_iam_role_path = var.task_exec_iam_role_path + task_exec_iam_role_description = var.task_exec_iam_role_description + task_exec_iam_role_permissions_boundary = var.task_exec_iam_role_permissions_boundary + task_exec_iam_role_tags = var.task_exec_iam_role_tags + task_exec_iam_role_policies = var.task_exec_iam_role_policies + + # Task execution IAM role policy + create_task_exec_policy = var.create_task_exec_policy + task_exec_ssm_param_arns = var.task_exec_ssm_param_arns + task_exec_secret_arns = var.task_exec_secret_arns + task_exec_iam_statements = var.task_exec_iam_statements + + tags = merge(var.tags, var.cluster_tags) } ################################################################################ -# Cluster Capacity Providers +# Service(s) ################################################################################ -locals { - default_capacity_providers = merge( - { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, - { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } - ) -} - -resource "aws_ecs_cluster_capacity_providers" "this" { - count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 - - cluster_name = aws_ecs_cluster.this[0].name - capacity_providers = distinct(concat( - [for k, v in var.fargate_capacity_providers : try(v.name, k)], - [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] - )) - - dynamic "default_capacity_provider_strategy" { - for_each = local.default_capacity_providers - iterator = strategy - - content { - capacity_provider = try(strategy.value.name, strategy.key) - base = try(strategy.value.default_capacity_provider_strategy.base, null) - weight = try(strategy.value.default_capacity_provider_strategy.weight, null) +module "service" { + source = "./modules/service" + + for_each = { for k, v in var.services : k => v if var.create } + + create = try(each.value.create, true) + + # Service + ignore_task_definition_changes = try(each.value.ignore_task_definition_changes, false) + alarms = try(each.value.alarms, {}) + capacity_provider_strategy = try(each.value.capacity_provider_strategy, {}) + cluster_arn = module.cluster.arn + deployment_circuit_breaker = try(each.value.deployment_circuit_breaker, {}) + deployment_controller = try(each.value.deployment_controller, {}) + deployment_maximum_percent = try(each.value.deployment_maximum_percent, 200) + deployment_minimum_healthy_percent = try(each.value.deployment_minimum_healthy_percent, 66) + desired_count = try(each.value.desired_count, 1) + enable_ecs_managed_tags = try(each.value.enable_ecs_managed_tags, true) + enable_execute_command = try(each.value.enable_execute_command, false) + force_new_deployment = try(each.value.force_new_deployment, true) + health_check_grace_period_seconds = try(each.value.health_check_grace_period_seconds, null) + launch_type = try(each.value.launch_type, "FARGATE") + load_balancer = lookup(each.value, "load_balancer", {}) + name = try(each.value.name, each.key) + assign_public_ip = try(each.value.assign_public_ip, false) + security_group_ids = lookup(each.value, "security_group_ids", []) + subnet_ids = lookup(each.value, "subnet_ids", []) + ordered_placement_strategy = try(each.value.ordered_placement_strategy, {}) + placement_constraints = try(each.value.placement_constraints, {}) + platform_version = try(each.value.platform_version, null) + propagate_tags = try(each.value.propagate_tags, null) + scheduling_strategy = try(each.value.scheduling_strategy, null) + service_connect_configuration = lookup(each.value, "service_connect_configuration", {}) + service_registries = lookup(each.value, "service_registries", {}) + timeouts = try(each.value.timeouts, {}) + triggers = try(each.value.triggers, {}) + wait_for_steady_state = try(each.value.wait_for_steady_state, null) + + # Service IAM role + create_iam_role = try(each.value.create_iam_role, true) + iam_role_arn = lookup(each.value, "iam_role_arn", null) + iam_role_name = try(each.value.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, null) + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, null) + iam_role_tags = try(each.value.iam_role_tags, {}) + iam_role_statements = lookup(each.value, "iam_role_statements", {}) + + # Task definition + create_task_definition = try(each.value.create_task_definition, true) + task_definition_arn = lookup(each.value, "task_definition_arn", null) + container_definitions = try(each.value.container_definitions, {}) + container_definition_defaults = try(each.value.container_definition_defaults, {}) + cpu = try(each.value.cpu, 1024) + ephemeral_storage = try(each.value.ephemeral_storage, {}) + family = try(each.value.family, null) + inference_accelerator = try(each.value.inference_accelerator, {}) + ipc_mode = try(each.value.ipc_mode, null) + memory = try(each.value.memory, 2048) + network_mode = try(each.value.network_mode, "awsvpc") + proxy_configuration = try(each.value.proxy_configuration, {}) + requires_compatibilities = try(each.value.requires_compatibilities, ["FARGATE"]) + runtime_platform = try(each.value.runtime_platform, { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + }) + skip_destroy = try(each.value.skip_destroy, null) + volume = try(each.value.volume, {}) + task_tags = try(each.value.task_tags, {}) + + # Task execution IAM role + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, true) + task_exec_iam_role_arn = lookup(each.value, "task_exec_iam_role_arn", null) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, null) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, null) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, {}) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, {}) + + # Task execution IAM role policy + create_task_exec_policy = try(each.value.create_task_exec_policy, true) + task_exec_ssm_param_arns = lookup(each.value, "task_exec_ssm_param_arns", ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_secret_arns = lookup(each.value, "task_exec_secret_arns", ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_iam_statements = lookup(each.value, "task_exec_iam_statements", {}) + + # Tasks - IAM role + create_tasks_iam_role = try(each.value.create_tasks_iam_role, true) + tasks_iam_role_arn = lookup(each.value, "tasks_iam_role_arn", null) + tasks_iam_role_name = try(each.value.tasks_iam_role_name, null) + tasks_iam_role_use_name_prefix = try(each.value.tasks_iam_role_use_name_prefix, true) + tasks_iam_role_path = try(each.value.tasks_iam_role_path, null) + tasks_iam_role_description = try(each.value.tasks_iam_role_description, null) + tasks_iam_role_permissions_boundary = try(each.value.tasks_iam_role_permissions_boundary, null) + tasks_iam_role_tags = try(each.value.tasks_iam_role_tags, {}) + tasks_iam_role_policies = lookup(each.value, "tasks_iam_role_policies", {}) + tasks_iam_role_statements = lookup(each.value, "tasks_iam_role_statements", {}) + + # Task set + external_id = try(each.value.external_id, null) + scale = try(each.value.scale, {}) + force_delete = try(each.value.force_delete, null) + wait_until_stable = try(each.value.wait_until_stable, null) + wait_until_stable_timeout = try(each.value.wait_until_stable_timeout, null) + + # Autoscaling + enable_autoscaling = try(each.value.enable_autoscaling, true) + autoscaling_min_capacity = try(each.value.autoscaling_min_capacity, 1) + autoscaling_max_capacity = try(each.value.autoscaling_max_capacity, 10) + autoscaling_policies = try(each.value.autoscaling_policies, { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } } - } - - depends_on = [ - aws_ecs_capacity_provider.this - ] -} + memory = { + policy_type = "TargetTrackingScaling" -################################################################################ -# Capacity Provider - Autoscaling Group(s) -################################################################################ - -resource "aws_ecs_capacity_provider" "this" { - for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } - - name = try(each.value.name, each.key) - - auto_scaling_group_provider { - auto_scaling_group_arn = each.value.auto_scaling_group_arn - managed_termination_protection = try(each.value.managed_termination_protection, null) - - dynamic "managed_scaling" { - for_each = try([each.value.managed_scaling], []) - - content { - instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) - maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) - minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) - status = try(managed_scaling.value.status, null) - target_capacity = try(managed_scaling.value.target_capacity, null) + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } } } - } + }) + autoscaling_scheduled_actions = try(each.value.autoscaling_scheduled_actions, {}) + + # Security Group + create_security_group = try(each.value.create_security_group, true) + security_group_name = try(each.value.security_group_name, null) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, true) + security_group_description = try(each.value.security_group_description, null) + security_group_rules = lookup(each.value, "security_group_rules", {}) + security_group_tags = try(each.value.security_group_tags, {}) tags = merge(var.tags, try(each.value.tags, {})) } diff --git a/modules/cluster/README.md b/modules/cluster/README.md new file mode 100644 index 0000000..0f4adb6 --- /dev/null +++ b/modules/cluster/README.md @@ -0,0 +1,214 @@ +# Amazon ECS Cluster Terraform Module + +Terraform module which creates Amazon ECS (Elastic Container Service) cluster resources on AWS. + +## Available Features + +- ECS cluster +- Fargate capacity providers +- EC2 AutoScaling Group capacity providers +- ECS Service w/ task definition, task set, and container definition support + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/README.md) + +## Usage + +### Fargate Capacity Providers + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + cluster_name = "ecs-fargate" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +### EC2 Autoscaling Capacity Providers + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + cluster_name = "ecs-ec2" + + cluster_configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-ec2" + } + } + } + + autoscaling_capacity_providers = { + one = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + + default_capacity_provider_strategy = { + weight = 60 + base = 20 + } + } + two = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } + + default_capacity_provider_strategy = { + weight = 40 + } + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + # Disable creation of cluster and all resources + create = false + + # ... omitted +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.55 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_ecs_capacity_provider.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_capacity_provider) | resource | +| [aws_ecs_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster) | resource | +| [aws_ecs_cluster_capacity_providers.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster_capacity_providers) | resource | +| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster | `any` | `{}` | no | +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events | `number` | `90` | no | +| [cloudwatch\_log\_group\_tags](#input\_cloudwatch\_log\_group\_tags) | A map of additional tags to add to the log group created | `map(string)` | `{}` | no | +| [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | +| [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace | `map(string)` | `{}` | no | +| [cluster\_settings](#input\_cluster\_settings) | Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `map(string)` |

{
"name": "containerInsights",
"value": "enabled"
}
| no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | +| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | +| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | ARN that identifies the cluster | +| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [cluster\_capacity\_providers](#output\_cluster\_capacity\_providers) | Map of cluster capacity providers attributes | +| [id](#output\_id) | ID that identifies the cluster | +| [name](#output\_name) | Name that identifies the cluster | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/cluster/main.tf b/modules/cluster/main.tf new file mode 100644 index 0000000..6830af2 --- /dev/null +++ b/modules/cluster/main.tf @@ -0,0 +1,327 @@ +data "aws_partition" "current" {} + +################################################################################ +# Cluster +################################################################################ + +locals { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = try(aws_cloudwatch_log_group.this[0].name, null) + } + } +} + +resource "aws_ecs_cluster" "this" { + count = var.create ? 1 : 0 + + name = var.cluster_name + + dynamic "configuration" { + for_each = var.create_cloudwatch_log_group ? [var.cluster_configuration] : [] + + content { + dynamic "execute_command_configuration" { + for_each = try([merge(local.execute_command_configuration, configuration.value.execute_command_configuration)], [{}]) + + content { + kms_key_id = try(execute_command_configuration.value.kms_key_id, null) + logging = try(execute_command_configuration.value.logging, "DEFAULT") + + dynamic "log_configuration" { + for_each = try([execute_command_configuration.value.log_configuration], []) + + content { + cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) + cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) + s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) + s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) + s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + } + } + } + } + } + } + + dynamic "configuration" { + for_each = !var.create_cloudwatch_log_group && length(var.cluster_configuration) > 0 ? [var.cluster_configuration] : [] + + content { + dynamic "execute_command_configuration" { + for_each = try([configuration.value.execute_command_configuration], [{}]) + + content { + kms_key_id = try(execute_command_configuration.value.kms_key_id, null) + logging = try(execute_command_configuration.value.logging, "DEFAULT") + + dynamic "log_configuration" { + for_each = try([execute_command_configuration.value.log_configuration], []) + + content { + cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) + cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) + s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) + s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) + s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + } + } + } + } + } + } + + dynamic "service_connect_defaults" { + for_each = length(var.cluster_service_connect_defaults) > 0 ? [var.cluster_service_connect_defaults] : [] + + content { + namespace = service_connect_defaults.value.namespace + } + } + + dynamic "setting" { + for_each = [var.cluster_settings] + + content { + name = setting.value.name + value = setting.value.value + } + } + + tags = var.tags +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +resource "aws_cloudwatch_log_group" "this" { + count = var.create && var.create_cloudwatch_log_group ? 1 : 0 + + name = "/aws/ecs/${var.cluster_name}" + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = merge(var.tags, var.cloudwatch_log_group_tags) +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +locals { + default_capacity_providers = merge( + { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, + { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } + ) +} + +resource "aws_ecs_cluster_capacity_providers" "this" { + count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 + + cluster_name = aws_ecs_cluster.this[0].name + capacity_providers = distinct(concat( + [for k, v in var.fargate_capacity_providers : try(v.name, k)], + [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] + )) + + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html#capacity-providers-considerations + dynamic "default_capacity_provider_strategy" { + for_each = local.default_capacity_providers + iterator = strategy + + content { + capacity_provider = try(strategy.value.name, strategy.key) + base = try(strategy.value.default_capacity_provider_strategy.base, null) + weight = try(strategy.value.default_capacity_provider_strategy.weight, null) + } + } + + depends_on = [ + aws_ecs_capacity_provider.this + ] +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +resource "aws_ecs_capacity_provider" "this" { + for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } + + name = try(each.value.name, each.key) + + auto_scaling_group_provider { + auto_scaling_group_arn = each.value.auto_scaling_group_arn + # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work + managed_termination_protection = length(try([each.value.managed_scaling], [])) == 0 ? "DISABLED" : try(each.value.managed_termination_protection, null) + + dynamic "managed_scaling" { + for_each = try([each.value.managed_scaling], []) + + content { + instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) + maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) + minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) + status = try(managed_scaling.value.status, null) + target_capacity = try(managed_scaling.value.target_capacity, null) + } + } + } + + tags = merge(var.tags, try(each.value.tags, {})) +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +locals { + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.cluster_name), "") + + create_task_exec_iam_role = var.create && var.create_task_exec_iam_role + create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy +} + +data "aws_iam_policy_document" "task_exec_assume" { + count = local.create_task_exec_iam_role ? 1 : 0 + + statement { + sid = "ECSTaskExecutionAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.${data.aws_partition.current.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "task_exec" { + count = local.create_task_exec_iam_role ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + path = var.task_exec_iam_role_path + description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${var.cluster_name}") + + assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json + permissions_boundary = var.task_exec_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec_additional" { + for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } + + role = aws_iam_role.task_exec[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "Logs" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = ["*"] + } + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "ECR" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] + + content { + sid = "GetSSMParams" + actions = ["ssm:GetParameters"] + resources = var.task_exec_ssm_param_arns + } + } + + dynamic "statement" { + for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] + + content { + sid = "GetSecrets" + actions = ["secretsmanager:GetSecretValue"] + resources = var.task_exec_secret_arns + } + } + + dynamic "statement" { + for_each = var.task_exec_iam_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") + policy = data.aws_iam_policy_document.task_exec[0].json + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + role = aws_iam_role.task_exec[0].name + policy_arn = aws_iam_policy.task_exec[0].arn +} diff --git a/modules/cluster/outputs.tf b/modules/cluster/outputs.tf new file mode 100644 index 0000000..7ddfbb5 --- /dev/null +++ b/modules/cluster/outputs.tf @@ -0,0 +1,70 @@ +################################################################################ +# Cluster +################################################################################ + +output "arn" { + description = "ARN that identifies the cluster" + value = try(aws_ecs_cluster.this[0].arn, null) +} + +output "id" { + description = "ID that identifies the cluster" + value = try(aws_ecs_cluster.this[0].id, null) +} + +output "name" { + description = "Name that identifies the cluster" + value = try(aws_ecs_cluster.this[0].name, null) +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].name, null) +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].arn, null) +} + +################################################################################ +# Cluster Capacity Providers +################################################################################ + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = { for k, v in aws_ecs_cluster_capacity_providers.this : v.id => v } +} + +################################################################################ +# Capacity Provider - Autoscaling Group(s) +################################################################################ + +output "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = aws_ecs_capacity_provider.this +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = try(aws_iam_role.task_exec[0].name, null) +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = try(aws_iam_role.task_exec[0].arn, null) +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = try(aws_iam_role.task_exec[0].unique_id, null) +} diff --git a/modules/cluster/variables.tf b/modules/cluster/variables.tf new file mode 100644 index 0000000..2b9a52b --- /dev/null +++ b/modules/cluster/variables.tf @@ -0,0 +1,169 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Cluster +################################################################################ + +variable "cluster_name" { + description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = "" +} + +variable "cluster_configuration" { + description = "The execute command configuration for the cluster" + type = any + default = {} +} + +variable "cluster_settings" { + description = "Configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" + type = map(string) + default = { + name = "containerInsights" + value = "enabled" + } +} + +variable "cluster_service_connect_defaults" { + description = "Configures a default Service Connect namespace" + type = map(string) + default = {} +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events" + type = number + default = 90 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "cloudwatch_log_group_tags" { + description = "A map of additional tags to add to the log group created" + type = map(string) + default = {} +} + +################################################################################ +# Capacity Providers +################################################################################ + +variable "default_capacity_provider_use_fargate" { + description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" + type = bool + default = true +} + +variable "fargate_capacity_providers" { + description = "Map of Fargate capacity provider definitions to use for the cluster" + type = any + default = {} +} + +variable "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity provider definitions to create for the cluster" + type = any + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = false +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} diff --git a/modules/cluster/versions.tf b/modules/cluster/versions.tf new file mode 100644 index 0000000..290d221 --- /dev/null +++ b/modules/cluster/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.55" + } + } +} diff --git a/modules/container-definition/README.md b/modules/container-definition/README.md new file mode 100644 index 0000000..71eea02 --- /dev/null +++ b/modules/container-definition/README.md @@ -0,0 +1,200 @@ +# Amazon ECS Container Definition Module + +Configuration in this directory creates an Amazon ECS container definition. + +The module defaults to creating and utilizing a CloudWatch log group. You can disable this behavior by setting `enable_cloudwatch_logging` = `false` - useful for scenarios where Firelens is used for log forwarding. + +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/design.md) + +## Usage + +### Standard + +```hcl +module "ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + + name = "example" + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + memory_reservation = 100 + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +### W/ Firelens + +```hcl +module "fluentbit_ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + name = "fluent-bit" + + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + + tags = { + Environment = "dev" + Terraform = "true" + } +} + +module "example_ecs_container_definition" { + source = "terraform-aws-modules/ecs/aws//modules/container-definition" + + name = "example" + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + + tags = { + Environment = "dev" + Terraform = "true" + } +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.55 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_cloudwatch_log_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [cloudwatch\_log\_group\_kms\_key\_id](#input\_cloudwatch\_log\_group\_kms\_key\_id) | If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html) | `string` | `null` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events. Default is 30 days | `number` | `30` | no | +| [command](#input\_command) | The command that's passed to the container | `list(string)` | `[]` | no | +| [cpu](#input\_cpu) | The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value | `number` | `null` | no | +| [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | +| [dependencies](#input\_dependencies) | The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY |
list(object({
condition = string
containerName = string
}))
| `[]` | no | +| [disable\_networking](#input\_disable\_networking) | When this parameter is true, networking is disabled within the container | `bool` | `null` | no | +| [dns\_search\_domains](#input\_dns\_search\_domains) | Container DNS search domains. A list of DNS search domains that are presented to the container | `list(string)` | `[]` | no | +| [dns\_servers](#input\_dns\_servers) | Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers | `list(string)` | `[]` | no | +| [docker\_labels](#input\_docker\_labels) | A key/value map of labels to add to the container | `map(string)` | `{}` | no | +| [docker\_security\_options](#input\_docker\_security\_options) | A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type | `list(string)` | `[]` | no | +| [enable\_cloudwatch\_logging](#input\_enable\_cloudwatch\_logging) | Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers | `bool` | `true` | no | +| [entrypoint](#input\_entrypoint) | The entry point that is passed to the container | `list(string)` | `[]` | no | +| [environment](#input\_environment) | The environment variables to pass to the container |
list(object({
name = string
value = string
}))
| `[]` | no | +| [environment\_files](#input\_environment\_files) | A list of files containing the environment variables to pass to a container |
list(object({
value = string
type = string
}))
| `[]` | no | +| [essential](#input\_essential) | If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped | `bool` | `null` | no | +| [extra\_hosts](#input\_extra\_hosts) | A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container |
list(object({
hostname = string
ipAddress = string
}))
| `[]` | no | +| [firelens\_configuration](#input\_firelens\_configuration) | The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide | `any` | `{}` | no | +| [health\_check](#input\_health\_check) | The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html) | `any` | `{}` | no | +| [hostname](#input\_hostname) | The hostname to use for your container | `string` | `null` | no | +| [image](#input\_image) | The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest` | `string` | `null` | no | +| [interactive](#input\_interactive) | When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated | `bool` | `false` | no | +| [links](#input\_links) | The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge` | `list(string)` | `[]` | no | +| [linux\_parameters](#input\_linux\_parameters) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | +| [log\_configuration](#input\_log\_configuration) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | +| [memory](#input\_memory) | The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified | `number` | `null` | no | +| [memory\_reservation](#input\_memory\_reservation) | The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance | `number` | `null` | no | +| [mount\_points](#input\_mount\_points) | The mount points for data volumes in your container | `list(any)` | `[]` | no | +| [name](#input\_name) | The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed | `string` | `null` | no | +| [operating\_system\_family](#input\_operating\_system\_family) | The OS family for task | `string` | `"LINUX"` | no | +| [port\_mappings](#input\_port\_mappings) | The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort | `list(any)` | `[]` | no | +| [privileged](#input\_privileged) | When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user) | `bool` | `false` | no | +| [pseudo\_terminal](#input\_pseudo\_terminal) | When this parameter is true, a `TTY` is allocated | `bool` | `false` | no | +| [readonly\_root\_filesystem](#input\_readonly\_root\_filesystem) | When this parameter is true, the container is given read-only access to its root file system | `bool` | `true` | no | +| [repository\_credentials](#input\_repository\_credentials) | Container repository credentials; required when using a private repo. This map currently supports a single key; "credentialsParameter", which should be the ARN of a Secrets Manager's secret holding the credentials | `map(string)` | `{}` | no | +| [resource\_requirements](#input\_resource\_requirements) | The type and amount of a resource to assign to a container. The only supported resource is a GPU |
list(object({
type = string
value = string
}))
| `[]` | no | +| [secrets](#input\_secrets) | The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide |
list(object({
name = string
valueFrom = string
}))
| `[]` | no | +| [service](#input\_service) | The name of the service that the container definition is associated with | `string` | `""` | no | +| [start\_timeout](#input\_start\_timeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no | +| [stop\_timeout](#input\_stop\_timeout) | Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own | `number` | `120` | no | +| [system\_controls](#input\_system\_controls) | A list of namespaced kernel parameters to set in the container | `list(map(string))` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [ulimits](#input\_ulimits) | A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker |
list(object({
hardLimit = number
name = string
softLimit = number
}))
| `[]` | no | +| [user](#input\_user) | The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set | `string` | `null` | no | +| [volumes\_from](#input\_volumes\_from) | Data volumes to mount from another container | `list(any)` | `[]` | no | +| [working\_directory](#input\_working\_directory) | The working directory to run commands inside the container | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | Arn of cloudwatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of cloudwatch log group created | +| [container\_definition](#output\_container\_definition) | Container definition | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/container-definition/main.tf b/modules/container-definition/main.tf new file mode 100644 index 0000000..da4ea1c --- /dev/null +++ b/modules/container-definition/main.tf @@ -0,0 +1,72 @@ +data "aws_region" "current" {} + +locals { + is_not_windows = contains(["LINUX"], var.operating_system_family) + + log_configuration = merge( + { for k, v in { + logDriver = "awslogs", + options = { + awslogs-region = data.aws_region.current.name, + awslogs-group = try(aws_cloudwatch_log_group.this[0].name, ""), + awslogs-stream-prefix = "ecs" + }, + } : k => v if var.enable_cloudwatch_logging }, + var.log_configuration + ) + + definition = { + command = length(var.command) > 0 ? var.command : null + cpu = var.cpu + dependsOn = length(var.dependencies) > 0 ? var.dependencies : null # depends_on is a reserved word + disableNetworking = local.is_not_windows ? var.disable_networking : null + dnsSearchDomains = local.is_not_windows && length(var.dns_search_domains) > 0 ? var.dns_search_domains : null + dnsServers = local.is_not_windows && length(var.dns_servers) > 0 ? var.dns_servers : null + dockerLabels = length(var.docker_labels) > 0 ? var.docker_labels : null + dockerSecurityOptions = length(var.docker_security_options) > 0 ? var.docker_security_options : null + entrypoint = length(var.entrypoint) > 0 ? var.entrypoint : null + environment = length(var.environment) > 0 ? var.environment : null + environmentFiles = length(var.environment_files) > 0 ? var.environment_files : null + essential = var.essential + extraHosts = local.is_not_windows && length(var.extra_hosts) > 0 ? var.extra_hosts : null + firelensConfiguration = length(var.firelens_configuration) > 0 ? var.firelens_configuration : null + healthCheck = length(var.health_check) > 0 ? var.health_check : null + hostname = var.hostname + image = var.image + interactive = var.interactive + links = local.is_not_windows && length(var.links) > 0 ? var.links : null + linuxParameters = local.is_not_windows && length(var.linux_parameters) > 0 ? var.linux_parameters : null + logConfiguration = local.log_configuration + memory = var.memory + memoryReservation = var.memory_reservation + mountPoints = length(var.mount_points) > 0 ? var.mount_points : null + name = var.name + portMappings = length(var.port_mappings) > 0 ? var.port_mappings : null + privileged = local.is_not_windows ? var.privileged : null + pseudoTerminal = var.pseudo_terminal + readonlyRootFilesystem = local.is_not_windows ? var.readonly_root_filesystem : null + repositoryCredentials = length(var.repository_credentials) > 0 ? var.repository_credentials : null + resourceRequirements = length(var.resource_requirements) > 0 ? var.resource_requirements : null + secrets = length(var.secrets) > 0 ? var.secrets : null + startTimeout = var.start_timeout + stopTimeout = var.stop_timeout + systemControls = length(var.system_controls) > 0 ? var.system_controls : null + ulimits = local.is_not_windows && length(var.ulimits) > 0 ? var.ulimits : null + user = local.is_not_windows ? var.user : null + volumesFrom = length(var.volumes_from) > 0 ? var.volumes_from : null + workingDirectory = var.working_directory + } + + # Strip out all null values, ECS API will provide defaults in place of null/empty values + container_definition = { for k, v in local.definition : k => v if v != null } +} + +resource "aws_cloudwatch_log_group" "this" { + count = var.create_cloudwatch_log_group && var.enable_cloudwatch_logging ? 1 : 0 + + name = "/aws/ecs/${var.service}/${var.name}" + retention_in_days = var.cloudwatch_log_group_retention_in_days + kms_key_id = var.cloudwatch_log_group_kms_key_id + + tags = var.tags +} diff --git a/modules/container-definition/outputs.tf b/modules/container-definition/outputs.tf new file mode 100644 index 0000000..66a1d36 --- /dev/null +++ b/modules/container-definition/outputs.tf @@ -0,0 +1,22 @@ +################################################################################ +# Container Definition +################################################################################ + +output "container_definition" { + description = "Container definition" + value = local.container_definition +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].name, null) +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = try(aws_cloudwatch_log_group.this[0].arn, null) +} diff --git a/modules/container-definition/variables.tf b/modules/container-definition/variables.tf new file mode 100644 index 0000000..b7861c0 --- /dev/null +++ b/modules/container-definition/variables.tf @@ -0,0 +1,305 @@ +variable "operating_system_family" { + description = "The OS family for task" + type = string + default = "LINUX" +} + +################################################################################ +# Container Definition +################################################################################ + +variable "command" { + description = "The command that's passed to the container" + type = list(string) + default = [] +} + +variable "cpu" { + description = "The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value" + type = number + default = null +} + +variable "dependencies" { + description = "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" + type = list(object({ + condition = string + containerName = string + })) + default = [] +} + +variable "disable_networking" { + description = "When this parameter is true, networking is disabled within the container" + type = bool + default = null +} + +variable "dns_search_domains" { + description = "Container DNS search domains. A list of DNS search domains that are presented to the container" + type = list(string) + default = [] +} + +variable "dns_servers" { + description = "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" + type = list(string) + default = [] +} + +variable "docker_labels" { + description = "A key/value map of labels to add to the container" + type = map(string) + default = {} +} + +variable "docker_security_options" { + description = "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" + type = list(string) + default = [] +} + +variable "entrypoint" { + description = "The entry point that is passed to the container" + type = list(string) + default = [] +} + +variable "environment" { + description = "The environment variables to pass to the container" + type = list(object({ + name = string + value = string + })) + default = [] +} + +variable "environment_files" { + description = "A list of files containing the environment variables to pass to a container" + type = list(object({ + value = string + type = string + })) + default = [] +} + +variable "essential" { + description = "If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped" + type = bool + default = null +} + +variable "extra_hosts" { + description = "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" + type = list(object({ + hostname = string + ipAddress = string + })) + default = [] +} + +variable "firelens_configuration" { + description = "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" + type = any + default = {} +} + +variable "health_check" { + description = "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" + type = any + default = {} +} + +variable "hostname" { + description = "The hostname to use for your container" + type = string + default = null +} + +variable "image" { + description = "The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest`" + type = string + default = null +} + +variable "interactive" { + description = "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated" + type = bool + default = false +} + +variable "links" { + description = "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" + type = list(string) + default = [] +} + +variable "linux_parameters" { + description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + type = any + default = {} +} + +variable "log_configuration" { + description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" + type = any + default = {} +} + +variable "memory" { + description = "The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified" + type = number + default = null +} + +variable "memory_reservation" { + description = "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" + type = number + default = null +} + +variable "mount_points" { + description = "The mount points for data volumes in your container" + type = list(any) + default = [] +} + +variable "name" { + description = "The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed" + type = string + default = null +} + +variable "port_mappings" { + description = "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" + type = list(any) + default = [] +} + +variable "privileged" { + description = "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)" + type = bool + default = false +} + +variable "pseudo_terminal" { + description = "When this parameter is true, a `TTY` is allocated" + type = bool + default = false +} + +variable "readonly_root_filesystem" { + description = "When this parameter is true, the container is given read-only access to its root file system" + type = bool + default = true +} + +variable "repository_credentials" { + description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" + type = map(string) + default = {} +} + +variable "resource_requirements" { + description = "The type and amount of a resource to assign to a container. The only supported resource is a GPU" + type = list(object({ + type = string + value = string + })) + default = [] +} + +variable "secrets" { + description = "The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide" + type = list(object({ + name = string + valueFrom = string + })) + default = [] +} + +variable "start_timeout" { + description = "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" + type = number + default = 30 +} + +variable "stop_timeout" { + description = "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" + type = number + default = 120 +} + +variable "system_controls" { + description = "A list of namespaced kernel parameters to set in the container" + type = list(map(string)) + default = [] +} + +variable "ulimits" { + description = "A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker" + type = list(object({ + hardLimit = number + name = string + softLimit = number + })) + default = [] +} + +variable "user" { + description = "The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set" + type = string + default = null +} + +variable "volumes_from" { + description = "Data volumes to mount from another container" + type = list(any) + default = [] +} + +variable "working_directory" { + description = "The working directory to run commands inside the container" + type = string + default = null +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "service" { + description = "The name of the service that the container definition is associated with" + type = string + default = "" +} + +variable "enable_cloudwatch_logging" { + description = "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers" + type = bool + default = true +} + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events. Default is 30 days" + type = number + default = 30 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} diff --git a/modules/container-definition/versions.tf b/modules/container-definition/versions.tf new file mode 100644 index 0000000..290d221 --- /dev/null +++ b/modules/container-definition/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.55" + } + } +} diff --git a/modules/service/README.md b/modules/service/README.md index 200f096..af1ac14 100644 --- a/modules/service/README.md +++ b/modules/service/README.md @@ -1,18 +1,16 @@ -# ECS Service Module +# Amazon ECS Service Module -Configuration in this directory creates an ECS Service EKS Profile +Configuration in this directory creates an Amazon ECS Service and associated resources. -⚠️ Module is under active development ⚠️ +Some notable configurations to be aware of when using this module: +1. `desired_count`/`scale` is always ignored; the module is designed to utilize autoscaling by default (though it can be disabled) +2. The default configuration is intended for `FARGATE` use -## TODO +For more details see the [design doc](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/docs/design.md) -- [ ] `aws_ecs_service` (one default, one with `ignore_changes` for things like `desired_count`) -- [ ] `aws_ecs_task_definition` -- [ ] `aws_ecs_task_set` -- [ ] `aws_appautoscaling_target` & `aws_appautoscaling_policy` (`for_each` over a shared map where each key = 1 target and 2 policies, 1 policy for scale up, 1 for scale down) -- [ ] Task role (`aws_iam_role`, `aws_iam_role_policy_attachment`, assume role `aws_iam_policy_document`) -- [ ] Task exectution role (`aws_iam_role`, `aws_iam_role_policy_attachment`, assume role `aws_iam_policy_document`) -- [ ] ECS CloudWatch events role (`aws_iam_role`, `aws_iam_role_policy_attachment`, assume role `aws_iam_policy_document`) +### Logging + +Please refer to [FireLens examples repository](https://github.com/aws-samples/amazon-ecs-firelens-examples) for logging configuration examples for FireLens on Amazon ECS and AWS Fargate. ## Usage @@ -20,10 +18,99 @@ Configuration in this directory creates an ECS Service EKS Profile module "ecs_service" { source = "terraform-aws-modules/ecs/aws//modules/service" - name = "MyService" - cluster_id = module.ecs.cluster_id + name = "example" + cluster_arn = "arn:aws:ecs:us-west-2:123456789012:cluster/default" + + cpu = 1024 + memory = 4096 + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + } + + ecs-sample = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = "ecs-sample" + containerPort = 80 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = "eu-west-1" + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory_reservation = 100 + } + } - # TODO + service_connect_configuration = { + namespace = "example" + service = { + client_alias = { + port = 80 + dns_name = "ecs-sample" + } + port_name = "ecs-sample" + discovery_name = "ecs-sample" + } + } + + load_balancer = { + service = { + target_group_arn = "arn:aws:elasticloadbalancing:eu-west-1:1234567890:targetgroup/bluegreentarget1/209a844cd01825a4" + container_name = "ecs-sample" + container_port = 80 + } + } + + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = 80 + to_port = 80 + protocol = "tcp" + description = "Service port" + source_security_group_id = "sg-12345678" + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } tags = { Environment = "dev" @@ -32,9 +119,44 @@ module "ecs_service" { } ``` -### Logging +## Conditional Creation + +The following values are provided to toggle on/off creation of the associated resources as desired: + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + + # Disable creation of service and all resources + create = false + + # Disable creation of the service IAM role; `iam_role_arn` should be provided + create_iam_role = false + + # Disable creation of the task definition; `task_definition_arn` should be provided + create_task_definition = false + + # Disable creation of the task execution IAM role; `task_exec_iam_role_arn` should be provided + create_task_exec_iam_role = false + + # Disable creation of the task execution IAM role policy + create_task_exec_policy = false + + # Disable creation of the tasks IAM role; `tasks_iam_role_arn` should be provided + create_tasks_iam_role = false -Please refer to https://github.com/aws-samples/amazon-ecs-firelens-examples for logging architectures for FireLens on Amazon ECS and AWS Fargate. + # Disable creation of the service security group + create_security_group = false + + # ... omitted +} +``` + +## Examples + +- [ECS Cluster Complete](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS Cluster w/ EC2 Autoscaling Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/ec2-autoscaling) +- [ECS Cluster w/ Fargate Capacity Provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/fargate) ## Requirements @@ -42,25 +164,187 @@ Please refer to https://github.com/aws-samples/amazon-ecs-firelens-examples for | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.6 | +| [aws](#requirement\_aws) | >= 4.55 | ## Providers -No providers. +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.55 | ## Modules -No modules. +| Name | Source | Version | +|------|--------|---------| +| [container\_definition](#module\_container\_definition) | ../container-definition | n/a | ## Resources -No resources. +| Name | Type | +|------|------| +| [aws_appautoscaling_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_policy) | resource | +| [aws_appautoscaling_scheduled_action.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_scheduled_action) | resource | +| [aws_appautoscaling_target.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/appautoscaling_target) | resource | +| [aws_ecs_service.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_service.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_service) | resource | +| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | resource | +| [aws_ecs_task_set.ignore_task_definition](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | +| [aws_ecs_task_set.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | +| [aws_iam_policy.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role_policy.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy_attachment.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecs_task_definition) | data source | +| [aws_iam_policy_document.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.service_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.tasks_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [aws_subnet.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | ## Inputs -No inputs. +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alarms](#input\_alarms) | Information about the CloudWatch alarms | `any` | `{}` | no | +| [assign\_public\_ip](#input\_assign\_public\_ip) | Assign a public IP address to the ENI (Fargate launch type only) | `bool` | `false` | no | +| [autoscaling\_max\_capacity](#input\_autoscaling\_max\_capacity) | Maximum number of tasks to run in your service | `number` | `10` | no | +| [autoscaling\_min\_capacity](#input\_autoscaling\_min\_capacity) | Minimum number of tasks to run in your service | `number` | `1` | no | +| [autoscaling\_policies](#input\_autoscaling\_policies) | Map of autoscaling policies to create for the service | `any` |
{
"cpu": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageCPUUtilization"
}
}
},
"memory": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageMemoryUtilization"
}
}
}
}
| no | +| [autoscaling\_scheduled\_actions](#input\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions to create for the service | `any` | `{}` | no | +| [capacity\_provider\_strategy](#input\_capacity\_provider\_strategy) | Capacity provider strategies to use for the service. Can be one or more | `any` | `{}` | no | +| [cluster\_arn](#input\_cluster\_arn) | ARN of the ECS cluster where the resources will be provisioned | `string` | `""` | no | +| [container\_definition\_defaults](#input\_container\_definition\_defaults) | A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions` | `any` | `{}` | no | +| [container\_definitions](#input\_container\_definitions) | A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document | `any` | `{}` | no | +| [cpu](#input\_cpu) | Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `1024` | no | +| [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | +| [create\_iam\_role](#input\_create\_iam\_role) | Determines whether the ECS service IAM role should be created | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | +| [create\_task\_definition](#input\_create\_task\_definition) | Determines whether to create a task definition or use existing/provided | `bool` | `true` | no | +| [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `true` | no | +| [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | +| [create\_tasks\_iam\_role](#input\_create\_tasks\_iam\_role) | Determines whether the ECS tasks IAM role should be created | `bool` | `true` | no | +| [deployment\_circuit\_breaker](#input\_deployment\_circuit\_breaker) | Configuration block for deployment circuit breaker | `any` | `{}` | no | +| [deployment\_controller](#input\_deployment\_controller) | Configuration block for deployment controller configuration | `any` | `{}` | no | +| [deployment\_maximum\_percent](#input\_deployment\_maximum\_percent) | Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment | `number` | `200` | no | +| [deployment\_minimum\_healthy\_percent](#input\_deployment\_minimum\_healthy\_percent) | Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment | `number` | `66` | no | +| [desired\_count](#input\_desired\_count) | Number of instances of the task definition to place and keep running. Defaults to `0` | `number` | `1` | no | +| [enable\_autoscaling](#input\_enable\_autoscaling) | Determines whether to enable autoscaling for the service | `bool` | `true` | no | +| [enable\_ecs\_managed\_tags](#input\_enable\_ecs\_managed\_tags) | Specifies whether to enable Amazon ECS managed tags for the tasks within the service | `bool` | `true` | no | +| [enable\_execute\_command](#input\_enable\_execute\_command) | Specifies whether to enable Amazon ECS Exec for the tasks within the service | `bool` | `false` | no | +| [ephemeral\_storage](#input\_ephemeral\_storage) | The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate | `any` | `{}` | no | +| [external\_id](#input\_external\_id) | The external ID associated with the task set | `string` | `null` | no | +| [family](#input\_family) | A unique name for your task definition | `string` | `null` | no | +| [force\_delete](#input\_force\_delete) | Whether to allow deleting the task set without waiting for scaling down to 0 | `bool` | `null` | no | +| [force\_new\_deployment](#input\_force\_new\_deployment) | Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates | `bool` | `true` | no | +| [health\_check\_grace\_period\_seconds](#input\_health\_check\_grace\_period\_seconds) | Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers | `number` | `null` | no | +| [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [iam\_role\_description](#input\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [iam\_role\_statements](#input\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [ignore\_task\_definition\_changes](#input\_ignore\_task\_definition\_changes) | Whether changes to service `task_definition` changes should be ignored | `bool` | `false` | no | +| [inference\_accelerator](#input\_inference\_accelerator) | Configuration block(s) with Inference Accelerators settings | `any` | `{}` | no | +| [ipc\_mode](#input\_ipc\_mode) | IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none` | `string` | `null` | no | +| [launch\_type](#input\_launch\_type) | Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE` | `string` | `"FARGATE"` | no | +| [load\_balancer](#input\_load\_balancer) | Configuration block for load balancers | `any` | `{}` | no | +| [memory](#input\_memory) | Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `2048` | no | +| [name](#input\_name) | Name of the service (up to 255 letters, numbers, hyphens, and underscores) | `string` | `null` | no | +| [network\_mode](#input\_network\_mode) | Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host` | `string` | `"awsvpc"` | no | +| [ordered\_placement\_strategy](#input\_ordered\_placement\_strategy) | Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence | `any` | `{}` | no | +| [pid\_mode](#input\_pid\_mode) | Process namespace to use for the containers in the task. The valid values are `host` and `task` | `string` | `null` | no | +| [placement\_constraints](#input\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10) | `any` | `{}` | no | +| [platform\_version](#input\_platform\_version) | Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST` | `string` | `null` | no | +| [propagate\_tags](#input\_propagate\_tags) | Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION` | `string` | `null` | no | +| [proxy\_configuration](#input\_proxy\_configuration) | Configuration block for the App Mesh proxy | `any` | `{}` | no | +| [requires\_compatibilities](#input\_requires\_compatibilities) | Set of launch types required by the task. The valid values are `EC2` and `FARGATE` | `list(string)` |
[
"FARGATE"
]
| no | +| [runtime\_platform](#input\_runtime\_platform) | Configuration block for `runtime_platform` that containers in your task may use | `any` |
{
"cpu_architecture": "X86_64",
"operating_system_family": "LINUX"
}
| no | +| [scale](#input\_scale) | A floating-point percentage of the desired number of tasks to place and keep running in the task set | `any` | `{}` | no | +| [scheduling\_strategy](#input\_scheduling\_strategy) | Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA` | `string` | `null` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | +| [security\_group\_ids](#input\_security\_group\_ids) | List of security groups to associate with the task or service | `list(string)` | `[]` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | no | +| [security\_group\_rules](#input\_security\_group\_rules) | Security group rules to add to the security group created | `any` | `{}` | no | +| [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | +| [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | +| [service\_connect\_configuration](#input\_service\_connect\_configuration) | The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace | `any` | `{}` | no | +| [service\_registries](#input\_service\_registries) | Service discovery registries for the service | `any` | `{}` | no | +| [skip\_destroy](#input\_skip\_destroy) | If true, the task is not deleted when the service is deleted | `bool` | `null` | no | +| [subnet\_ids](#input\_subnet\_ids) | List of subnets to associate with the task or service | `list(string)` | `[]` | no | +| [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [task\_definition\_arn](#input\_task\_definition\_arn) | Existing task definition ARN. Required when `create_task_definition` is `false` | `string` | `null` | no | +| [task\_exec\_iam\_role\_arn](#input\_task\_exec\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [task\_exec\_iam\_role\_name](#input\_task\_exec\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [task\_exec\_iam\_role\_path](#input\_task\_exec\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [task\_exec\_iam\_role\_permissions\_boundary](#input\_task\_exec\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | +| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | +| [task\_tags](#input\_task\_tags) | A map of additional tags to add to the task definition/set created | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_arn](#input\_tasks\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | +| [tasks\_iam\_role\_description](#input\_tasks\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [tasks\_iam\_role\_name](#input\_tasks\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [tasks\_iam\_role\_path](#input\_tasks\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [tasks\_iam\_role\_permissions\_boundary](#input\_tasks\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [tasks\_iam\_role\_policies](#input\_tasks\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_statements](#input\_tasks\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [tasks\_iam\_role\_tags](#input\_tasks\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_use\_name\_prefix](#input\_tasks\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the service | `map(string)` | `{}` | no | +| [triggers](#input\_triggers) | Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()` | `any` | `{}` | no | +| [volume](#input\_volume) | Configuration block for volumes that containers in your task may use | `any` | `{}` | no | +| [wait\_for\_steady\_state](#input\_wait\_for\_steady\_state) | If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false` | `bool` | `null` | no | +| [wait\_until\_stable](#input\_wait\_until\_stable) | Whether terraform should wait until the task set has reached `STEADY_STATE` | `bool` | `null` | no | +| [wait\_until\_stable\_timeout](#input\_wait\_until\_stable\_timeout) | Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or µs), `ms`, `s`, `m`, and `h`. Default `10m` | `number` | `null` | no | ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [autoscaling\_policies](#output\_autoscaling\_policies) | Map of autoscaling policies and their attributes | +| [autoscaling\_scheduled\_actions](#output\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions and their attributes | +| [container\_definitions](#output\_container\_definitions) | Container definitions | +| [iam\_role\_arn](#output\_iam\_role\_arn) | Service IAM role ARN | +| [iam\_role\_name](#output\_iam\_role\_name) | Service IAM role name | +| [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | +| [id](#output\_id) | ARN that identifies the service | +| [name](#output\_name) | Name of the service | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [task\_definition\_arn](#output\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [task\_definition\_family](#output\_task\_definition\_family) | The unique name of the task definition | +| [task\_definition\_revision](#output\_task\_definition\_revision) | Revision of the task in a particular family | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the task execution IAM role | +| [task\_set\_arn](#output\_task\_set\_arn) | The Amazon Resource Name (ARN) that identifies the task set | +| [task\_set\_id](#output\_task\_set\_id) | The ID of the task set | +| [task\_set\_stability\_status](#output\_task\_set\_stability\_status) | The stability status. This indicates whether the task set has reached a steady state | +| [task\_set\_status](#output\_task\_set\_status) | The status of the task set | +| [tasks\_iam\_role\_arn](#output\_tasks\_iam\_role\_arn) | Tasks IAM role ARN | +| [tasks\_iam\_role\_name](#output\_tasks\_iam\_role\_name) | Tasks IAM role name | +| [tasks\_iam\_role\_unique\_id](#output\_tasks\_iam\_role\_unique\_id) | Stable and unique string identifying the tasks IAM role | + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/modules/service/main.tf b/modules/service/main.tf index 066066c..4996ce5 100644 --- a/modules/service/main.tf +++ b/modules/service/main.tf @@ -1 +1,1328 @@ -locals {} +data "aws_region" "current" {} +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} + +locals { + account_id = data.aws_caller_identity.current.account_id + dns_suffix = data.aws_partition.current.dns_suffix + partition = data.aws_partition.current.partition + region = data.aws_region.current.name +} + +################################################################################ +# Service +################################################################################ + +locals { + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-external.html + is_external_deployment = try(var.deployment_controller.type, null) == "EXTERNAL" + is_daemon = var.scheduling_strategy == "DAEMON" + is_fargate = var.launch_type == "FARGATE" + + # Flattened `network_configuration` + network_configuration = { + assign_public_ip = var.assign_public_ip + security_groups = flatten(concat([try(aws_security_group.this[0].id, [])], var.security_group_ids)) + subnets = var.subnet_ids + } +} + +resource "aws_ecs_service" "this" { + count = var.create && !var.ignore_task_definition_changes ? 1 : 0 + + dynamic "alarms" { + for_each = length(var.alarms) > 0 ? [var.alarms] : [] + + content { + alarm_names = alarms.value.alarm_names + enable = try(alarms.value.enable, true) + rollback = try(alarms.value.rollback, true) + } + } + + dynamic "capacity_provider_strategy" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + cluster = var.cluster_arn + + dynamic "deployment_circuit_breaker" { + for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + + content { + enable = deployment_circuit_breaker.value.enable + rollback = deployment_circuit_breaker.value.rollback + } + } + + dynamic "deployment_controller" { + for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + + content { + type = try(deployment_controller.value.type, null) + } + } + + deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent + deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent + desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count + enable_ecs_managed_tags = var.enable_ecs_managed_tags + enable_execute_command = var.enable_execute_command + force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment + health_check_grace_period_seconds = var.health_check_grace_period_seconds + iam_role = local.iam_role_arn + launch_type = local.is_external_deployment ? null : var.launch_type + + dynamic "load_balancer" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + + content { + container_name = load_balancer.value.container_name + container_port = load_balancer.value.container_port + elb_name = try(load_balancer.value.elb_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + } + } + + name = var.name + + dynamic "network_configuration" { + # Set by task set if deployment controller is external + for_each = var.network_mode == "awsvpc" ? [{ for k, v in local.network_configuration : k => v if !local.is_external_deployment }] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "ordered_placement_strategy" { + for_each = var.ordered_placement_strategy + + content { + field = try(ordered_placement_strategy.value.field, null) + type = ordered_placement_strategy.value.type + } + } + + dynamic "placement_constraints" { + for_each = var.placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + # Set by task set if deployment controller is external + platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy + + dynamic "service_connect_configuration" { + for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + + content { + enabled = try(service_connect_configuration.value.enabled, true) + + dynamic "log_configuration" { + for_each = try([service_connect_configuration.value.log_configuration], []) + + content { + log_driver = try(log_configuration.value.log_driver, null) + options = try(log_configuration.value.options, null) + + dynamic "secret_option" { + for_each = try(log_configuration.value.secret_option, []) + + content { + name = secret_option.value.name + value_from = secret_option.value.value_from + } + } + } + } + + namespace = lookup(service_connect_configuration.value, "namespace", null) + + dynamic "service" { + for_each = try([service_connect_configuration.value.service], []) + + content { + + dynamic "client_alias" { + for_each = try([service.value.client_alias], []) + + content { + dns_name = try(client_alias.value.dns_name, null) + port = client_alias.value.port + } + } + + discovery_name = try(service.value.discovery_name, null) + ingress_port_override = try(service.value.ingress_port_override, null) + port_name = service.value.port_name + } + } + } + } + + dynamic "service_registries" { + # Set by task set if deployment controller is external + for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + task_definition = local.task_definition + triggers = var.triggers + wait_for_steady_state = var.wait_for_steady_state + + propagate_tags = var.propagate_tags + tags = var.tags + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + depends_on = [aws_iam_role_policy_attachment.service] + + lifecycle { + ignore_changes = [ + desired_count, # Always ignored + ] + } +} + +################################################################################ +# Service - Ignore `task_definition` +################################################################################ + +resource "aws_ecs_service" "ignore_task_definition" { + count = var.create && var.ignore_task_definition_changes ? 1 : 0 + + dynamic "alarms" { + for_each = length(var.alarms) > 0 ? [var.alarms] : [] + + content { + alarm_names = alarms.value.alarm_names + enable = try(alarms.value.enable, true) + rollback = try(alarms.value.rollback, true) + } + } + + dynamic "capacity_provider_strategy" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + cluster = var.cluster_arn + + dynamic "deployment_circuit_breaker" { + for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + + content { + enable = deployment_circuit_breaker.value.enable + rollback = deployment_circuit_breaker.value.rollback + } + } + + dynamic "deployment_controller" { + for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + + content { + type = try(deployment_controller.value.type, null) + } + } + + deployment_maximum_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_maximum_percent + deployment_minimum_healthy_percent = local.is_daemon || local.is_external_deployment ? null : var.deployment_minimum_healthy_percent + desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count + enable_ecs_managed_tags = var.enable_ecs_managed_tags + enable_execute_command = var.enable_execute_command + force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment + health_check_grace_period_seconds = var.health_check_grace_period_seconds + iam_role = local.iam_role_arn + launch_type = local.is_external_deployment ? null : var.launch_type + + dynamic "load_balancer" { + # Set by task set if deployment controller is external + for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + + content { + container_name = load_balancer.value.container_name + container_port = load_balancer.value.container_port + elb_name = try(load_balancer.value.elb_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + } + } + + name = var.name + + dynamic "network_configuration" { + # Set by task set if deployment controller is external + for_each = var.network_mode == "awsvpc" ? [{ for k, v in local.network_configuration : k => v if !local.is_external_deployment }] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "ordered_placement_strategy" { + for_each = var.ordered_placement_strategy + + content { + field = try(ordered_placement_strategy.value.field, null) + type = ordered_placement_strategy.value.type + } + } + + dynamic "placement_constraints" { + for_each = var.placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + # Set by task set if deployment controller is external + platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy + + dynamic "service_connect_configuration" { + for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + + content { + enabled = try(service_connect_configuration.value.enabled, true) + + dynamic "log_configuration" { + for_each = try([service_connect_configuration.value.log_configuration], []) + + content { + log_driver = try(log_configuration.value.log_driver, null) + options = try(log_configuration.value.options, null) + + dynamic "secret_option" { + for_each = try(log_configuration.value.secret_option, []) + + content { + name = secret_option.value.name + value_from = secret_option.value.value_from + } + } + } + } + + namespace = lookup(service_connect_configuration.value, "namespace", null) + + dynamic "service" { + for_each = try([service_connect_configuration.value.service], []) + + content { + + dynamic "client_alias" { + for_each = try([service.value.client_alias], []) + + content { + dns_name = try(client_alias.value.dns_name, null) + port = client_alias.value.port + } + } + + discovery_name = try(service.value.discovery_name, null) + ingress_port_override = try(service.value.ingress_port_override, null) + port_name = service.value.port_name + } + } + } + } + + dynamic "service_registries" { + # Set by task set if deployment controller is external + for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + task_definition = local.task_definition + triggers = var.triggers + wait_for_steady_state = var.wait_for_steady_state + + propagate_tags = var.propagate_tags + tags = var.tags + + timeouts { + create = try(var.timeouts.create, null) + update = try(var.timeouts.update, null) + delete = try(var.timeouts.delete, null) + } + + depends_on = [aws_iam_role_policy_attachment.service] + + lifecycle { + ignore_changes = [ + desired_count, # Always ignored + task_definition, + ] + } +} + +################################################################################ +# Service - IAM Role +################################################################################ + +locals { + # Role is not required if task definition uses `awsvpc` network mode or if a load balancer is not used + needs_iam_role = var.network_mode != "awsvpc" && length(var.load_balancer) > 0 + create_iam_role = var.create && var.create_iam_role && local.needs_iam_role + iam_role_arn = local.needs_iam_role ? try(aws_iam_role.service[0].arn, var.iam_role_arn) : null + + iam_role_name = try(coalesce(var.iam_role_name, var.name), "") +} + +data "aws_iam_policy_document" "service_assume" { + count = local.create_iam_role ? 1 : 0 + + statement { + sid = "ECSServiceAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs.${local.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "service" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + path = var.iam_role_path + description = var.iam_role_description + + assume_role_policy = data.aws_iam_policy_document.service_assume[0].json + permissions_boundary = var.iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.iam_role_tags) +} + +data "aws_iam_policy_document" "service" { + count = local.create_iam_role ? 1 : 0 + + statement { + sid = "ECSService" + resources = ["*"] + + actions = [ + "ec2:Describe*", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:Describe*", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:RegisterTargets" + ] + } + + dynamic "statement" { + for_each = var.iam_role_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "service" { + count = local.create_iam_role ? 1 : 0 + + name = var.iam_role_use_name_prefix ? null : local.iam_role_name + name_prefix = var.iam_role_use_name_prefix ? "${local.iam_role_name}-" : null + description = coalesce(var.iam_role_description, "ECS service policy that allows Amazon ECS to make calls to your load balancer on your behalf") + policy = data.aws_iam_policy_document.service[0].json + + tags = merge(var.tags, var.iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "service" { + count = local.create_iam_role ? 1 : 0 + + role = aws_iam_role.service[0].name + policy_arn = aws_iam_policy.service[0].arn +} + +################################################################################ +# Container Definition +################################################################################ + +module "container_definition" { + source = "../container-definition" + + for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition } + + operating_system_family = try(var.runtime_platform.operating_system_family, "LINUX") + + # Container Definition + command = try(each.value.command, var.container_definition_defaults.command, []) + cpu = try(each.value.cpu, var.container_definition_defaults.cpu, null) + dependencies = try(each.value.dependencies, var.container_definition_defaults.dependencies, []) # depends_on is a reserved word + disable_networking = try(each.value.disable_networking, var.container_definition_defaults.disable_networking, null) + dns_search_domains = try(each.value.dns_search_domains, var.container_definition_defaults.dns_search_domains, []) + dns_servers = try(each.value.dns_servers, var.container_definition_defaults.dns_servers, []) + docker_labels = try(each.value.docker_labels, var.container_definition_defaults.docker_labels, {}) + docker_security_options = try(each.value.docker_security_options, var.container_definition_defaults.docker_security_options, []) + entrypoint = try(each.value.entrypoint, var.container_definition_defaults.entrypoint, []) + environment = try(each.value.environment, var.container_definition_defaults.environment, []) + environment_files = try(each.value.environment_files, var.container_definition_defaults.environment_files, []) + essential = try(each.value.essential, var.container_definition_defaults.essential, null) + extra_hosts = try(each.value.extra_hosts, var.container_definition_defaults.extra_hosts, []) + firelens_configuration = try(each.value.firelens_configuration, var.container_definition_defaults.firelens_configuration, {}) + health_check = try(each.value.health_check, var.container_definition_defaults.health_check, {}) + hostname = try(each.value.hostname, var.container_definition_defaults.hostname, null) + image = try(each.value.image, var.container_definition_defaults.image, null) + interactive = try(each.value.interactive, var.container_definition_defaults.interactive, false) + links = try(each.value.links, var.container_definition_defaults.links, []) + linux_parameters = try(each.value.linux_parameters, var.container_definition_defaults.linux_parameters, {}) + log_configuration = try(each.value.log_configuration, var.container_definition_defaults.log_configuration, {}) + memory = try(each.value.memory, var.container_definition_defaults.memory, null) + memory_reservation = try(each.value.memory_reservation, var.container_definition_defaults.memory_reservation, null) + mount_points = try(each.value.mount_points, var.container_definition_defaults.mount_points, []) + name = try(each.value.name, each.key) + port_mappings = try(each.value.port_mappings, var.container_definition_defaults.port_mappings, []) + privileged = try(each.value.privileged, var.container_definition_defaults.privileged, false) + pseudo_terminal = try(each.value.pseudo_terminal, var.container_definition_defaults.pseudo_terminal, false) + readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.container_definition_defaults.readonly_root_filesystem, true) + repository_credentials = try(each.value.repository_credentials, var.container_definition_defaults.repository_credentials, {}) + resource_requirements = try(each.value.resource_requirements, var.container_definition_defaults.resource_requirements, []) + secrets = try(each.value.secrets, var.container_definition_defaults.secrets, []) + start_timeout = try(each.value.start_timeout, var.container_definition_defaults.start_timeout, 30) + stop_timeout = try(each.value.stop_timeout, var.container_definition_defaults.stop_timeout, 120) + system_controls = try(each.value.system_controls, var.container_definition_defaults.system_controls, []) + ulimits = try(each.value.ulimits, var.container_definition_defaults.ulimits, []) + user = try(each.value.user, var.container_definition_defaults.user, null) + volumes_from = try(each.value.volumes_from, var.container_definition_defaults.volumes_from, []) + working_directory = try(each.value.working_directory, var.container_definition_defaults.working_directory, null) + + # CloudWatch Log Group + service = var.name + enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.container_definition_defaults.enable_cloudwatch_logging, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.container_definition_defaults.create_cloudwatch_log_group, true) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.container_definition_defaults.cloudwatch_log_group_retention_in_days, 14) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.container_definition_defaults.cloudwatch_log_group_kms_key_id, null) + + tags = var.tags +} + +################################################################################ +# Task Definition +################################################################################ + +locals { + create_task_definition = var.create && var.create_task_definition + + # This allows us to query both the existing as well as Terraform's state and get + # and get the max version of either source, useful for when external resources + # update the container definition + max_task_def_revision = local.create_task_definition ? max(aws_ecs_task_definition.this[0].revision, data.aws_ecs_task_definition.this[0].revision) : 0 + task_definition = local.create_task_definition ? "${aws_ecs_task_definition.this[0].family}:${local.max_task_def_revision}" : var.task_definition_arn +} + +# This allows us to query both the existing as well as Terraform's state and get +# and get the max version of either source, useful for when external resources +# update the container definition +data "aws_ecs_task_definition" "this" { + count = local.create_task_definition ? 1 : 0 + + task_definition = aws_ecs_task_definition.this[0].family + + depends_on = [ + # Needs to exist first on first deployment + aws_ecs_task_definition.this + ] +} + +resource "aws_ecs_task_definition" "this" { + count = local.create_task_definition ? 1 : 0 + + # Convert map of maps to array of maps before JSON encoding + container_definitions = jsonencode([for k, v in module.container_definition : v.container_definition]) + cpu = var.cpu + + dynamic "ephemeral_storage" { + for_each = length(var.ephemeral_storage) > 0 ? [var.ephemeral_storage] : [] + + content { + size_in_gib = ephemeral_storage.value.size_in_gib + } + } + + execution_role_arn = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn) + family = coalesce(var.family, var.name) + + dynamic "inference_accelerator" { + for_each = var.inference_accelerator + + content { + device_name = inference_accelerator.value.device_name + device_type = inference_accelerator.value.device_type + } + } + + ipc_mode = var.ipc_mode + memory = var.memory + network_mode = var.network_mode + pid_mode = var.pid_mode + + dynamic "placement_constraints" { + for_each = var.placement_constraints + + content { + expression = try(placement_constraints.value.expression, null) + type = placement_constraints.value.type + } + } + + dynamic "proxy_configuration" { + for_each = length(var.proxy_configuration) > 0 ? [var.proxy_configuration] : [] + + content { + container_name = proxy_configuration.value.container_name + properties = try(proxy_configuration.value.properties, null) + type = try(proxy_configuration.value.type, null) + } + } + + requires_compatibilities = var.requires_compatibilities + + dynamic "runtime_platform" { + for_each = length(var.runtime_platform) > 0 ? [var.runtime_platform] : [] + + content { + cpu_architecture = try(runtime_platform.value.cpu_architecture, null) + operating_system_family = try(runtime_platform.value.operating_system_family, null) + } + } + + skip_destroy = var.skip_destroy + task_role_arn = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn) + + dynamic "volume" { + for_each = var.volume + + content { + dynamic "docker_volume_configuration" { + for_each = try([volume.value.docker_volume_configuration], []) + + content { + autoprovision = try(docker_volume_configuration.value.autoprovision, null) + driver = try(docker_volume_configuration.value.driver, null) + driver_opts = try(docker_volume_configuration.value.driver_opts, null) + labels = try(docker_volume_configuration.value.labels, null) + scope = try(docker_volume_configuration.value.scope, null) + } + } + + dynamic "efs_volume_configuration" { + for_each = try([volume.value.efs_volume_configuration], []) + + content { + dynamic "authorization_config" { + for_each = try([efs_volume_configuration.value.authorization_config], []) + + content { + access_point_id = try(authorization_config.value.access_point_id, null) + iam = try(authorization_config.value.iam, null) + } + } + + file_system_id = efs_volume_configuration.value.file_system_id + root_directory = try(efs_volume_configuration.value.root_directory, null) + transit_encryption = try(efs_volume_configuration.value.transit_encryption, null) + transit_encryption_port = try(efs_volume_configuration.value.transit_encryption_port, null) + } + } + + dynamic "fsx_windows_file_server_volume_configuration" { + for_each = try([volume.value.fsx_windows_file_server_volume_configuration], []) + + content { + dynamic "authorization_config" { + for_each = try([fsx_windows_file_server_volume_configuration.value.authorization_config], []) + + content { + credentials_parameter = authorization_config.value.credentials_parameter + domain = authorization_config.value.domain + } + } + + file_system_id = fsx_windows_file_server_volume_configuration.value.file_system_id + root_directory = fsx_windows_file_server_volume_configuration.value.root_directory + } + } + + host_path = try(volume.value.host_path, null) + name = try(volume.value.name, volume.key) + } + } + + tags = merge(var.tags, var.task_tags) + + lifecycle { + create_before_destroy = true + } +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +locals { + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.name), "") + + create_task_exec_iam_role = local.create_task_definition && var.create_task_exec_iam_role + create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy +} + +data "aws_iam_policy_document" "task_exec_assume" { + count = local.create_task_exec_iam_role ? 1 : 0 + + statement { + sid = "ECSTaskExecutionAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.${local.dns_suffix}"] + } + } +} + +resource "aws_iam_role" "task_exec" { + count = local.create_task_exec_iam_role ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + path = var.task_exec_iam_role_path + description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${local.task_exec_iam_role_name}") + + assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json + permissions_boundary = var.task_exec_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec_additional" { + for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role } + + role = aws_iam_role.task_exec[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "Logs" + actions = [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ] + resources = ["*"] + } + + # Pulled from AmazonECSTaskExecutionRolePolicy + statement { + sid = "ECR" + actions = [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : [] + + content { + sid = "GetSSMParams" + actions = ["ssm:GetParameters"] + resources = var.task_exec_ssm_param_arns + } + } + + dynamic "statement" { + for_each = length(var.task_exec_secret_arns) > 0 ? [1] : [] + + content { + sid = "GetSecrets" + actions = ["secretsmanager:GetSecretValue"] + resources = var.task_exec_secret_arns + } + } + + dynamic "statement" { + for_each = var.task_exec_iam_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name + name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null + description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy") + policy = data.aws_iam_policy_document.task_exec[0].json + + tags = merge(var.tags, var.task_exec_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "task_exec" { + count = local.create_task_exec_policy ? 1 : 0 + + role = aws_iam_role.task_exec[0].name + policy_arn = aws_iam_policy.task_exec[0].arn +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +locals { + tasks_iam_role_name = try(coalesce(var.tasks_iam_role_name, var.name), "") + create_tasks_iam_role = local.create_task_definition && var.create_tasks_iam_role +} + +data "aws_iam_policy_document" "tasks_assume" { + count = local.create_tasks_iam_role ? 1 : 0 + + statement { + sid = "ECSTasksAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs-tasks.${local.dns_suffix}"] + } + + # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role + condition { + test = "ArnLike" + variable = "aws:SourceArn" + values = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:*"] + } + + condition { + test = "StringEquals" + variable = "aws:SourceAccount" + values = [local.account_id] + } + } +} + +resource "aws_iam_role" "tasks" { + count = local.create_tasks_iam_role ? 1 : 0 + + name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name + name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null + path = var.tasks_iam_role_path + description = var.tasks_iam_role_description + + assume_role_policy = data.aws_iam_policy_document.tasks_assume[0].json + permissions_boundary = var.tasks_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.tasks_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "tasks" { + for_each = { for k, v in var.tasks_iam_role_policies : k => v if local.create_tasks_iam_role } + + role = aws_iam_role.tasks[0].name + policy_arn = each.value +} + +data "aws_iam_policy_document" "tasks" { + count = local.create_tasks_iam_role && length(var.tasks_iam_role_statements) > 0 ? 1 : 0 + + dynamic "statement" { + for_each = var.tasks_iam_role_statements + + content { + sid = try(statement.value.sid, null) + actions = try(statement.value.actions, null) + not_actions = try(statement.value.not_actions, null) + effect = try(statement.value.effect, null) + resources = try(statement.value.resources, null) + not_resources = try(statement.value.not_resources, null) + + dynamic "principals" { + for_each = try(statement.value.principals, []) + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = try(statement.value.not_principals, []) + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = try(statement.value.conditions, []) + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_role_policy" "tasks" { + count = local.create_tasks_iam_role && length(var.tasks_iam_role_statements) > 0 ? 1 : 0 + + name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name + name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null + policy = data.aws_iam_policy_document.tasks[0].json + role = aws_iam_role.tasks[0].id +} + +################################################################################ +# Task Set +################################################################################ + +resource "aws_ecs_task_set" "this" { + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html + count = local.create_task_definition && local.is_external_deployment && !var.ignore_task_definition_changes ? 1 : 0 + + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) + cluster = var.cluster_arn + external_id = var.external_id + task_definition = local.task_definition + + dynamic "network_configuration" { + for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "load_balancer" { + for_each = var.load_balancer + + content { + load_balancer_name = try(load_balancer.value.load_balancer_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + container_name = load_balancer.value.container_name + container_port = try(load_balancer.value.container_port, null) + } + } + + dynamic "service_registries" { + for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + launch_type = var.launch_type + + dynamic "capacity_provider_strategy" { + for_each = var.capacity_provider_strategy + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + platform_version = local.is_fargate ? var.platform_version : null + + dynamic "scale" { + for_each = length(var.scale) > 0 ? [var.scale] : [] + + content { + unit = try(scale.value.unit, null) + value = try(scale.value.value, null) + } + } + + force_delete = var.force_delete + wait_until_stable = var.wait_until_stable + wait_until_stable_timeout = var.wait_until_stable_timeout + + tags = merge(var.tags, var.task_tags) + + lifecycle { + ignore_changes = [ + scale, # Always ignored + ] + } +} + +################################################################################ +# Task Set - Ignore `task_definition` +################################################################################ + +resource "aws_ecs_task_set" "ignore_task_definition" { + # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html + count = local.create_task_definition && local.is_external_deployment && var.ignore_task_definition_changes ? 1 : 0 + + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) + cluster = var.cluster_arn + external_id = var.external_id + task_definition = local.task_definition + + dynamic "network_configuration" { + for_each = var.network_mode == "awsvpc" ? [local.network_configuration] : [] + + content { + assign_public_ip = network_configuration.value.assign_public_ip + security_groups = network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "load_balancer" { + for_each = var.load_balancer + + content { + load_balancer_name = try(load_balancer.value.load_balancer_name, null) + target_group_arn = try(load_balancer.value.target_group_arn, null) + container_name = load_balancer.value.container_name + container_port = try(load_balancer.value.container_port, null) + } + } + + dynamic "service_registries" { + for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + + content { + container_name = try(service_registries.value.container_name, null) + container_port = try(service_registries.value.container_port, null) + port = try(service_registries.value.port, null) + registry_arn = service_registries.value.registry_arn + } + } + + launch_type = var.launch_type + + dynamic "capacity_provider_strategy" { + for_each = var.capacity_provider_strategy + + content { + base = try(capacity_provider_strategy.value.base, null) + capacity_provider = capacity_provider_strategy.value.capacity_provider + weight = try(capacity_provider_strategy.value.weight, null) + } + } + + platform_version = local.is_fargate ? var.platform_version : null + + dynamic "scale" { + for_each = length(var.scale) > 0 ? [var.scale] : [] + + content { + unit = try(scale.value.unit, null) + value = try(scale.value.value, null) + } + } + + force_delete = var.force_delete + wait_until_stable = var.wait_until_stable + wait_until_stable_timeout = var.wait_until_stable_timeout + + tags = merge(var.tags, var.task_tags) + + lifecycle { + ignore_changes = [ + scale, # Always ignored + task_definition, + ] + } +} + +################################################################################ +# Autoscaling +################################################################################ + +locals { + enable_autoscaling = var.create && var.enable_autoscaling && !local.is_daemon + + cluster_name = element(split("/", var.cluster_arn), 1) +} + +resource "aws_appautoscaling_target" "this" { + count = local.enable_autoscaling ? 1 : 0 + + # Desired needs to be between or equal to min/max + min_capacity = min(var.autoscaling_min_capacity, var.desired_count) + max_capacity = max(var.autoscaling_max_capacity, var.desired_count) + + resource_id = "service/${local.cluster_name}/${try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name)}" + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +resource "aws_appautoscaling_policy" "this" { + for_each = { for k, v in var.autoscaling_policies : k => v if local.enable_autoscaling } + + name = try(each.value.name, each.key) + policy_type = try(each.value.policy_type, "TargetTrackingScaling") + resource_id = aws_appautoscaling_target.this[0].resource_id + scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension + service_namespace = aws_appautoscaling_target.this[0].service_namespace + + dynamic "step_scaling_policy_configuration" { + for_each = try([each.value.step_scaling_policy_configuration], []) + + content { + adjustment_type = try(step_scaling_policy_configuration.value.adjustment_type, null) + cooldown = try(step_scaling_policy_configuration.value.cooldown, null) + metric_aggregation_type = try(step_scaling_policy_configuration.value.metric_aggregation_type, null) + min_adjustment_magnitude = try(step_scaling_policy_configuration.value.min_adjustment_magnitude, null) + + dynamic "step_adjustment" { + for_each = try(step_scaling_policy_configuration.value.step_adjustment, []) + + content { + metric_interval_lower_bound = try(step_adjustment.value.metric_interval_lower_bound, null) + metric_interval_upper_bound = try(step_adjustment.value.metric_interval_upper_bound, null) + scaling_adjustment = try(step_adjustment.value.scaling_adjustment, null) + } + } + } + } + + dynamic "target_tracking_scaling_policy_configuration" { + for_each = try(each.value.policy_type, null) == "TargetTrackingScaling" ? try([each.value.target_tracking_scaling_policy_configuration], []) : [] + + content { + dynamic "customized_metric_specification" { + for_each = try([target_tracking_scaling_policy_configuration.value.customized_metric_specification], []) + + content { + dynamic "dimensions" { + for_each = try(customized_metric_specification.value.dimensions, []) + + content { + name = dimensions.value.name + value = dimensions.value.value + } + } + + metric_name = customized_metric_specification.value.metric_name + namespace = customized_metric_specification.value.namespace + statistic = customized_metric_specification.value.statistic + unit = try(customized_metric_specification.value.unit, null) + } + } + + disable_scale_in = try(target_tracking_scaling_policy_configuration.value.disable_scale_in, null) + + dynamic "predefined_metric_specification" { + for_each = try([target_tracking_scaling_policy_configuration.value.predefined_metric_specification], []) + + content { + predefined_metric_type = predefined_metric_specification.value.predefined_metric_type + resource_label = try(predefined_metric_specification.value.resource_label, null) + } + } + + scale_in_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_in_cooldown, 300) + scale_out_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_out_cooldown, 60) + target_value = try(target_tracking_scaling_policy_configuration.value.target_value, 75) + } + } +} + +resource "aws_appautoscaling_scheduled_action" "this" { + for_each = { for k, v in var.autoscaling_scheduled_actions : k => v if local.enable_autoscaling } + + name = try(each.value.name, each.key) + service_namespace = aws_appautoscaling_target.this[0].service_namespace + resource_id = aws_appautoscaling_target.this[0].resource_id + scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension + + scalable_target_action { + min_capacity = each.value.min_capacity + max_capacity = each.value.max_capacity + } + + schedule = each.value.schedule + start_time = try(each.value.start_time, null) + end_time = try(each.value.end_time, null) + timezone = try(each.value.timezone, null) +} + +################################################################################ +# Security Group +################################################################################ + +locals { + create_security_group = var.create && var.create_security_group && var.network_mode == "awsvpc" + security_group_name = try(coalesce(var.security_group_name, var.name), "") +} + +data "aws_subnet" "this" { + count = local.create_security_group ? 1 : 0 + + id = element(var.subnet_ids, 0) +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + name = var.security_group_use_name_prefix ? null : local.security_group_name + name_prefix = var.security_group_use_name_prefix ? "${local.security_group_name}-" : null + description = var.security_group_description + vpc_id = data.aws_subnet.this[0].vpc_id + + tags = merge(var.tags, var.security_group_tags) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_security_group_rule" "this" { + for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } + + # Required + security_group_id = aws_security_group.this[0].id + protocol = each.value.protocol + from_port = each.value.from_port + to_port = each.value.to_port + type = each.value.type + + # Optional + description = lookup(each.value, "description", null) + cidr_blocks = lookup(each.value, "cidr_blocks", null) + ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null) + prefix_list_ids = lookup(each.value, "prefix_list_ids", null) + self = lookup(each.value, "self", null) + source_security_group_id = lookup(each.value, "source_security_group_id", null) +} diff --git a/modules/service/outputs.tf b/modules/service/outputs.tf index e69de29..059a64d 100644 --- a/modules/service/outputs.tf +++ b/modules/service/outputs.tf @@ -0,0 +1,152 @@ +################################################################################ +# Service +################################################################################ + +output "id" { + description = "ARN that identifies the service" + value = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id, null) +} + +output "name" { + description = "Name of the service" + value = try(aws_ecs_service.this[0].name, aws_ecs_service.ignore_task_definition[0].name, null) +} + +################################################################################ +# IAM Role +################################################################################ + +output "iam_role_name" { + description = "Service IAM role name" + value = try(aws_iam_role.service[0].name, null) +} + +output "iam_role_arn" { + description = "Service IAM role ARN" + value = try(aws_iam_role.service[0].arn, null) +} + +output "iam_role_unique_id" { + description = "Stable and unique string identifying the service IAM role" + value = try(aws_iam_role.service[0].unique_id, null) +} + +################################################################################ +# Container Definition +################################################################################ + +output "container_definitions" { + description = "Container definitions" + value = module.container_definition +} + +################################################################################ +# Task Definition +################################################################################ + +output "task_definition_arn" { + description = "Full ARN of the Task Definition (including both `family` and `revision`)" + value = try(aws_ecs_task_definition.this[0].arn, null) +} + +output "task_definition_revision" { + description = "Revision of the task in a particular family" + value = try(aws_ecs_task_definition.this[0].revision, null) +} + +output "task_definition_family" { + description = "The unique name of the task definition" + value = try(aws_ecs_task_definition.this[0].family, null) +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = try(aws_iam_role.task_exec[0].name, null) +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = try(aws_iam_role.task_exec[0].arn, null) +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = try(aws_iam_role.task_exec[0].unique_id, null) +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +output "tasks_iam_role_name" { + description = "Tasks IAM role name" + value = try(aws_iam_role.tasks[0].name, null) +} + +output "tasks_iam_role_arn" { + description = "Tasks IAM role ARN" + value = try(aws_iam_role.tasks[0].arn, null) +} + +output "tasks_iam_role_unique_id" { + description = "Stable and unique string identifying the tasks IAM role" + value = try(aws_iam_role.tasks[0].unique_id, null) +} + +################################################################################ +# Task Set +################################################################################ + +output "task_set_id" { + description = "The ID of the task set" + value = try(aws_ecs_task_set.this[0].task_set_id, aws_ecs_task_set.ignore_task_definition[0].task_set_id, null) +} + +output "task_set_arn" { + description = "The Amazon Resource Name (ARN) that identifies the task set" + value = try(aws_ecs_task_set.this[0].arn, aws_ecs_task_set.ignore_task_definition[0].arn, null) +} + +output "task_set_stability_status" { + description = "The stability status. This indicates whether the task set has reached a steady state" + value = try(aws_ecs_task_set.this[0].stability_status, aws_ecs_task_set.ignore_task_definition[0].stability_status, null) +} + +output "task_set_status" { + description = "The status of the task set" + value = try(aws_ecs_task_set.this[0].status, aws_ecs_task_set.ignore_task_definition[0].status, null) +} + +################################################################################ +# Autoscaling +################################################################################ + +output "autoscaling_policies" { + description = "Map of autoscaling policies and their attributes" + value = aws_appautoscaling_policy.this +} + +output "autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions and their attributes" + value = aws_appautoscaling_scheduled_action.this +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = try(aws_security_group.this[0].arn, null) +} + +output "security_group_id" { + description = "ID of the security group" + value = try(aws_security_group.this[0].id, null) +} diff --git a/modules/service/variables.tf b/modules/service/variables.tf index e69de29..4c2136e 100644 --- a/modules/service/variables.tf +++ b/modules/service/variables.tf @@ -0,0 +1,644 @@ +variable "create" { + description = "Determines whether resources will be created (affects all resources)" + type = bool + default = true +} + +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + +################################################################################ +# Service +################################################################################ + +variable "ignore_task_definition_changes" { + description = "Whether changes to service `task_definition` changes should be ignored" + type = bool + default = false +} + +variable "alarms" { + description = "Information about the CloudWatch alarms" + type = any + default = {} +} + +variable "capacity_provider_strategy" { + description = "Capacity provider strategies to use for the service. Can be one or more" + type = any + default = {} +} + +variable "cluster_arn" { + description = "ARN of the ECS cluster where the resources will be provisioned" + type = string + default = "" +} + +variable "deployment_circuit_breaker" { + description = "Configuration block for deployment circuit breaker" + type = any + default = {} +} + +variable "deployment_controller" { + description = "Configuration block for deployment controller configuration" + type = any + default = {} +} + +variable "deployment_maximum_percent" { + description = "Upper limit (as a percentage of the service's `desired_count`) of the number of running tasks that can be running in a service during a deployment" + type = number + default = 200 +} + +variable "deployment_minimum_healthy_percent" { + description = "Lower limit (as a percentage of the service's `desired_count`) of the number of running tasks that must remain running and healthy in a service during a deployment" + type = number + default = 66 +} + +variable "desired_count" { + description = "Number of instances of the task definition to place and keep running. Defaults to `0`" + type = number + default = 1 +} + +variable "enable_ecs_managed_tags" { + description = "Specifies whether to enable Amazon ECS managed tags for the tasks within the service" + type = bool + default = true +} + +variable "enable_execute_command" { + description = "Specifies whether to enable Amazon ECS Exec for the tasks within the service" + type = bool + default = false +} + +variable "force_new_deployment" { + description = "Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates" + type = bool + default = true +} + +variable "health_check_grace_period_seconds" { + description = "Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers" + type = number + default = null +} + +variable "launch_type" { + description = "Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE`" + type = string + default = "FARGATE" +} + +variable "load_balancer" { + description = "Configuration block for load balancers" + type = any + default = {} +} + +variable "name" { + description = "Name of the service (up to 255 letters, numbers, hyphens, and underscores)" + type = string + default = null +} + +variable "assign_public_ip" { + description = "Assign a public IP address to the ENI (Fargate launch type only)" + type = bool + default = false +} + +variable "security_group_ids" { + description = "List of security groups to associate with the task or service" + type = list(string) + default = [] +} + +variable "subnet_ids" { + description = "List of subnets to associate with the task or service" + type = list(string) + default = [] +} + +variable "ordered_placement_strategy" { + description = "Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence" + type = any + default = {} +} + +variable "placement_constraints" { + description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10)" + type = any + default = {} +} + +variable "platform_version" { + description = "Platform version on which to run your service. Only applicable for `launch_type` set to `FARGATE`. Defaults to `LATEST`" + type = string + default = null +} + +variable "propagate_tags" { + description = "Specifies whether to propagate the tags from the task definition or the service to the tasks. The valid values are `SERVICE` and `TASK_DEFINITION`" + type = string + default = null +} + +variable "scheduling_strategy" { + description = "Scheduling strategy to use for the service. The valid values are `REPLICA` and `DAEMON`. Defaults to `REPLICA`" + type = string + default = null +} + +variable "service_connect_configuration" { + description = "The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace" + type = any + default = {} +} + +variable "service_registries" { + description = "Service discovery registries for the service" + type = any + default = {} +} + +variable "timeouts" { + description = "Create, update, and delete timeout configurations for the service" + type = map(string) + default = {} +} + +variable "triggers" { + description = "Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()`" + type = any + default = {} +} + +variable "wait_for_steady_state" { + description = "If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false`" + type = bool + default = null +} + +################################################################################ +# Service - IAM Role +################################################################################ + +variable "create_iam_role" { + description = "Determines whether the ECS service IAM role should be created" + type = bool + default = true +} + +variable "iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "iam_role_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Task Definition +################################################################################ + +variable "create_task_definition" { + description = "Determines whether to create a task definition or use existing/provided" + type = bool + default = true +} + +variable "task_definition_arn" { + description = "Existing task definition ARN. Required when `create_task_definition` is `false`" + type = string + default = null +} + +variable "container_definitions" { + description = "A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document" + type = any + default = {} +} + +variable "container_definition_defaults" { + description = "A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions`" + type = any + default = {} +} + +variable "cpu" { + description = "Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" + type = number + default = 1024 +} + +variable "ephemeral_storage" { + description = "The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate" + type = any + default = {} +} + +variable "family" { + description = "A unique name for your task definition" + type = string + default = null +} + +variable "inference_accelerator" { + description = "Configuration block(s) with Inference Accelerators settings" + type = any + default = {} +} + +variable "ipc_mode" { + description = "IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`" + type = string + default = null +} + +variable "memory" { + description = "Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required" + type = number + default = 2048 +} + +variable "network_mode" { + description = "Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host`" + type = string + default = "awsvpc" +} + +variable "pid_mode" { + description = "Process namespace to use for the containers in the task. The valid values are `host` and `task`" + type = string + default = null +} + +# Shared between service and task definition +# variable "placement_constraints" { +# description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10)" +# type = any +# default = {} +# } + +variable "proxy_configuration" { + description = "Configuration block for the App Mesh proxy" + type = any + default = {} +} + +variable "requires_compatibilities" { + description = "Set of launch types required by the task. The valid values are `EC2` and `FARGATE`" + type = list(string) + default = ["FARGATE"] +} + +variable "runtime_platform" { + description = "Configuration block for `runtime_platform` that containers in your task may use" + type = any + default = { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + } +} + +variable "skip_destroy" { + description = "If true, the task is not deleted when the service is deleted" + type = bool + default = null +} + +variable "volume" { + description = "Configuration block for volumes that containers in your task may use" + type = any + default = {} +} + +variable "task_tags" { + description = "A map of additional tags to add to the task definition/set created" + type = map(string) + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = true +} + +variable "task_exec_iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Tasks - IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html +################################################################################ + +variable "create_tasks_iam_role" { + description = "Determines whether the ECS tasks IAM role should be created" + type = bool + default = true +} + +variable "tasks_iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +variable "tasks_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "tasks_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "tasks_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "tasks_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "tasks_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "tasks_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "tasks_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "tasks_iam_role_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Task Set +################################################################################ + +variable "external_id" { + description = "The external ID associated with the task set" + type = string + default = null +} + +variable "scale" { + description = "A floating-point percentage of the desired number of tasks to place and keep running in the task set" + type = any + default = {} +} + +variable "force_delete" { + description = "Whether to allow deleting the task set without waiting for scaling down to 0" + type = bool + default = null +} + +variable "wait_until_stable" { + description = "Whether terraform should wait until the task set has reached `STEADY_STATE`" + type = bool + default = null +} + +variable "wait_until_stable_timeout" { + description = "Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or µs), `ms`, `s`, `m`, and `h`. Default `10m`" + type = number + default = null +} + +################################################################################ +# Autoscaling +################################################################################ + +variable "enable_autoscaling" { + description = "Determines whether to enable autoscaling for the service" + type = bool + default = true +} + +variable "autoscaling_min_capacity" { + description = "Minimum number of tasks to run in your service" + type = number + default = 1 +} + +variable "autoscaling_max_capacity" { + description = "Maximum number of tasks to run in your service" + type = number + default = 10 +} + +variable "autoscaling_policies" { + description = "Map of autoscaling policies to create for the service" + type = any + default = { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + memory = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + } +} + +variable "autoscaling_scheduled_actions" { + description = "Map of autoscaling scheduled actions to create for the service" + type = any + default = {} +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines if a security group is created" + type = bool + default = true +} + +variable "security_group_name" { + description = "Name to use on security group created" + type = string + default = null +} + +variable "security_group_use_name_prefix" { + description = "Determines whether the security group name (`security_group_name`) is used as a prefix" + type = bool + default = true +} + +variable "security_group_description" { + description = "Description of the security group created" + type = string + default = null +} + +variable "security_group_rules" { + description = "Security group rules to add to the security group created" + type = any + default = {} +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} +} diff --git a/modules/service/versions.tf b/modules/service/versions.tf index 35402be..290d221 100644 --- a/modules/service/versions.tf +++ b/modules/service/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.6" + version = ">= 4.55" } } } diff --git a/outputs.tf b/outputs.tf index 2b1d6bb..7f7d17a 100644 --- a/outputs.tf +++ b/outputs.tf @@ -4,35 +4,59 @@ output "cluster_arn" { description = "ARN that identifies the cluster" - value = try(aws_ecs_cluster.this[0].arn, null) + value = module.cluster.arn } output "cluster_id" { description = "ID that identifies the cluster" - value = try(aws_ecs_cluster.this[0].id, null) + value = module.cluster.id } output "cluster_name" { description = "Name that identifies the cluster" - value = try(aws_ecs_cluster.this[0].name, null) + value = module.cluster.name } -################################################################################ -# Cluster Capacity Providers -################################################################################ +output "cloudwatch_log_group_name" { + description = "Name of cloudwatch log group created" + value = module.cluster.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "Arn of cloudwatch log group created" + value = module.cluster.cloudwatch_log_group_arn +} output "cluster_capacity_providers" { description = "Map of cluster capacity providers attributes" - value = { - for k, v in aws_ecs_cluster_capacity_providers.this : v.id => v - } + value = module.cluster.cluster_capacity_providers +} + +output "autoscaling_capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = module.cluster.autoscaling_capacity_providers +} + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.cluster.task_exec_iam_role_name +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.cluster.task_exec_iam_role_arn +} + +output "task_exec_iam_role_unique_id" { + description = "Stable and unique string identifying the task execution IAM role" + value = module.cluster.task_exec_iam_role_unique_id } ################################################################################ -# Capacity Provider - Autoscaling Group(s) +# Service(s) ################################################################################ -output "autoscaling_capacity_providers" { - description = "Map of autoscaling capacity providers created and their attributes" - value = aws_ecs_capacity_provider.this +output "services" { + description = "Map of services created and their attributes" + value = module.service } diff --git a/variables.tf b/variables.tf index 6a74075..97705e2 100644 --- a/variables.tf +++ b/variables.tf @@ -35,6 +35,46 @@ variable "cluster_settings" { } } +variable "cluster_service_connect_defaults" { + description = "Configures a default Service Connect namespace" + type = map(string) + default = {} +} + +variable "cluster_tags" { + description = "A map of additional tags to add to the cluster" + type = map(string) + default = {} +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +variable "create_cloudwatch_log_group" { + description = "Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled" + type = bool + default = true +} + +variable "cloudwatch_log_group_retention_in_days" { + description = "Number of days to retain log events" + type = number + default = 90 +} + +variable "cloudwatch_log_group_kms_key_id" { + description = "If a KMS Key ARN is set, this key will be used to encrypt the corresponding log group. Please be sure that the KMS Key has an appropriate key policy (https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html)" + type = string + default = null +} + +variable "cloudwatch_log_group_tags" { + description = "A map of additional tags to add to the log group created" + type = map(string) + default = {} +} + ################################################################################ # Capacity Providers ################################################################################ @@ -52,7 +92,94 @@ variable "fargate_capacity_providers" { } variable "autoscaling_capacity_providers" { - description = "Map of autoscaling capacity provider definitons to create for the cluster" + description = "Map of autoscaling capacity provider definitions to create for the cluster" + type = any + default = {} +} + +################################################################################ +# Task Execution - IAM Role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html +################################################################################ + +variable "create_task_exec_iam_role" { + description = "Determines whether the ECS task definition IAM role should be created" + type = bool + default = false +} + +variable "task_exec_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "task_exec_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix" + type = bool + default = true +} + +variable "task_exec_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "task_exec_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "task_exec_iam_role_permissions_boundary" { + description = "ARN of the policy that is used to set the permissions boundary for the IAM role" + type = string + default = null +} + +variable "task_exec_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} +} + +variable "task_exec_iam_role_policies" { + description = "Map of IAM role policy ARNs to attach to the IAM role" + type = map(string) + default = {} +} + +variable "create_task_exec_policy" { + description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters" + type = bool + default = true +} + +variable "task_exec_ssm_param_arns" { + description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:ssm:*:*:parameter/*"] +} + +variable "task_exec_secret_arns" { + description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" + type = list(string) + default = ["arn:aws:secretsmanager:*:*:secret:*"] +} + +variable "task_exec_iam_statements" { + description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" + type = any + default = {} +} + +################################################################################ +# Service(s) +################################################################################ + +variable "services" { + description = "Map of service definitions to create" type = any default = {} } diff --git a/versions.tf b/versions.tf index 35402be..290d221 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.6" + version = ">= 4.55" } } } diff --git a/wrappers/README.md b/wrappers/README.md new file mode 100644 index 0000000..449acd8 --- /dev/null +++ b/wrappers/README.md @@ -0,0 +1,100 @@ +# Wrapper for the root module + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/cluster/README.md b/wrappers/cluster/README.md new file mode 100644 index 0000000..1a79528 --- /dev/null +++ b/wrappers/cluster/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/cluster` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers/cluster" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers/cluster?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers/cluster" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/cluster/main.tf b/wrappers/cluster/main.tf new file mode 100644 index 0000000..cfcee01 --- /dev/null +++ b/wrappers/cluster/main.tf @@ -0,0 +1,34 @@ +module "wrapper" { + source = "../../modules/cluster" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + tags = try(each.value.tags, var.defaults.tags, {}) + cluster_name = try(each.value.cluster_name, var.defaults.cluster_name, "") + cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, {}) + cluster_settings = try(each.value.cluster_settings, var.defaults.cluster_settings, { + name = "containerInsights" + value = "enabled" + }) + cluster_service_connect_defaults = try(each.value.cluster_service_connect_defaults, var.defaults.cluster_service_connect_defaults, {}) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.defaults.cloudwatch_log_group_retention_in_days, 90) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.defaults.cloudwatch_log_group_kms_key_id, null) + cloudwatch_log_group_tags = try(each.value.cloudwatch_log_group_tags, var.defaults.cloudwatch_log_group_tags, {}) + default_capacity_provider_use_fargate = try(each.value.default_capacity_provider_use_fargate, var.defaults.default_capacity_provider_use_fargate, true) + fargate_capacity_providers = try(each.value.fargate_capacity_providers, var.defaults.fargate_capacity_providers, {}) + autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, {}) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, var.defaults.task_exec_iam_role_path, null) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, var.defaults.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) +} diff --git a/wrappers/cluster/outputs.tf b/wrappers/cluster/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/cluster/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/cluster/variables.tf b/wrappers/cluster/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/cluster/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/cluster/versions.tf b/wrappers/cluster/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/wrappers/cluster/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/wrappers/container-definition/README.md b/wrappers/container-definition/README.md new file mode 100644 index 0000000..4731aa9 --- /dev/null +++ b/wrappers/container-definition/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/container-definition` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers/container-definition" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers/container-definition?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers/container-definition" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/container-definition/main.tf b/wrappers/container-definition/main.tf new file mode 100644 index 0000000..7537505 --- /dev/null +++ b/wrappers/container-definition/main.tf @@ -0,0 +1,52 @@ +module "wrapper" { + source = "../../modules/container-definition" + + for_each = var.items + + operating_system_family = try(each.value.operating_system_family, var.defaults.operating_system_family, "LINUX") + command = try(each.value.command, var.defaults.command, []) + cpu = try(each.value.cpu, var.defaults.cpu, null) + dependencies = try(each.value.dependencies, var.defaults.dependencies, []) + disable_networking = try(each.value.disable_networking, var.defaults.disable_networking, null) + dns_search_domains = try(each.value.dns_search_domains, var.defaults.dns_search_domains, []) + dns_servers = try(each.value.dns_servers, var.defaults.dns_servers, []) + docker_labels = try(each.value.docker_labels, var.defaults.docker_labels, {}) + docker_security_options = try(each.value.docker_security_options, var.defaults.docker_security_options, []) + entrypoint = try(each.value.entrypoint, var.defaults.entrypoint, []) + environment = try(each.value.environment, var.defaults.environment, []) + environment_files = try(each.value.environment_files, var.defaults.environment_files, []) + essential = try(each.value.essential, var.defaults.essential, null) + extra_hosts = try(each.value.extra_hosts, var.defaults.extra_hosts, []) + firelens_configuration = try(each.value.firelens_configuration, var.defaults.firelens_configuration, {}) + health_check = try(each.value.health_check, var.defaults.health_check, {}) + hostname = try(each.value.hostname, var.defaults.hostname, null) + image = try(each.value.image, var.defaults.image, null) + interactive = try(each.value.interactive, var.defaults.interactive, false) + links = try(each.value.links, var.defaults.links, []) + linux_parameters = try(each.value.linux_parameters, var.defaults.linux_parameters, {}) + log_configuration = try(each.value.log_configuration, var.defaults.log_configuration, {}) + memory = try(each.value.memory, var.defaults.memory, null) + memory_reservation = try(each.value.memory_reservation, var.defaults.memory_reservation, null) + mount_points = try(each.value.mount_points, var.defaults.mount_points, []) + name = try(each.value.name, var.defaults.name, null) + port_mappings = try(each.value.port_mappings, var.defaults.port_mappings, []) + privileged = try(each.value.privileged, var.defaults.privileged, false) + pseudo_terminal = try(each.value.pseudo_terminal, var.defaults.pseudo_terminal, false) + readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.defaults.readonly_root_filesystem, true) + repository_credentials = try(each.value.repository_credentials, var.defaults.repository_credentials, {}) + resource_requirements = try(each.value.resource_requirements, var.defaults.resource_requirements, []) + secrets = try(each.value.secrets, var.defaults.secrets, []) + start_timeout = try(each.value.start_timeout, var.defaults.start_timeout, 30) + stop_timeout = try(each.value.stop_timeout, var.defaults.stop_timeout, 120) + system_controls = try(each.value.system_controls, var.defaults.system_controls, []) + ulimits = try(each.value.ulimits, var.defaults.ulimits, []) + user = try(each.value.user, var.defaults.user, null) + volumes_from = try(each.value.volumes_from, var.defaults.volumes_from, []) + working_directory = try(each.value.working_directory, var.defaults.working_directory, null) + service = try(each.value.service, var.defaults.service, "") + enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.defaults.enable_cloudwatch_logging, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.defaults.cloudwatch_log_group_retention_in_days, 30) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.defaults.cloudwatch_log_group_kms_key_id, null) + tags = try(each.value.tags, var.defaults.tags, {}) +} diff --git a/wrappers/container-definition/outputs.tf b/wrappers/container-definition/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/container-definition/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/container-definition/variables.tf b/wrappers/container-definition/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/container-definition/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/container-definition/versions.tf b/wrappers/container-definition/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/wrappers/container-definition/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/wrappers/main.tf b/wrappers/main.tf new file mode 100644 index 0000000..ec58568 --- /dev/null +++ b/wrappers/main.tf @@ -0,0 +1,36 @@ +module "wrapper" { + source = "../" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + tags = try(each.value.tags, var.defaults.tags, {}) + cluster_name = try(each.value.cluster_name, var.defaults.cluster_name, "") + cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, {}) + cluster_settings = try(each.value.cluster_settings, var.defaults.cluster_settings, { + name = "containerInsights" + value = "enabled" + }) + cluster_service_connect_defaults = try(each.value.cluster_service_connect_defaults, var.defaults.cluster_service_connect_defaults, {}) + cluster_tags = try(each.value.cluster_tags, var.defaults.cluster_tags, {}) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.defaults.cloudwatch_log_group_retention_in_days, 90) + cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.defaults.cloudwatch_log_group_kms_key_id, null) + cloudwatch_log_group_tags = try(each.value.cloudwatch_log_group_tags, var.defaults.cloudwatch_log_group_tags, {}) + default_capacity_provider_use_fargate = try(each.value.default_capacity_provider_use_fargate, var.defaults.default_capacity_provider_use_fargate, true) + fargate_capacity_providers = try(each.value.fargate_capacity_providers, var.defaults.fargate_capacity_providers, {}) + autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, {}) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, var.defaults.task_exec_iam_role_path, null) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, var.defaults.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) + services = try(each.value.services, var.defaults.services, {}) +} diff --git a/wrappers/outputs.tf b/wrappers/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/service/README.md b/wrappers/service/README.md new file mode 100644 index 0000000..219da91 --- /dev/null +++ b/wrappers/service/README.md @@ -0,0 +1,100 @@ +# Wrapper for module: `modules/service` + +The configuration in this directory contains an implementation of a single module wrapper pattern, which allows managing several copies of a module in places where using the native Terraform 0.13+ `for_each` feature is not feasible (e.g., with Terragrunt). + +You may want to use a single Terragrunt configuration file to manage multiple resources without duplicating `terragrunt.hcl` files for each copy of the same module. + +This wrapper does not implement any extra functionality. + +## Usage with Terragrunt + +`terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/ecs/aws//wrappers/service" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-ecs.git//wrappers/service?ref=master" +} + +inputs = { + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Usage with Terraform + +```hcl +module "wrapper" { + source = "terraform-aws-modules/ecs/aws//wrappers/service" + + defaults = { # Default values + create = true + tags = { + Terraform = "true" + Environment = "dev" + } + } + + items = { + my-item = { + # omitted... can be any argument supported by the module + } + my-second-item = { + # omitted... can be any argument supported by the module + } + # omitted... + } +} +``` + +## Example: Manage multiple S3 buckets in one Terragrunt layer + +`eu-west-1/s3-buckets/terragrunt.hcl`: + +```hcl +terraform { + source = "tfr:///terraform-aws-modules/s3-bucket/aws//wrappers" + # Alternative source: + # source = "git::git@github.com:terraform-aws-modules/terraform-aws-s3-bucket.git//wrappers?ref=master" +} + +inputs = { + defaults = { + force_destroy = true + + attach_elb_log_delivery_policy = true + attach_lb_log_delivery_policy = true + attach_deny_insecure_transport_policy = true + attach_require_latest_tls_policy = true + } + + items = { + bucket1 = { + bucket = "my-random-bucket-1" + } + bucket2 = { + bucket = "my-random-bucket-2" + tags = { + Secure = "probably" + } + } + } +} +``` diff --git a/wrappers/service/main.tf b/wrappers/service/main.tf new file mode 100644 index 0000000..42ba6f1 --- /dev/null +++ b/wrappers/service/main.tf @@ -0,0 +1,125 @@ +module "wrapper" { + source = "../../modules/service" + + for_each = var.items + + create = try(each.value.create, var.defaults.create, true) + tags = try(each.value.tags, var.defaults.tags, {}) + ignore_task_definition_changes = try(each.value.ignore_task_definition_changes, var.defaults.ignore_task_definition_changes, false) + alarms = try(each.value.alarms, var.defaults.alarms, {}) + capacity_provider_strategy = try(each.value.capacity_provider_strategy, var.defaults.capacity_provider_strategy, {}) + cluster_arn = try(each.value.cluster_arn, var.defaults.cluster_arn, "") + deployment_circuit_breaker = try(each.value.deployment_circuit_breaker, var.defaults.deployment_circuit_breaker, {}) + deployment_controller = try(each.value.deployment_controller, var.defaults.deployment_controller, {}) + deployment_maximum_percent = try(each.value.deployment_maximum_percent, var.defaults.deployment_maximum_percent, 200) + deployment_minimum_healthy_percent = try(each.value.deployment_minimum_healthy_percent, var.defaults.deployment_minimum_healthy_percent, 66) + desired_count = try(each.value.desired_count, var.defaults.desired_count, 1) + enable_ecs_managed_tags = try(each.value.enable_ecs_managed_tags, var.defaults.enable_ecs_managed_tags, true) + enable_execute_command = try(each.value.enable_execute_command, var.defaults.enable_execute_command, false) + force_new_deployment = try(each.value.force_new_deployment, var.defaults.force_new_deployment, true) + health_check_grace_period_seconds = try(each.value.health_check_grace_period_seconds, var.defaults.health_check_grace_period_seconds, null) + launch_type = try(each.value.launch_type, var.defaults.launch_type, "FARGATE") + load_balancer = try(each.value.load_balancer, var.defaults.load_balancer, {}) + name = try(each.value.name, var.defaults.name, null) + assign_public_ip = try(each.value.assign_public_ip, var.defaults.assign_public_ip, false) + security_group_ids = try(each.value.security_group_ids, var.defaults.security_group_ids, []) + subnet_ids = try(each.value.subnet_ids, var.defaults.subnet_ids, []) + ordered_placement_strategy = try(each.value.ordered_placement_strategy, var.defaults.ordered_placement_strategy, {}) + placement_constraints = try(each.value.placement_constraints, var.defaults.placement_constraints, {}) + platform_version = try(each.value.platform_version, var.defaults.platform_version, null) + propagate_tags = try(each.value.propagate_tags, var.defaults.propagate_tags, null) + scheduling_strategy = try(each.value.scheduling_strategy, var.defaults.scheduling_strategy, null) + service_connect_configuration = try(each.value.service_connect_configuration, var.defaults.service_connect_configuration, {}) + service_registries = try(each.value.service_registries, var.defaults.service_registries, {}) + timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) + triggers = try(each.value.triggers, var.defaults.triggers, {}) + wait_for_steady_state = try(each.value.wait_for_steady_state, var.defaults.wait_for_steady_state, null) + create_iam_role = try(each.value.create_iam_role, var.defaults.create_iam_role, true) + iam_role_arn = try(each.value.iam_role_arn, var.defaults.iam_role_arn, null) + iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) + iam_role_path = try(each.value.iam_role_path, var.defaults.iam_role_path, null) + iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.defaults.iam_role_permissions_boundary, null) + iam_role_tags = try(each.value.iam_role_tags, var.defaults.iam_role_tags, {}) + iam_role_statements = try(each.value.iam_role_statements, var.defaults.iam_role_statements, {}) + create_task_definition = try(each.value.create_task_definition, var.defaults.create_task_definition, true) + task_definition_arn = try(each.value.task_definition_arn, var.defaults.task_definition_arn, null) + container_definitions = try(each.value.container_definitions, var.defaults.container_definitions, {}) + container_definition_defaults = try(each.value.container_definition_defaults, var.defaults.container_definition_defaults, {}) + cpu = try(each.value.cpu, var.defaults.cpu, 1024) + ephemeral_storage = try(each.value.ephemeral_storage, var.defaults.ephemeral_storage, {}) + family = try(each.value.family, var.defaults.family, null) + inference_accelerator = try(each.value.inference_accelerator, var.defaults.inference_accelerator, {}) + ipc_mode = try(each.value.ipc_mode, var.defaults.ipc_mode, null) + memory = try(each.value.memory, var.defaults.memory, 2048) + network_mode = try(each.value.network_mode, var.defaults.network_mode, "awsvpc") + pid_mode = try(each.value.pid_mode, var.defaults.pid_mode, null) + proxy_configuration = try(each.value.proxy_configuration, var.defaults.proxy_configuration, {}) + requires_compatibilities = try(each.value.requires_compatibilities, var.defaults.requires_compatibilities, ["FARGATE"]) + runtime_platform = try(each.value.runtime_platform, var.defaults.runtime_platform, { + operating_system_family = "LINUX" + cpu_architecture = "X86_64" + }) + skip_destroy = try(each.value.skip_destroy, var.defaults.skip_destroy, null) + volume = try(each.value.volume, var.defaults.volume, {}) + task_tags = try(each.value.task_tags, var.defaults.task_tags, {}) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, true) + task_exec_iam_role_arn = try(each.value.task_exec_iam_role_arn, var.defaults.task_exec_iam_role_arn, null) + task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) + task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) + task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, var.defaults.task_exec_iam_role_path, null) + task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) + task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, var.defaults.task_exec_iam_role_permissions_boundary, null) + task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) + task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) + task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) + create_tasks_iam_role = try(each.value.create_tasks_iam_role, var.defaults.create_tasks_iam_role, true) + tasks_iam_role_arn = try(each.value.tasks_iam_role_arn, var.defaults.tasks_iam_role_arn, null) + tasks_iam_role_name = try(each.value.tasks_iam_role_name, var.defaults.tasks_iam_role_name, null) + tasks_iam_role_use_name_prefix = try(each.value.tasks_iam_role_use_name_prefix, var.defaults.tasks_iam_role_use_name_prefix, true) + tasks_iam_role_path = try(each.value.tasks_iam_role_path, var.defaults.tasks_iam_role_path, null) + tasks_iam_role_description = try(each.value.tasks_iam_role_description, var.defaults.tasks_iam_role_description, null) + tasks_iam_role_permissions_boundary = try(each.value.tasks_iam_role_permissions_boundary, var.defaults.tasks_iam_role_permissions_boundary, null) + tasks_iam_role_tags = try(each.value.tasks_iam_role_tags, var.defaults.tasks_iam_role_tags, {}) + tasks_iam_role_policies = try(each.value.tasks_iam_role_policies, var.defaults.tasks_iam_role_policies, {}) + tasks_iam_role_statements = try(each.value.tasks_iam_role_statements, var.defaults.tasks_iam_role_statements, {}) + external_id = try(each.value.external_id, var.defaults.external_id, null) + scale = try(each.value.scale, var.defaults.scale, {}) + force_delete = try(each.value.force_delete, var.defaults.force_delete, null) + wait_until_stable = try(each.value.wait_until_stable, var.defaults.wait_until_stable, null) + wait_until_stable_timeout = try(each.value.wait_until_stable_timeout, var.defaults.wait_until_stable_timeout, null) + enable_autoscaling = try(each.value.enable_autoscaling, var.defaults.enable_autoscaling, true) + autoscaling_min_capacity = try(each.value.autoscaling_min_capacity, var.defaults.autoscaling_min_capacity, 1) + autoscaling_max_capacity = try(each.value.autoscaling_max_capacity, var.defaults.autoscaling_max_capacity, 10) + autoscaling_policies = try(each.value.autoscaling_policies, var.defaults.autoscaling_policies, { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + memory = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + }) + autoscaling_scheduled_actions = try(each.value.autoscaling_scheduled_actions, var.defaults.autoscaling_scheduled_actions, {}) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) + security_group_rules = try(each.value.security_group_rules, var.defaults.security_group_rules, {}) + security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) +} diff --git a/wrappers/service/outputs.tf b/wrappers/service/outputs.tf new file mode 100644 index 0000000..ec6da5f --- /dev/null +++ b/wrappers/service/outputs.tf @@ -0,0 +1,5 @@ +output "wrapper" { + description = "Map of outputs of a wrapper." + value = module.wrapper + # sensitive = false # No sensitive module output found +} diff --git a/wrappers/service/variables.tf b/wrappers/service/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/service/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/service/versions.tf b/wrappers/service/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/wrappers/service/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +} diff --git a/wrappers/variables.tf b/wrappers/variables.tf new file mode 100644 index 0000000..a6ea096 --- /dev/null +++ b/wrappers/variables.tf @@ -0,0 +1,11 @@ +variable "defaults" { + description = "Map of default values which will be used for each item." + type = any + default = {} +} + +variable "items" { + description = "Maps of items to create a wrapper from. Values are passed through to the module." + type = any + default = {} +} diff --git a/wrappers/versions.tf b/wrappers/versions.tf new file mode 100644 index 0000000..51cad10 --- /dev/null +++ b/wrappers/versions.tf @@ -0,0 +1,3 @@ +terraform { + required_version = ">= 0.13.1" +}