diff --git a/README.md b/README.md index 48487cf6..2900c4c5 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ module "ecs" { } # Cluster capacity providers + cluster_capacity_providers = ["FARGATE", "FARGATE_SPOT"] default_capacity_provider_strategy = { FARGATE = { weight = 50 @@ -146,9 +147,11 @@ module "ecs" { ## 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) +- [ECS cluster w/ integrated service(s)](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS container definition](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/container-definition) +- [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) +- [ECS cluster w/ ECS managed instances capacity provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/managed-instances) ## Requirements @@ -156,7 +159,7 @@ module "ecs" { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | ## Providers @@ -177,12 +180,13 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster |
map(object({
auto_scaling_group_arn = string
managed_draining = optional(string, "ENABLED")
managed_scaling = optional(object({
instance_warmup_period = optional(number)
maximum_scaling_step_size = optional(number)
minimum_scaling_step_size = optional(number)
status = optional(string)
target_capacity = optional(number)
}))
managed_termination_protection = optional(string)
name = optional(string) # Will fall back to use map key if not set
tags = optional(map(string), {})
}))
| `null` | no | +| [capacity\_providers](#input\_capacity\_providers) | Map of capacity provider definitions to create for the cluster |
map(object({
auto_scaling_group_provider = optional(object({
auto_scaling_group_arn = string
managed_draining = optional(string, "ENABLED")
managed_scaling = optional(object({
instance_warmup_period = optional(number)
maximum_scaling_step_size = optional(number)
minimum_scaling_step_size = optional(number)
status = optional(string)
target_capacity = optional(number)
}))
managed_termination_protection = optional(string)
}))
managed_instances_provider = optional(object({
infrastructure_role_arn = optional(string)
instance_launch_template = object({
ec2_instance_profile_arn = optional(string)
instance_requirements = optional(object({
accelerator_count = optional(object({
max = optional(number)
min = optional(number)
}))
accelerator_manufacturers = optional(list(string))
accelerator_names = optional(list(string))
accelerator_total_memory_mib = optional(object({
max = optional(number)
min = optional(number)
}))
accelerator_types = optional(list(string))
allowed_instance_types = optional(list(string))
bare_metal = optional(string)
baseline_ebs_bandwidth_mbps = optional(object({
max = optional(number)
min = optional(number)
}))
burstable_performance = optional(string)
cpu_manufacturers = optional(list(string))
excluded_instance_types = optional(list(string))
instance_generations = optional(list(string))
local_storage = optional(string)
local_storage_types = optional(list(string))
max_spot_price_as_percentage_of_optimal_on_demand_price = optional(number)
memory_gib_per_vcpu = optional(object({
max = optional(number)
min = optional(number)
}))
memory_mib = optional(object({
max = optional(number)
min = optional(number)
}))
network_bandwidth_gbps = optional(object({
max = optional(number)
min = optional(number)
}))
network_interface_count = optional(object({
max = optional(number)
min = optional(number)
}))
on_demand_max_price_percentage_over_lowest_price = optional(number)
require_hibernate_support = optional(bool)
spot_max_price_percentage_over_lowest_price = optional(number)
total_local_storage_gb = optional(object({
max = optional(number)
min = optional(number)
}))
vcpu_count = optional(object({
max = optional(number)
min = optional(number)
}))
}))
monitoring = optional(string)
network_configuration = optional(object({
security_groups = optional(list(string), [])
subnets = list(string)
}))
storage_configuration = optional(object({
storage_size_gib = number
}))
})
propagate_tags = optional(string, "CAPACITY_PROVIDER")
}))
name = optional(string) # Will fall back to use map key if not set
tags = optional(map(string), {})
}))
| `null` | no | | [cloudwatch\_log\_group\_class](#input\_cloudwatch\_log\_group\_class) | Specified the log class of the log group. Possible values are: `STANDARD` or `INFREQUENT_ACCESS` | `string` | `null` | 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\_name](#input\_cloudwatch\_log\_group\_name) | Custom name of CloudWatch Log Group for ECS cluster | `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\_capacity\_providers](#input\_cluster\_capacity\_providers) | List of capacity provider names to associate with the ECS cluster. Note: any capacity providers created by this module will be automatically added | `list(string)` | `[]` | no | | [cluster\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster |
object({
execute_command_configuration = optional(object({
kms_key_id = optional(string)
log_configuration = optional(object({
cloud_watch_encryption_enabled = optional(bool)
cloud_watch_log_group_name = optional(string)
s3_bucket_encryption_enabled = optional(bool)
s3_bucket_name = optional(string)
s3_kms_key_id = optional(string)
s3_key_prefix = optional(string)
}))
logging = optional(string, "OVERRIDE")
}))
managed_storage_configuration = optional(object({
fargate_ephemeral_storage_kms_key_id = optional(string)
kms_key_id = optional(string)
}))
})
|
{
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "placeholder"
}
}
}
| 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 |
object({
namespace = string
})
| `null` | no | @@ -190,11 +194,40 @@ No resources. | [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\_infrastructure\_iam\_role](#input\_create\_infrastructure\_iam\_role) | Determines whether the ECS infrastructure IAM role should be created | `bool` | `true` | no | +| [create\_node\_iam\_instance\_profile](#input\_create\_node\_iam\_instance\_profile) | Determines whether an IAM instance profile is created or to use an existing IAM instance profile | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `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\_strategy](#input\_default\_capacity\_provider\_strategy) | Map of default capacity provider strategy definitions to use for the cluster |
map(object({
base = optional(number)
name = optional(string) # Will fall back to use map key if not set
weight = optional(number)
}))
| `null` | no | +| [disable\_v7\_default\_name\_description](#input\_disable\_v7\_default\_name\_description) | [DEPRECATED - will be removed in v9.0] Determines whether to disable the default postfix added to resource names and descriptions added in v7.0 | `bool` | `false` | no | +| [infrastructure\_iam\_role\_description](#input\_infrastructure\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [infrastructure\_iam\_role\_name](#input\_infrastructure\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [infrastructure\_iam\_role\_override\_policy\_documents](#input\_infrastructure\_iam\_role\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [infrastructure\_iam\_role\_path](#input\_infrastructure\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [infrastructure\_iam\_role\_permissions\_boundary](#input\_infrastructure\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [infrastructure\_iam\_role\_source\_policy\_documents](#input\_infrastructure\_iam\_role\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [infrastructure\_iam\_role\_statements](#input\_infrastructure\_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 |
map(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string, "Allow")
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
variable = string
values = list(string)
})))
}))
| `null` | no | +| [infrastructure\_iam\_role\_tags](#input\_infrastructure\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [infrastructure\_iam\_role\_use\_name\_prefix](#input\_infrastructure\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | +| [node\_iam\_role\_additional\_policies](#input\_node\_iam\_role\_additional\_policies) | Additional policies to be added to the IAM role | `map(string)` | `{}` | no | +| [node\_iam\_role\_description](#input\_node\_iam\_role\_description) | Description of the role | `string` | `"ECS Managed Instances node IAM role"` | no | +| [node\_iam\_role\_name](#input\_node\_iam\_role\_name) | Name to use on IAM role/instance profile created | `string` | `null` | no | +| [node\_iam\_role\_override\_policy\_documents](#input\_node\_iam\_role\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [node\_iam\_role\_path](#input\_node\_iam\_role\_path) | IAM role/instance profile path | `string` | `null` | no | +| [node\_iam\_role\_permissions\_boundary](#input\_node\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [node\_iam\_role\_source\_policy\_documents](#input\_node\_iam\_role\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [node\_iam\_role\_statements](#input\_node\_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 |
map(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string, "Allow")
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
variable = string
values = list(string)
})))
}))
| `null` | no | +| [node\_iam\_role\_tags](#input\_node\_iam\_role\_tags) | A map of additional tags to add to the IAM role/instance profile created | `map(string)` | `{}` | no | +| [node\_iam\_role\_use\_name\_prefix](#input\_node\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role/instance profile name (`node_iam_role_name`) is used as a prefix | `bool` | `true` | no | | [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | -| [services](#input\_services) | Map of service definitions to create |
map(object({
create = optional(bool)
create_service = optional(bool)
tags = optional(map(string))

# Service
ignore_task_definition_changes = optional(bool)
alarms = optional(object({
alarm_names = list(string)
enable = optional(bool)
rollback = optional(bool)
}))
availability_zone_rebalancing = optional(string)
capacity_provider_strategy = optional(map(object({
base = optional(number)
capacity_provider = string
weight = optional(number)
})))
deployment_circuit_breaker = optional(object({
enable = bool
rollback = bool
}))
deployment_configuration = optional(object({
strategy = optional(string)
bake_time_in_minutes = optional(string)
canary_configuration = optional(object({
canary_bake_time_in_minutes = optional(string)
canary_percent = optional(string)
}))
linear_configuration = optional(object({
step_bake_time_in_minutes = optional(string)
step_percent = optional(string)
}))
lifecycle_hook = optional(map(object({
hook_target_arn = string
role_arn = string
lifecycle_stages = list(string)
hook_details = optional(string)
})))
}))
deployment_controller = optional(object({
type = optional(string)
}))
deployment_maximum_percent = optional(number, 200)
deployment_minimum_healthy_percent = optional(number, 66)
desired_count = optional(number, 1)
enable_ecs_managed_tags = optional(bool)
enable_execute_command = optional(bool)
force_delete = optional(bool)
force_new_deployment = optional(bool)
health_check_grace_period_seconds = optional(number)
launch_type = optional(string)
load_balancer = optional(map(object({
container_name = string
container_port = number
elb_name = optional(string)
target_group_arn = optional(string)
advanced_configuration = optional(object({
alternate_target_group_arn = string
production_listener_rule = string
role_arn = string
test_listener_rule = optional(string)
}))
})))
name = optional(string) # Will fall back to use map key if not set
assign_public_ip = optional(bool)
security_group_ids = optional(list(string))
subnet_ids = optional(list(string))
ordered_placement_strategy = optional(map(object({
field = optional(string)
type = string
})))
placement_constraints = optional(map(object({
expression = optional(string)
type = string
})))
platform_version = optional(string)
propagate_tags = optional(string)
scheduling_strategy = optional(string)
service_connect_configuration = optional(object({
enabled = optional(bool)
log_configuration = optional(object({
log_driver = string
options = optional(map(string))
secret_option = optional(list(object({
name = string
value_from = string
})))
}))
namespace = optional(string)
service = optional(list(object({
client_alias = optional(object({
dns_name = optional(string)
port = number
test_traffic_rules = optional(list(object({
header = optional(object({
name = string
value = object({
exact = string
})
}))
})))
}))
discovery_name = optional(string)
ingress_port_override = optional(number)
port_name = string
timeout = optional(object({
idle_timeout_seconds = optional(number)
per_request_timeout_seconds = optional(number)
}))
tls = optional(object({
issuer_cert_authority = object({
aws_pca_authority_arn = string
})
kms_key = optional(string)
role_arn = optional(string)
}))
})))
}))
service_registries = optional(object({
container_name = optional(string)
container_port = optional(number)
port = optional(number)
registry_arn = string
}))
sigint_rollback = optional(bool)
timeouts = optional(object({
create = optional(string)
update = optional(string)
delete = optional(string)
}))
triggers = optional(map(string))
volume_configuration = optional(object({
name = string
managed_ebs_volume = object({
encrypted = optional(bool)
file_system_type = optional(string)
iops = optional(number)
kms_key_id = optional(string)
size_in_gb = optional(number)
snapshot_id = optional(string)
tag_specifications = optional(list(object({
propagate_tags = optional(string)
resource_type = string
tags = optional(map(string))
})))
throughput = optional(number)
volume_type = optional(string)
})
}))
vpc_lattice_configurations = optional(object({
role_arn = string
target_group_arn = string
port_name = string
}))
wait_for_steady_state = optional(bool)
service_tags = optional(map(string))
# Service - IAM Role
create_iam_role = optional(bool)
iam_role_arn = optional(string)
iam_role_name = optional(string)
iam_role_use_name_prefix = optional(bool)
iam_role_path = optional(string)
iam_role_description = optional(string)
iam_role_permissions_boundary = optional(string)
iam_role_tags = optional(map(string))
iam_role_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))
# Task Definition
create_task_definition = optional(bool)
task_definition_arn = optional(string)
container_definitions = optional(map(object({
operating_system_family = optional(string)
tags = optional(map(string))

# Container definition
command = optional(list(string))
cpu = optional(number)
credentialSpecs = optional(list(string))
dependsOn = optional(list(object({
condition = string
containerName = string
})))
disableNetworking = optional(bool)
dnsSearchDomains = optional(list(string))
dnsServers = optional(list(string))
dockerLabels = optional(map(string))
dockerSecurityOptions = optional(list(string))
enable_execute_command = optional(bool)
entrypoint = optional(list(string))
environment = optional(list(object({
name = string
value = string
})))
environmentFiles = optional(list(object({
type = string
value = string
})))
essential = optional(bool)
extraHosts = optional(list(object({
hostname = string
ipAddress = string
})))
firelensConfiguration = optional(object({
options = optional(map(string))
type = optional(string)
}))
healthCheck = optional(object({
command = optional(list(string))
interval = optional(number)
retries = optional(number)
startPeriod = optional(number)
timeout = optional(number)
}))
hostname = optional(string)
image = optional(string)
interactive = optional(bool)
links = optional(list(string))
linuxParameters = optional(object({
capabilities = optional(object({
add = optional(list(string))
drop = optional(list(string))
}))
devices = optional(list(object({
containerPath = optional(string)
hostPath = optional(string)
permissions = optional(list(string))
})))
initProcessEnabled = optional(bool)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
}))
logConfiguration = optional(object({
logDriver = optional(string)
options = optional(map(string))
secretOptions = optional(list(object({
name = string
valueFrom = string
})))
}))
memory = optional(number)
memoryReservation = optional(number)
mountPoints = optional(list(object({
containerPath = optional(string)
readOnly = optional(bool)
sourceVolume = optional(string)
})), [])
name = optional(string)
portMappings = optional(list(object({
appProtocol = optional(string)
containerPort = optional(number)
containerPortRange = optional(string)
hostPort = optional(number)
name = optional(string)
protocol = optional(string)
})), [])
privileged = optional(bool)
pseudoTerminal = optional(bool)
readonlyRootFilesystem = optional(bool)
repositoryCredentials = optional(object({
credentialsParameter = optional(string)
}))
resourceRequirements = optional(list(object({
type = string
value = string
})))
restartPolicy = optional(object({
enabled = optional(bool)
ignoredExitCodes = optional(list(number))
restartAttemptPeriod = optional(number)
}))
secrets = optional(list(object({
name = string
valueFrom = string
})))
startTimeout = optional(number)
stopTimeout = optional(number)
systemControls = optional(list(object({
namespace = optional(string)
value = optional(string)
})))
ulimits = optional(list(object({
hardLimit = number
name = string
softLimit = number
})))
user = optional(string)
versionConsistency = optional(string)
volumesFrom = optional(list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
})))
workingDirectory = optional(string)

# Cloudwatch Log Group
service = optional(string, "")
enable_cloudwatch_logging = optional(bool)
create_cloudwatch_log_group = optional(bool)
cloudwatch_log_group_name = optional(string)
cloudwatch_log_group_use_name_prefix = optional(bool)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_retention_in_days = optional(number)
cloudwatch_log_group_kms_key_id = optional(string)
})))
cpu = optional(number, 1024)
enable_fault_injection = optional(bool)
ephemeral_storage = optional(object({
size_in_gib = number
}))
family = optional(string)
ipc_mode = optional(string)
memory = optional(number, 2048)
network_mode = optional(string)
pid_mode = optional(string)
proxy_configuration = optional(object({
container_name = string
properties = optional(map(string))
type = optional(string)
}))
requires_compatibilities = optional(list(string))
runtime_platform = optional(object({
cpu_architecture = optional(string)
operating_system_family = optional(string)
}))
skip_destroy = optional(bool)
task_definition_placement_constraints = optional(map(object({
expression = optional(string)
type = string
})))
track_latest = optional(bool)
volume = optional(map(object({
configure_at_launch = optional(bool)
docker_volume_configuration = optional(object({
autoprovision = optional(bool)
driver = optional(string)
driver_opts = optional(map(string))
labels = optional(map(string))
scope = optional(string)
}))
efs_volume_configuration = optional(object({
authorization_config = optional(object({
access_point_id = optional(string)
iam = optional(string)
}))
file_system_id = string
root_directory = optional(string)
transit_encryption = optional(string)
transit_encryption_port = optional(number)
}))
fsx_windows_file_server_volume_configuration = optional(object({
authorization_config = optional(object({
credentials_parameter = string
domain = string
}))
file_system_id = string
root_directory = string
}))
host_path = optional(string)
name = optional(string)
})))
task_tags = optional(map(string))
# Task Execution - IAM Role
create_task_exec_iam_role = optional(bool)
task_exec_iam_role_arn = optional(string)
task_exec_iam_role_name = optional(string)
task_exec_iam_role_use_name_prefix = optional(bool)
task_exec_iam_role_path = optional(string)
task_exec_iam_role_description = optional(string)
task_exec_iam_role_permissions_boundary = optional(string)
task_exec_iam_role_tags = optional(map(string))
task_exec_iam_role_policies = optional(map(string))
task_exec_iam_role_max_session_duration = optional(number)
create_task_exec_policy = optional(bool)
task_exec_ssm_param_arns = optional(list(string))
task_exec_secret_arns = optional(list(string))
task_exec_iam_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))
task_exec_iam_policy_path = optional(string)
# Tasks - IAM Role
create_tasks_iam_role = optional(bool)
tasks_iam_role_arn = optional(string)
tasks_iam_role_name = optional(string)
tasks_iam_role_use_name_prefix = optional(bool)
tasks_iam_role_path = optional(string)
tasks_iam_role_description = optional(string)
tasks_iam_role_permissions_boundary = optional(string)
tasks_iam_role_tags = optional(map(string))
tasks_iam_role_policies = optional(map(string))
tasks_iam_role_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))
# Task Set
external_id = optional(string)
scale = optional(object({
unit = optional(string)
value = optional(number)
}))
wait_until_stable = optional(bool)
wait_until_stable_timeout = optional(string)
# Autoscaling
enable_autoscaling = optional(bool)
autoscaling_min_capacity = optional(number)
autoscaling_max_capacity = optional(number)
autoscaling_policies = optional(map(object({
name = optional(string) # Will fall back to the key name if not provided
policy_type = optional(string)
predictive_scaling_policy_configuration = optional(object({
max_capacity_breach_behavior = optional(string)
max_capacity_buffer = optional(number)
metric_specification = list(object({
customized_capacity_metric_specification = optional(object({
metric_data_query = list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimension = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
namespace = optional(string)
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
}))
}))
customized_load_metric_specification = optional(object({
metric_data_query = list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimension = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
namespace = optional(string)
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
}))
}))
customized_scaling_metric_specification = optional(object({
metric_data_query = list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimension = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
namespace = optional(string)
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
}))
}))
predefined_load_metric_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
predefined_metric_pair_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
predefined_scaling_metric_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
target_value = number
}))
mode = optional(string)
scheduling_buffer_time = optional(number)
}))
step_scaling_policy_configuration = optional(object({
adjustment_type = optional(string)
cooldown = optional(number)
metric_aggregation_type = optional(string)
min_adjustment_magnitude = optional(number)
step_adjustment = optional(list(object({
metric_interval_lower_bound = optional(string)
metric_interval_upper_bound = optional(string)
scaling_adjustment = number
})))
}))
target_tracking_scaling_policy_configuration = optional(object({
customized_metric_specification = optional(object({
dimensions = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
metrics = optional(list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimensions = optional(list(object({
name = string
value = string
})))
metric_name = string
namespace = string
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
})))
namespace = optional(string)
statistic = optional(string)
unit = optional(string)
}))

disable_scale_in = optional(bool)
predefined_metric_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
scale_in_cooldown = optional(number)
scale_out_cooldown = optional(number)
target_value = optional(number)
}))
})))
autoscaling_scheduled_actions = optional(map(object({
name = optional(string)
min_capacity = number
max_capacity = number
schedule = string
start_time = optional(string)
end_time = optional(string)
timezone = optional(string)
})))
autoscaling_suspended_state = optional(object({
dynamic_scaling_in_suspended = optional(bool)
dynamic_scaling_out_suspended = optional(bool)
scheduled_scaling_suspended = optional(bool)
}))
# Security Group
create_security_group = optional(bool)
vpc_id = optional(string)
security_group_name = optional(string)
security_group_use_name_prefix = optional(bool)
security_group_description = optional(string)
security_group_ingress_rules = optional(map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string)
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string))
to_port = optional(string)
})))
security_group_egress_rules = optional(map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string)
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string))
to_port = optional(string)
})))
security_group_tags = optional(map(string))
# ECS Infrastructure IAM Role
create_infrastructure_iam_role = optional(bool)
infrastructure_iam_role_arn = optional(string)
infrastructure_iam_role_name = optional(string)
infrastructure_iam_role_use_name_prefix = optional(bool)
infrastructure_iam_role_path = optional(string)
infrastructure_iam_role_description = optional(string)
infrastructure_iam_role_permissions_boundary = optional(string)
infrastructure_iam_role_tags = optional(map(string))
}))
| `null` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | +| [security\_group\_egress\_rules](#input\_security\_group\_egress\_rules) | Security group egress rules to add to the security group created |
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
}))
|
{
"all_ipv4": {
"cidr_ipv4": "0.0.0.0/0",
"description": "Allow all IPv4 traffic",
"ip_protocol": "-1"
},
"all_ipv6": {
"cidr_ipv6": "::/0",
"description": "Allow all IPv6 traffic",
"ip_protocol": "-1"
}
}
| no | +| [security\_group\_ingress\_rules](#input\_security\_group\_ingress\_rules) | Security group ingress rules to add to the security group created |
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
}))
| `{}` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | 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 | +| [services](#input\_services) | Map of service definitions to create |
map(object({
create = optional(bool)
create_service = optional(bool)
tags = optional(map(string))

# Service
ignore_task_definition_changes = optional(bool)
alarms = optional(object({
alarm_names = list(string)
enable = optional(bool)
rollback = optional(bool)
}))
availability_zone_rebalancing = optional(string)
capacity_provider_strategy = optional(map(object({
base = optional(number)
capacity_provider = string
weight = optional(number)
})))
deployment_circuit_breaker = optional(object({
enable = bool
rollback = bool
}))
deployment_configuration = optional(object({
strategy = optional(string)
bake_time_in_minutes = optional(string)
canary_configuration = optional(object({
canary_bake_time_in_minutes = optional(string)
canary_percent = optional(string)
}))
linear_configuration = optional(object({
step_bake_time_in_minutes = optional(string)
step_percent = optional(string)
}))
lifecycle_hook = optional(map(object({
hook_target_arn = string
role_arn = optional(string)
lifecycle_stages = list(string)
hook_details = optional(string)
})))
}))
deployment_controller = optional(object({
type = optional(string)
}))
deployment_maximum_percent = optional(number, 200)
deployment_minimum_healthy_percent = optional(number, 66)
desired_count = optional(number, 1)
enable_ecs_managed_tags = optional(bool)
enable_execute_command = optional(bool)
force_delete = optional(bool)
force_new_deployment = optional(bool)
health_check_grace_period_seconds = optional(number)
launch_type = optional(string)
load_balancer = optional(map(object({
container_name = string
container_port = number
elb_name = optional(string)
target_group_arn = optional(string)
advanced_configuration = optional(object({
alternate_target_group_arn = string
production_listener_rule = string # Should be optional but bug in provider
role_arn = optional(string)
test_listener_rule = optional(string)
}))
})))
name = optional(string) # Will fall back to use map key if not set
assign_public_ip = optional(bool)
security_group_ids = optional(list(string))
subnet_ids = optional(list(string))
ordered_placement_strategy = optional(list(object({
field = optional(string)
type = string
})))
placement_constraints = optional(map(object({
expression = optional(string)
type = string
})))
platform_version = optional(string)
propagate_tags = optional(string)
scheduling_strategy = optional(string)
service_connect_configuration = optional(object({
enabled = optional(bool)
log_configuration = optional(object({
log_driver = string
options = optional(map(string))
secret_option = optional(list(object({
name = string
value_from = string
})))
}))
namespace = optional(string)
service = optional(list(object({
client_alias = optional(object({
dns_name = optional(string)
port = number
test_traffic_rules = optional(list(object({
header = optional(object({
name = string
value = object({
exact = string
})
}))
})))
}))
discovery_name = optional(string)
ingress_port_override = optional(number)
port_name = string
timeout = optional(object({
idle_timeout_seconds = optional(number)
per_request_timeout_seconds = optional(number)
}))
tls = optional(object({
issuer_cert_authority = object({
aws_pca_authority_arn = string
})
kms_key = optional(string)
role_arn = optional(string)
}))
})))
}))
service_registries = optional(object({
container_name = optional(string)
container_port = optional(number)
port = optional(number)
registry_arn = string
}))
sigint_rollback = optional(bool)
timeouts = optional(object({
create = optional(string)
update = optional(string)
delete = optional(string)
}))
triggers = optional(map(string))
volume_configuration = optional(object({
name = string
managed_ebs_volume = object({
encrypted = optional(bool)
file_system_type = optional(string)
iops = optional(number)
kms_key_id = optional(string)
size_in_gb = optional(number)
snapshot_id = optional(string)
tag_specifications = optional(list(object({
propagate_tags = optional(string)
resource_type = string
tags = optional(map(string))
})))
throughput = optional(number)
volume_type = optional(string)
})
}))
vpc_lattice_configurations = optional(object({
role_arn = string
target_group_arn = string
port_name = string
}))
wait_for_steady_state = optional(bool)
service_tags = optional(map(string))
# Service - IAM Role
create_iam_role = optional(bool)
iam_role_arn = optional(string)
iam_role_name = optional(string)
iam_role_use_name_prefix = optional(bool)
iam_role_path = optional(string)
iam_role_description = optional(string)
iam_role_permissions_boundary = optional(string)
iam_role_tags = optional(map(string))
iam_role_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))
# Task Definition
create_task_definition = optional(bool)
task_definition_arn = optional(string)
container_definitions = optional(map(object({
operating_system_family = optional(string)
tags = optional(map(string))

# Container definition
command = optional(list(string))
cpu = optional(number)
credentialSpecs = optional(list(string))
dependsOn = optional(list(object({
condition = string
containerName = string
})))
disableNetworking = optional(bool)
dnsSearchDomains = optional(list(string))
dnsServers = optional(list(string))
dockerLabels = optional(map(string))
dockerSecurityOptions = optional(list(string))
enable_execute_command = optional(bool)
entrypoint = optional(list(string))
environment = optional(list(object({
name = string
value = string
})))
environmentFiles = optional(list(object({
type = string
value = string
})))
essential = optional(bool)
extraHosts = optional(list(object({
hostname = string
ipAddress = string
})))
firelensConfiguration = optional(object({
options = optional(map(string))
type = optional(string)
}))
healthCheck = optional(object({
command = optional(list(string))
interval = optional(number)
retries = optional(number)
startPeriod = optional(number)
timeout = optional(number)
}))
hostname = optional(string)
image = optional(string)
interactive = optional(bool)
links = optional(list(string))
linuxParameters = optional(object({
capabilities = optional(object({
add = optional(list(string))
drop = optional(list(string))
}))
devices = optional(list(object({
containerPath = optional(string)
hostPath = optional(string)
permissions = optional(list(string))
})))
initProcessEnabled = optional(bool)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
}))
logConfiguration = optional(object({
logDriver = optional(string)
options = optional(map(string))
secretOptions = optional(list(object({
name = string
valueFrom = string
})))
}))
memory = optional(number)
memoryReservation = optional(number)
mountPoints = optional(list(object({
containerPath = optional(string)
readOnly = optional(bool)
sourceVolume = optional(string)
})), [])
name = optional(string)
portMappings = optional(list(object({
appProtocol = optional(string)
containerPort = optional(number)
containerPortRange = optional(string)
hostPort = optional(number)
name = optional(string)
protocol = optional(string)
})), [])
privileged = optional(bool)
pseudoTerminal = optional(bool)
readonlyRootFilesystem = optional(bool)
repositoryCredentials = optional(object({
credentialsParameter = optional(string)
}))
resourceRequirements = optional(list(object({
type = string
value = string
})))
restartPolicy = optional(object({
enabled = optional(bool)
ignoredExitCodes = optional(list(number))
restartAttemptPeriod = optional(number)
}))
secrets = optional(list(object({
name = string
valueFrom = string
})))
startTimeout = optional(number)
stopTimeout = optional(number)
systemControls = optional(list(object({
namespace = optional(string)
value = optional(string)
})))
ulimits = optional(list(object({
hardLimit = number
name = string
softLimit = number
})))
user = optional(string)
versionConsistency = optional(string)
volumesFrom = optional(list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
})))
workingDirectory = optional(string)

# Cloudwatch Log Group
service = optional(string, "")
enable_cloudwatch_logging = optional(bool)
create_cloudwatch_log_group = optional(bool)
cloudwatch_log_group_name = optional(string)
cloudwatch_log_group_use_name_prefix = optional(bool)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_retention_in_days = optional(number)
cloudwatch_log_group_kms_key_id = optional(string)
})))
cpu = optional(number, 1024)
enable_fault_injection = optional(bool)
ephemeral_storage = optional(object({
size_in_gib = number
}))
family = optional(string)
ipc_mode = optional(string)
memory = optional(number, 2048)
network_mode = optional(string)
pid_mode = optional(string)
proxy_configuration = optional(object({
container_name = string
properties = optional(map(string))
type = optional(string)
}))
requires_compatibilities = optional(list(string))
runtime_platform = optional(object({
cpu_architecture = optional(string)
operating_system_family = optional(string)
}))
skip_destroy = optional(bool)
task_definition_placement_constraints = optional(map(object({
expression = optional(string)
type = string
})))
track_latest = optional(bool)
volume = optional(map(object({
configure_at_launch = optional(bool)
docker_volume_configuration = optional(object({
autoprovision = optional(bool)
driver = optional(string)
driver_opts = optional(map(string))
labels = optional(map(string))
scope = optional(string)
}))
efs_volume_configuration = optional(object({
authorization_config = optional(object({
access_point_id = optional(string)
iam = optional(string)
}))
file_system_id = string
root_directory = optional(string)
transit_encryption = optional(string)
transit_encryption_port = optional(number)
}))
fsx_windows_file_server_volume_configuration = optional(object({
authorization_config = optional(object({
credentials_parameter = string
domain = string
}))
file_system_id = string
root_directory = string
}))
host_path = optional(string)
name = optional(string)
})))
task_tags = optional(map(string))
# Task Execution - IAM Role
create_task_exec_iam_role = optional(bool)
task_exec_iam_role_arn = optional(string)
task_exec_iam_role_name = optional(string)
task_exec_iam_role_use_name_prefix = optional(bool)
task_exec_iam_role_path = optional(string)
task_exec_iam_role_description = optional(string)
task_exec_iam_role_permissions_boundary = optional(string)
task_exec_iam_role_tags = optional(map(string))
task_exec_iam_role_policies = optional(map(string))
task_exec_iam_role_max_session_duration = optional(number)
create_task_exec_policy = optional(bool)
task_exec_ssm_param_arns = optional(list(string))
task_exec_secret_arns = optional(list(string))
task_exec_iam_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))
task_exec_iam_policy_path = optional(string)
# Tasks - IAM Role
create_tasks_iam_role = optional(bool)
tasks_iam_role_arn = optional(string)
tasks_iam_role_name = optional(string)
tasks_iam_role_use_name_prefix = optional(bool)
tasks_iam_role_path = optional(string)
tasks_iam_role_description = optional(string)
tasks_iam_role_permissions_boundary = optional(string)
tasks_iam_role_tags = optional(map(string))
tasks_iam_role_policies = optional(map(string))
tasks_iam_role_statements = optional(list(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string)
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
values = list(string)
variable = string
})))
})))
# Task Set
external_id = optional(string)
scale = optional(object({
unit = optional(string)
value = optional(number)
}))
wait_until_stable = optional(bool)
wait_until_stable_timeout = optional(string)
# Autoscaling
enable_autoscaling = optional(bool)
autoscaling_min_capacity = optional(number)
autoscaling_max_capacity = optional(number)
autoscaling_policies = optional(map(object({
name = optional(string) # Will fall back to the key name if not provided
policy_type = optional(string)
predictive_scaling_policy_configuration = optional(object({
max_capacity_breach_behavior = optional(string)
max_capacity_buffer = optional(number)
metric_specification = list(object({
customized_capacity_metric_specification = optional(object({
metric_data_query = list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimension = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
namespace = optional(string)
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
}))
}))
customized_load_metric_specification = optional(object({
metric_data_query = list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimension = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
namespace = optional(string)
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
}))
}))
customized_scaling_metric_specification = optional(object({
metric_data_query = list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimension = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
namespace = optional(string)
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
}))
}))
predefined_load_metric_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
predefined_metric_pair_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
predefined_scaling_metric_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
target_value = number
}))
mode = optional(string)
scheduling_buffer_time = optional(number)
}))
step_scaling_policy_configuration = optional(object({
adjustment_type = optional(string)
cooldown = optional(number)
metric_aggregation_type = optional(string)
min_adjustment_magnitude = optional(number)
step_adjustment = optional(list(object({
metric_interval_lower_bound = optional(string)
metric_interval_upper_bound = optional(string)
scaling_adjustment = number
})))
}))
target_tracking_scaling_policy_configuration = optional(object({
customized_metric_specification = optional(object({
dimensions = optional(list(object({
name = string
value = string
})))
metric_name = optional(string)
metrics = optional(list(object({
expression = optional(string)
id = string
label = optional(string)
metric_stat = optional(object({
metric = object({
dimensions = optional(list(object({
name = string
value = string
})))
metric_name = string
namespace = string
})
stat = string
unit = optional(string)
}))
return_data = optional(bool)
})))
namespace = optional(string)
statistic = optional(string)
unit = optional(string)
}))

disable_scale_in = optional(bool)
predefined_metric_specification = optional(object({
predefined_metric_type = string
resource_label = optional(string)
}))
scale_in_cooldown = optional(number)
scale_out_cooldown = optional(number)
target_value = optional(number)
}))
})))
autoscaling_scheduled_actions = optional(map(object({
name = optional(string)
min_capacity = number
max_capacity = number
schedule = string
start_time = optional(string)
end_time = optional(string)
timezone = optional(string)
})))
autoscaling_suspended_state = optional(object({
dynamic_scaling_in_suspended = optional(bool)
dynamic_scaling_out_suspended = optional(bool)
scheduled_scaling_suspended = optional(bool)
}))
# Security Group
create_security_group = optional(bool)
vpc_id = optional(string)
security_group_name = optional(string)
security_group_use_name_prefix = optional(bool)
security_group_description = optional(string)
security_group_ingress_rules = optional(map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string)
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string))
to_port = optional(string)
})))
security_group_egress_rules = optional(map(object({
cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string)
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string))
to_port = optional(string)
})))
security_group_tags = optional(map(string))
# ECS Infrastructure IAM Role
create_infrastructure_iam_role = optional(bool)
infrastructure_iam_role_arn = optional(string)
infrastructure_iam_role_name = optional(string)
infrastructure_iam_role_use_name_prefix = optional(bool)
infrastructure_iam_role_path = optional(string)
infrastructure_iam_role_description = optional(string)
infrastructure_iam_role_permissions_boundary = optional(string)
infrastructure_iam_role_tags = optional(map(string))
}))
| `null` | 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 | @@ -206,18 +239,28 @@ No resources. | [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 |
map(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string, "Allow")
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
variable = string
values = list(string)
})))
}))
| `null` | 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)` | `[]` | 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)` | `[]` | no | +| [vpc\_id](#input\_vpc\_id) | The ID of the VPC where the security group will be created | `string` | `null` | no | ## Outputs | Name | Description | |------|-------------| -| [autoscaling\_capacity\_providers](#output\_autoscaling\_capacity\_providers) | Map of autoscaling capacity providers created and their attributes | +| [capacity\_providers](#output\_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 | +| [infrastructure\_iam\_role\_arn](#output\_infrastructure\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [infrastructure\_iam\_role\_name](#output\_infrastructure\_iam\_role\_name) | IAM role name | +| [infrastructure\_iam\_role\_unique\_id](#output\_infrastructure\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [node\_iam\_instance\_profile\_arn](#output\_node\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [node\_iam\_instance\_profile\_id](#output\_node\_iam\_instance\_profile\_id) | Instance profile's ID | +| [node\_iam\_instance\_profile\_unique](#output\_node\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [node\_iam\_role\_arn](#output\_node\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [node\_iam\_role\_name](#output\_node\_iam\_role\_name) | IAM role name | +| [node\_iam\_role\_unique\_id](#output\_node\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | | [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 | diff --git a/docs/UPGRADE-7.0.md b/docs/UPGRADE-7.0.md new file mode 100644 index 00000000..57be806a --- /dev/null +++ b/docs/UPGRADE-7.0.md @@ -0,0 +1,211 @@ +# Upgrade from v6.x to v7.x + +If you have any questions regarding this upgrade process, please consult the [`examples`](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples) directory: +If you find a bug, please open an issue with supporting configuration to reproduce. + +## List of backwards incompatible changes + +- Previously the module would infer the capacity providers to use based on those specified in the `default_capacity_provider_strategy` variable as well as any specified in the `autoscaling_capacity_providers` variable. As of v7.0.0, the module will no longer infer the capacity providers that should be associated with the cluster. Instead, users must explicitly specify the desired capacity providers using the new `cluster_capacity_providers` variable. The only inference of capacity providers are those created by the module itself when using the `capacity_providers` variable. Essentially, if you are using `FARGATE`, `FARGATE_SPOT`, or an externally created capacity provider, you must now specify those in the `cluster_capacity_providers` variable. +- With the addition of ECS managed instances support, the prior variable `autoscaling_capacity_providers` has been replaced with the more generic `capacity_providers` variable. If you were previously using `autoscaling_capacity_providers`, you will need to migrate to the new `capacity_providers` variable by simply renaming it and nesting each ASG capacity provider definition under the argument `auto_scaling_group_provider`. See the before vs after section below for an example of this change. Note: your existing ASG capacity providers will continue to work as before, this is simply a variable rename and variable definition modification. No resources will be replaced/destroyed as part of this change. +- The ECS service variable `ordered_placement_strategy` type definition has been changed from `map(object({...}))` to `list(object({...}))`. The argument needs to preserve order so a list is necessary. + +## Additional changes + +### Added + +- Default name postfixes for IAM roles and security groups have been added, along with default descriptions. When using the intended behavior of simply setting a `var.name` value and relying on the module, these new defaults help to distinguish resources created by the module. Instead of seeing 4 IAM roles named `"example-"`, you will now see names like `"example-service-"`, `"example-task-exec-"`, `"example-tasks-"`, and `"example-infra-"`. To aid in the migration, a variable `disable_v7_default_name_description` has been added that allow users to opt out of theses default settings for existing resources (avoid re-creating them). This ensures an easier upgrade path while also letting new resources benefit from the improved naming and descriptions. Note: this variable and therefore its behavior will be removed in version `v9.0` of the module, giving users time to remediate. +- Support for ECS managed instances has been added. Users can now create an ECS cluster that use EC2 instances created and managed by ECS managed instances capacity provider. Support for this includes the necessary IAM roles as well as a security group that is utilized by the managed instances. + +### Modified + +- The ECS service infrastructure IAM role is now associated with the `lifecycle_hook` and `advanced_configuration` arguments as part of the progressive deployment options. Users can still provide their own role, but the default now matches the rest of the module where the infrastructure IAM role created by the module will be used unless a different IAM role is provided. + +### Variable and output changes + +> [!NOTE] +> The variables and outputs added for ECS managed instance support has not been added to this list. Those details are not relevant to the upgrade process. See the [pull request](https://github.com/terraform-aws-modules/terraform-aws-ecs/pull/364) for more details on what has been added for ECS managed instances support (or consult the documentation/examples within the repository). + +1. Removed variables: + + - None + +2. Renamed variables: + + - `autoscaling_capacity_providers` -> `capacity_providers` + +3. Added variables: + + - `cluster_capacity_providers` + - `disable_v7_default_name_description` + +4. Removed outputs: + + - None + +5. Renamed outputs: + + - `autoscaling_capacity_providers` -> `capacity_providers` + +6. Added outputs: + + - None + +## Upgrade Migrations + +### Before 6.x Example + +#### Root Module + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + version = "~> 6.0" + + # Truncated for brevity ... + + default_capacity_provider_strategy = { + FARGATE = { + weight = 50 + base = 20 + } + FARGATE_SPOT = { + weight = 50 + } + } + + autoscaling_capacity_providers = { + ASG = { + auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + } + } +} +``` + +#### Cluster Sub-Module + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "~> 6.0" + + # Truncated for brevity ... + + default_capacity_provider_strategy = { + FARGATE = { + weight = 50 + base = 20 + } + FARGATE_SPOT = { + weight = 50 + } + } + + autoscaling_capacity_providers = { + ASG = { + auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + } + } +} +``` + +### After 7.x Example + +#### Root Module + +```hcl +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + version = "~> 7.0" + + # Truncated for brevity ... + + cluster_capacity_providers = ["FARGATE", "FARGATE_SPOT"] # <=== add + default_capacity_provider_strategy = { + FARGATE = { + weight = 50 + base = 20 + } + FARGATE_SPOT = { + weight = 50 + } + } + + capacity_providers = { # <=== change name + ASG = { + auto_scaling_group_provider = { # <=== add + auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + } # <=== add + } + } +} +``` + +#### Cluster Sub-Module + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "~> 7.0" + + # Truncated for brevity ... + + cluster_capacity_providers = ["FARGATE", "FARGATE_SPOT"] # <=== add + default_capacity_provider_strategy = { + FARGATE = { + weight = 50 + base = 20 + } + FARGATE_SPOT = { + weight = 50 + } + } + + capacity_providers = { # <=== change name + ASG = { + auto_scaling_group_provider = { # <=== add + auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } + } # <=== add + } + } +} +``` + +### State Changes + +None required. diff --git a/examples/complete/README.md b/examples/complete/README.md index 1d96ae73..9db4a04f 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -14,9 +14,9 @@ Configuration in this directory creates: To run this example you need to execute: ```bash -$ terraform init -$ terraform plan -$ terraform apply +terraform init +terraform plan +terraform apply ``` Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. @@ -27,13 +27,13 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 6.21 | +| [aws](#provider\_aws) | >= 6.23 | ## Modules @@ -66,12 +66,26 @@ No inputs. | Name | Description | |------|-------------| | [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the load balancer | +| [capacity\_providers](#output\_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\_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 | +| [infrastructure\_iam\_role\_arn](#output\_infrastructure\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [infrastructure\_iam\_role\_name](#output\_infrastructure\_iam\_role\_name) | IAM role name | +| [infrastructure\_iam\_role\_unique\_id](#output\_infrastructure\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [node\_iam\_instance\_profile\_arn](#output\_node\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [node\_iam\_instance\_profile\_id](#output\_node\_iam\_instance\_profile\_id) | Instance profile's ID | +| [node\_iam\_instance\_profile\_unique](#output\_node\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [node\_iam\_role\_arn](#output\_node\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [node\_iam\_role\_name](#output\_node\_iam\_role\_name) | IAM role name | +| [node\_iam\_role\_unique\_id](#output\_node\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | | [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 | ## License diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 94a2a764..73858a6b 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -37,6 +37,7 @@ module "ecs" { cluster_name = local.name # Cluster capacity providers + cluster_capacity_providers = ["FARGATE", "FARGATE_SPOT"] default_capacity_provider_strategy = { FARGATE = { weight = 50 @@ -47,17 +48,19 @@ module "ecs" { } } - autoscaling_capacity_providers = { + capacity_providers = { ASG = { - auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn - managed_draining = "ENABLED" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 5 - minimum_scaling_step_size = 1 - status = "ENABLED" - target_capacity = 60 + auto_scaling_group_provider = { + auto_scaling_group_arn = module.autoscaling.autoscaling_group_arn + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } } } } @@ -220,9 +223,14 @@ module "ecs" { load_balancer = { service = { - target_group_arn = module.alb.target_groups["ex_ecs"].arn + target_group_arn = module.alb.target_groups["ex-ecs"].arn container_name = local.container_name container_port = local.container_port + + advanced_configuration = { + alternate_target_group_arn = module.alb.target_groups["ex-ecs-alt"].arn + production_listener_rule = module.alb.listener_rules["ex-http/ex-forward"].arn + } } } @@ -328,18 +336,57 @@ module "alb" { } listeners = { - ex_http = { + ex-http = { port = 80 protocol = "HTTP" forward = { - target_group_key = "ex_ecs" + target_group_key = "ex-ecs" + } + + rules = { + ex-forward = { + priority = 100 + actions = [{ + forward = { + target_group_key = "ex-ecs" + } + }] + conditions = [{ + path_pattern = { + values = ["/"] + } + }] + } } } } target_groups = { - ex_ecs = { + ex-ecs = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + ex-ecs-alt = { backend_protocol = "HTTP" backend_port = local.container_port target_type = "ip" diff --git a/examples/complete/outputs.tf b/examples/complete/outputs.tf index dd0cdd27..e261408b 100644 --- a/examples/complete/outputs.tf +++ b/examples/complete/outputs.tf @@ -17,14 +17,84 @@ output "cluster_name" { value = module.ecs.cluster_name } +output "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = module.ecs.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = module.ecs.cloudwatch_log_group_arn +} + 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 +output "capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = module.ecs.capacity_providers +} + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs.task_exec_iam_role_name +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs.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.ecs.task_exec_iam_role_unique_id +} + +output "infrastructure_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs.infrastructure_iam_role_arn +} + +output "infrastructure_iam_role_name" { + description = "IAM role name" + value = module.ecs.infrastructure_iam_role_name +} + +output "infrastructure_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs.infrastructure_iam_role_unique_id +} + +output "node_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs.node_iam_role_arn +} + +output "node_iam_role_name" { + description = "IAM role name" + value = module.ecs.node_iam_role_name +} + +output "node_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs.node_iam_role_unique_id +} + +output "node_iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = module.ecs.node_iam_instance_profile_arn +} + +output "node_iam_instance_profile_id" { + description = "Instance profile's ID" + value = module.ecs.node_iam_instance_profile_id +} + +output "node_iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = module.ecs.node_iam_instance_profile_unique } ################################################################################ diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 70f23a44..7e5b918f 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/examples/container-definition/README.md b/examples/container-definition/README.md index bfa6280d..21380ee9 100644 --- a/examples/container-definition/README.md +++ b/examples/container-definition/README.md @@ -9,9 +9,9 @@ Configuration in this directory creates: To run this example you need to execute: ```bash -$ terraform init -$ terraform plan -$ terraform apply +terraform init +terraform plan +terraform apply ``` Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. @@ -22,7 +22,7 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | | [null](#requirement\_null) | >= 3.2 | ## Providers diff --git a/examples/container-definition/versions.tf b/examples/container-definition/versions.tf index 42207f11..ff39d329 100644 --- a/examples/container-definition/versions.tf +++ b/examples/container-definition/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } null = { source = "hashicorp/null" diff --git a/examples/ec2-autoscaling/README.md b/examples/ec2-autoscaling/README.md index a440d1b7..e570f615 100644 --- a/examples/ec2-autoscaling/README.md +++ b/examples/ec2-autoscaling/README.md @@ -14,9 +14,9 @@ Configuration in this directory creates: To run this example you need to execute: ```bash -$ terraform init -$ terraform plan -$ terraform apply +terraform init +terraform plan +terraform apply ``` Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. @@ -27,13 +27,13 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 6.21 | +| [aws](#provider\_aws) | >= 6.23 | ## Modules @@ -62,11 +62,22 @@ No inputs. | Name | Description | |------|-------------| | [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the load balancer | +| [capacity\_providers](#output\_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\_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 | +| [infrastructure\_iam\_role\_arn](#output\_infrastructure\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [infrastructure\_iam\_role\_name](#output\_infrastructure\_iam\_role\_name) | IAM role name | +| [infrastructure\_iam\_role\_unique\_id](#output\_infrastructure\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [node\_iam\_instance\_profile\_arn](#output\_node\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [node\_iam\_instance\_profile\_id](#output\_node\_iam\_instance\_profile\_id) | Instance profile's ID | +| [node\_iam\_instance\_profile\_unique](#output\_node\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [node\_iam\_role\_arn](#output\_node\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [node\_iam\_role\_name](#output\_node\_iam\_role\_name) | IAM role name | +| [node\_iam\_role\_unique\_id](#output\_node\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | | [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 | @@ -89,6 +100,9 @@ No inputs. | [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 | +| [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 diff --git a/examples/ec2-autoscaling/main.tf b/examples/ec2-autoscaling/main.tf index 454b6523..5b978ed8 100644 --- a/examples/ec2-autoscaling/main.tf +++ b/examples/ec2-autoscaling/main.tf @@ -38,40 +38,44 @@ module "ecs_cluster" { # Cluster capacity providers default_capacity_provider_strategy = { - ex_1 = { + ex-1 = { weight = 60 base = 20 } - ex_2 = { + ex-2 = { weight = 40 } } - autoscaling_capacity_providers = { + capacity_providers = { # On-demand instances - ex_1 = { - auto_scaling_group_arn = module.autoscaling["ex_1"].autoscaling_group_arn - managed_draining = "ENABLED" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 5 - minimum_scaling_step_size = 1 - status = "ENABLED" - target_capacity = 60 + ex-1 = { + auto_scaling_group_provider = { + auto_scaling_group_arn = module.autoscaling["ex-1"].autoscaling_group_arn + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } } } # Spot instances - ex_2 = { - auto_scaling_group_arn = module.autoscaling["ex_2"].autoscaling_group_arn - managed_draining = "ENABLED" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 15 - minimum_scaling_step_size = 5 - status = "ENABLED" - target_capacity = 90 + ex-2 = { + auto_scaling_group_provider = { + auto_scaling_group_arn = module.autoscaling["ex-2"].autoscaling_group_arn + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } } } } @@ -119,8 +123,8 @@ module "ecs_service" { requires_compatibilities = ["EC2"] capacity_provider_strategy = { # On-demand instances - ex_1 = { - capacity_provider = module.ecs_cluster.autoscaling_capacity_providers["ex_1"].name + ex-1 = { + capacity_provider = module.ecs_cluster.capacity_providers["ex-1"].name weight = 1 base = 1 } @@ -186,9 +190,14 @@ module "ecs_service" { load_balancer = { service = { - target_group_arn = module.alb.target_groups["ex_ecs"].arn + target_group_arn = module.alb.target_groups["ex-ecs"].arn container_name = local.container_name container_port = local.container_port + + advanced_configuration = { + alternate_target_group_arn = module.alb.target_groups["ex-ecs-alt"].arn + production_listener_rule = module.alb.listener_rules["ex-http/ex-forward"].arn + } } } @@ -244,18 +253,57 @@ module "alb" { } listeners = { - ex_http = { + ex-http = { port = 80 protocol = "HTTP" forward = { - target_group_key = "ex_ecs" + target_group_key = "ex-ecs" + } + + rules = { + ex-forward = { + priority = 100 + actions = [{ + forward = { + target_group_key = "ex-ecs" + } + }] + conditions = [{ + path_pattern = { + values = ["/"] + } + }] + } } } } target_groups = { - ex_ecs = { + ex-ecs = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + ex-ecs-alt = { backend_protocol = "HTTP" backend_port = local.container_port target_type = "ip" @@ -289,7 +337,7 @@ module "autoscaling" { for_each = { # On-demand instances - ex_1 = { + ex-1 = { instance_type = "t3.large" use_mixed_instances_policy = false mixed_instances_policy = null @@ -305,7 +353,7 @@ module "autoscaling" { EOT } # Spot instances - ex_2 = { + ex-2 = { instance_type = "t3.medium" use_mixed_instances_policy = true mixed_instances_policy = { diff --git a/examples/ec2-autoscaling/outputs.tf b/examples/ec2-autoscaling/outputs.tf index 3ad62947..9179fd9a 100644 --- a/examples/ec2-autoscaling/outputs.tf +++ b/examples/ec2-autoscaling/outputs.tf @@ -17,14 +17,84 @@ output "cluster_name" { value = module.ecs_cluster.name } +output "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = module.ecs_cluster.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = module.ecs_cluster.cloudwatch_log_group_arn +} + 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 +output "capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = module.ecs_cluster.capacity_providers +} + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_cluster.task_exec_iam_role_name +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_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.ecs_cluster.task_exec_iam_role_unique_id +} + +output "infrastructure_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs_cluster.infrastructure_iam_role_arn +} + +output "infrastructure_iam_role_name" { + description = "IAM role name" + value = module.ecs_cluster.infrastructure_iam_role_name +} + +output "infrastructure_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs_cluster.infrastructure_iam_role_unique_id +} + +output "node_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs_cluster.node_iam_role_arn +} + +output "node_iam_role_name" { + description = "IAM role name" + value = module.ecs_cluster.node_iam_role_name +} + +output "node_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs_cluster.node_iam_role_unique_id +} + +output "node_iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = module.ecs_cluster.node_iam_instance_profile_arn +} + +output "node_iam_instance_profile_id" { + description = "Instance profile's ID" + value = module.ecs_cluster.node_iam_instance_profile_id +} + +output "node_iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = module.ecs_cluster.node_iam_instance_profile_unique } ################################################################################ diff --git a/examples/ec2-autoscaling/versions.tf b/examples/ec2-autoscaling/versions.tf index 70f23a44..7e5b918f 100644 --- a/examples/ec2-autoscaling/versions.tf +++ b/examples/ec2-autoscaling/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/examples/fargate/README.md b/examples/fargate/README.md index e0cb98b0..e06e1da7 100644 --- a/examples/fargate/README.md +++ b/examples/fargate/README.md @@ -14,9 +14,9 @@ Configuration in this directory creates: To run this example you need to execute: ```bash -$ terraform init -$ terraform plan -$ terraform apply +terraform init +terraform plan +terraform apply ``` Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. @@ -27,13 +27,13 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 6.21 | +| [aws](#provider\_aws) | >= 6.23 | ## Modules @@ -49,9 +49,6 @@ Note that this example may create resources which will incur monetary charges on | Name | Type | |------|------| -| [aws_iam_role.ecs_elb_permissions](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy_attachment.ecs_elb_management_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | -| [aws_iam_role_policy_attachment.ecs_service_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | 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 | | [aws_ssm_parameter.fluentbit](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | @@ -65,11 +62,22 @@ No inputs. | Name | Description | |------|-------------| | [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the load balancer | +| [capacity\_providers](#output\_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\_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 | +| [infrastructure\_iam\_role\_arn](#output\_infrastructure\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [infrastructure\_iam\_role\_name](#output\_infrastructure\_iam\_role\_name) | IAM role name | +| [infrastructure\_iam\_role\_unique\_id](#output\_infrastructure\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [node\_iam\_instance\_profile\_arn](#output\_node\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [node\_iam\_instance\_profile\_id](#output\_node\_iam\_instance\_profile\_id) | Instance profile's ID | +| [node\_iam\_instance\_profile\_unique](#output\_node\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [node\_iam\_role\_arn](#output\_node\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [node\_iam\_role\_name](#output\_node\_iam\_role\_name) | IAM role name | +| [node\_iam\_role\_unique\_id](#output\_node\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | | [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 | @@ -94,6 +102,9 @@ No inputs. | [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 | | [task\_definition\_run\_task\_command](#output\_task\_definition\_run\_task\_command) | awscli command to run the standalone task | +| [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 diff --git a/examples/fargate/main.tf b/examples/fargate/main.tf index adabb628..386c529c 100644 --- a/examples/fargate/main.tf +++ b/examples/fargate/main.tf @@ -37,6 +37,7 @@ module "ecs_cluster" { name = local.name # Capacity provider + cluster_capacity_providers = ["FARGATE", "FARGATE_SPOT"] default_capacity_provider_strategy = { FARGATE = { weight = 50 @@ -176,16 +177,15 @@ module "ecs_service" { load_balancer = { service = { - target_group_arn = module.alb.target_groups["ex_ecs"].arn + target_group_arn = module.alb.target_groups["ex-ecs"].arn container_name = local.container_name container_port = local.container_port # for blue/green deployments advanced_configuration = { - alternate_target_group_arn = module.alb.target_groups["ex_ecs_alternate"].arn - production_listener_rule = module.alb.listener_rules["ex_http/production"].arn - test_listener_rule = module.alb.listener_rules["ex_http/test"].arn - role_arn = aws_iam_role.ecs_elb_permissions.arn + alternate_target_group_arn = module.alb.target_groups["ex-ecs-alternate"].arn + production_listener_rule = module.alb.listener_rules["ex-http/production"].arn + test_listener_rule = module.alb.listener_rules["ex-http/test"].arn } } } @@ -211,12 +211,6 @@ module "ecs_service" { } tags = local.tags - - depends_on = [ - aws_iam_role.ecs_elb_permissions, - aws_iam_role_policy_attachment.ecs_service_role, - aws_iam_role_policy_attachment.ecs_elb_management_role - ] } ################################################################################ @@ -315,7 +309,7 @@ module "alb" { } listeners = { - ex_http = { + ex-http = { port = 80 protocol = "HTTP" @@ -334,11 +328,11 @@ module "alb" { weighted_forward = { target_groups = [ { - target_group_key = "ex_ecs" + target_group_key = "ex-ecs" weight = 100 }, { - target_group_key = "ex_ecs_alternate" + target_group_key = "ex-ecs-alternate" weight = 0 } ] @@ -360,7 +354,7 @@ module "alb" { weighted_forward = { target_groups = [ { - target_group_key = "ex_ecs_alternate" + target_group_key = "ex-ecs-alternate" weight = 100 } ] @@ -380,7 +374,7 @@ module "alb" { } target_groups = { - ex_ecs = { + ex-ecs = { backend_protocol = "HTTP" backend_port = local.container_port target_type = "ip" @@ -405,7 +399,7 @@ module "alb" { } # for blue/green deployments - ex_ecs_alternate = { + ex-ecs-alternate = { backend_protocol = "HTTP" backend_port = local.container_port target_type = "ip" @@ -449,33 +443,3 @@ module "vpc" { tags = local.tags } - -resource "aws_iam_role" "ecs_elb_permissions" { - name = "${local.name}-ecs-elb-role" - assume_role_policy = jsonencode({ - Version = "2012-10-17" - Statement = [ - { - Action = "sts:AssumeRole" - Effect = "Allow" - Principal = { - Service = [ - "ecs-tasks.amazonaws.com", - "ecs.amazonaws.com", - ] - } - } - ] - }) -} - -# for example purposes only -resource "aws_iam_role_policy_attachment" "ecs_service_role" { - role = aws_iam_role.ecs_elb_permissions.name - policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole" -} - -resource "aws_iam_role_policy_attachment" "ecs_elb_management_role" { - role = aws_iam_role.ecs_elb_permissions.name - policy_arn = "arn:aws:iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers" -} diff --git a/examples/fargate/outputs.tf b/examples/fargate/outputs.tf index e31035a5..3e8a7657 100644 --- a/examples/fargate/outputs.tf +++ b/examples/fargate/outputs.tf @@ -17,14 +17,84 @@ output "cluster_name" { value = module.ecs_cluster.name } +output "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = module.ecs_cluster.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = module.ecs_cluster.cloudwatch_log_group_arn +} + 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 +output "capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = module.ecs_cluster.capacity_providers +} + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_cluster.task_exec_iam_role_name +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_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.ecs_cluster.task_exec_iam_role_unique_id +} + +output "infrastructure_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs_cluster.infrastructure_iam_role_arn +} + +output "infrastructure_iam_role_name" { + description = "IAM role name" + value = module.ecs_cluster.infrastructure_iam_role_name +} + +output "infrastructure_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs_cluster.infrastructure_iam_role_unique_id +} + +output "node_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs_cluster.node_iam_role_arn +} + +output "node_iam_role_name" { + description = "IAM role name" + value = module.ecs_cluster.node_iam_role_name +} + +output "node_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs_cluster.node_iam_role_unique_id +} + +output "node_iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = module.ecs_cluster.node_iam_instance_profile_arn +} + +output "node_iam_instance_profile_id" { + description = "Instance profile's ID" + value = module.ecs_cluster.node_iam_instance_profile_id +} + +output "node_iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = module.ecs_cluster.node_iam_instance_profile_unique } ################################################################################ diff --git a/examples/fargate/versions.tf b/examples/fargate/versions.tf index 70f23a44..7e5b918f 100644 --- a/examples/fargate/versions.tf +++ b/examples/fargate/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/examples/managed-instances/README.md b/examples/managed-instances/README.md new file mode 100644 index 00000000..71cabe58 --- /dev/null +++ b/examples/managed-instances/README.md @@ -0,0 +1,122 @@ +# ECS Clusters w/ ECS Managed Instances + +Configuration in this directory creates: + +- ECS cluster using ECS Managed Instances capacity provider +- 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 + +To run this example you need to execute: + +> [!CAUTION] +> Due to the ECS managed instances API, it appears that you need network connectivity quite early in the creation process and therefore also quite late in the deletion process. Therefore, for this example, a two step apply is necessary to create/destroy the resources successfully. The error you will see during creation if you do not follow this process is as follows: +> +> `Error: creating ECS Capacity Provider (mi-example): operation error ECS: CreateCapacityProvider, https response error StatusCode: 400, RequestID: 112ee8fc-7ffe-4f83-ae13-cb3a2efdb1c8, ClientException: Caught ServiceAccessDeniedException for ECSInfrastructureRole[arn:aws:iam::00000000000:role/ex-managed-instances-infra-2025121618163874450000000b] +> +> During deletion, you will see the process hang and timeout and notice that the managed instance agent is unable to connect and therefore unable to drain. +> If you create your network resources in a separate workspace/statefile (which you should!), it is unlikely you will face these issues. + +```bash +terraform init +terraform plan +terraform apply -target=module.vpc # to ensure NAT Gateway is created to allow network access quite early +terraform apply +``` + +Note that this example may create resources which will incur monetary charges on your AWS bill. Run `terraform destroy` when you no longer need these resources. + +```bash +terraform destroy -target=module.ecs_service -target=module.ecs_cluster +terraform destroy +``` + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.23 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.23 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 10.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 | ~> 6.0 | + +## Resources + +| Name | Type | +|------|------| +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [alb\_dns\_name](#output\_alb\_dns\_name) | The DNS name of the load balancer | +| [capacity\_providers](#output\_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 | +| [infrastructure\_iam\_role\_arn](#output\_infrastructure\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [infrastructure\_iam\_role\_name](#output\_infrastructure\_iam\_role\_name) | IAM role name | +| [infrastructure\_iam\_role\_unique\_id](#output\_infrastructure\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [node\_iam\_instance\_profile\_arn](#output\_node\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [node\_iam\_instance\_profile\_id](#output\_node\_iam\_instance\_profile\_id) | Instance profile's ID | +| [node\_iam\_instance\_profile\_unique](#output\_node\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [node\_iam\_role\_arn](#output\_node\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [node\_iam\_role\_name](#output\_node\_iam\_role\_name) | IAM role name | +| [node\_iam\_role\_unique\_id](#output\_node\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [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\_security\_group\_arn](#output\_service\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [service\_security\_group\_id](#output\_service\_security\_group\_id) | ID of the security group | +| [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | +| [service\_task\_definition\_family](#output\_service\_task\_definition\_family) | The unique name of the task definition | +| [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 | +| [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/examples/managed-instances/main.tf b/examples/managed-instances/main.tf new file mode 100644 index 00000000..37a5cd07 --- /dev/null +++ b/examples/managed-instances/main.tf @@ -0,0 +1,239 @@ +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 + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Cluster +################################################################################ + +module "ecs_cluster" { + source = "../../modules/cluster" + + name = local.name + + capacity_providers = { + mi-example = { + managed_instances_provider = { + instance_launch_template = { + instance_requirements = { + instance_generations = ["current"] + cpu_manufacturers = ["intel", "amd"] + + memory_mib = { + max = 8192 + min = 1024 + } + + vcpu_count = { + max = 4 + min = 1 + } + } + + network_configuration = { + subnets = module.vpc.private_subnets + } + + storage_configuration = { + storage_size_gib = 30 + } + } + } + } + } + + # Managed instances security group + vpc_id = module.vpc.vpc_id + security_group_ingress_rules = { + alb_http = { + from_port = local.container_port + description = "Service port" + referenced_security_group_id = module.alb.security_group_id + } + } + security_group_egress_rules = { + all = { + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "-1" + } + } + + tags = local.tags +} + +################################################################################ +# Service +################################################################################ + +module "ecs_service" { + source = "../../modules/service" + + # Service + name = local.name + cluster_arn = module.ecs_cluster.arn + + # Task Definition + requires_compatibilities = ["MANAGED_INSTANCES"] + launch_type = "EC2" + + # Container definition(s) + container_definitions = { + (local.container_name) = { + image = "public.ecr.aws/docker/library/httpd:latest" + + essential = true + entrypoint = ["sh", "-c"] + command = ["/bin/sh -c \"echo 'Amazon ECS Sample App

Amazon ECS Sample App

Congratulations!

Your application is now running on a container in Amazon ECS using Amazon ECS Managed Instances.

' > /usr/local/apache2/htdocs/index.html && httpd-foreground\""] + + cpu = 256 + memory = 512 + + readonlyRootFilesystem = false + + portMappings = [ + { + name = local.container_name + containerPort = local.container_port + hostPort = local.container_port + protocol = "tcp" + } + ] + } + } + + capacity_provider_strategy = { + # On-demand instances + mi-example = { + capacity_provider = module.ecs_cluster.capacity_providers["mi-example"].name + } + } + + load_balancer = { + service = { + target_group_arn = module.alb.target_groups["ex-ecs"].arn + container_name = local.container_name + container_port = local.container_port + } + } + + subnet_ids = module.vpc.private_subnets + security_group_ingress_rules = { + alb_http = { + from_port = local.container_port + description = "Service port" + referenced_security_group_id = module.alb.security_group_id + } + } + + tags = local.tags +} + +################################################################################ +# Supporting Resources +################################################################################ + +module "alb" { + source = "terraform-aws-modules/alb/aws" + version = "~> 10.0" + + name = local.name + + load_balancer_type = "application" + + vpc_id = module.vpc.vpc_id + subnets = module.vpc.public_subnets + + # For example only + enable_deletion_protection = false + + # Security Group + security_group_ingress_rules = { + all_http = { + from_port = local.container_port + to_port = local.container_port + ip_protocol = "tcp" + cidr_ipv4 = "0.0.0.0/0" + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = module.vpc.vpc_cidr_block + } + } + + listeners = { + ex-http = { + port = local.container_port + protocol = "HTTP" + + forward = { + target_group_key = "ex-ecs" + } + } + } + + target_groups = { + ex-ecs = { + backend_protocol = "HTTP" + backend_port = local.container_port + target_type = "ip" + deregistration_delay = 5 + load_balancing_cross_zone_enabled = true + + health_check = { + enabled = true + healthy_threshold = 5 + interval = 30 + matcher = "200" + path = "/" + port = "traffic-port" + protocol = "HTTP" + timeout = 5 + unhealthy_threshold = 2 + } + + # Theres nothing to attach here in this definition. Instead, + # ECS will attach the IPs of the tasks to this target group + create_attachment = false + } + } + + tags = local.tags +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 6.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/managed-instances/outputs.tf b/examples/managed-instances/outputs.tf new file mode 100644 index 00000000..af60e017 --- /dev/null +++ b/examples/managed-instances/outputs.tf @@ -0,0 +1,226 @@ +################################################################################ +# 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 "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = module.ecs_cluster.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = module.ecs_cluster.cloudwatch_log_group_arn +} + +output "cluster_capacity_providers" { + description = "Map of cluster capacity providers attributes" + value = module.ecs_cluster.cluster_capacity_providers +} + +output "capacity_providers" { + description = "Map of autoscaling capacity providers created and their attributes" + value = module.ecs_cluster.capacity_providers +} + +output "task_exec_iam_role_name" { + description = "Task execution IAM role name" + value = module.ecs_cluster.task_exec_iam_role_name +} + +output "task_exec_iam_role_arn" { + description = "Task execution IAM role ARN" + value = module.ecs_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.ecs_cluster.task_exec_iam_role_unique_id +} + +output "infrastructure_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs_cluster.infrastructure_iam_role_arn +} + +output "infrastructure_iam_role_name" { + description = "IAM role name" + value = module.ecs_cluster.infrastructure_iam_role_name +} + +output "infrastructure_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs_cluster.infrastructure_iam_role_unique_id +} + +output "node_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.ecs_cluster.node_iam_role_arn +} + +output "node_iam_role_name" { + description = "IAM role name" + value = module.ecs_cluster.node_iam_role_name +} + +output "node_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.ecs_cluster.node_iam_role_unique_id +} + +output "node_iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = module.ecs_cluster.node_iam_instance_profile_arn +} + +output "node_iam_instance_profile_id" { + description = "Instance profile's ID" + value = module.ecs_cluster.node_iam_instance_profile_id +} + +output "node_iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = module.ecs_cluster.node_iam_instance_profile_unique +} + +################################################################################ +# 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_definition_family" { + description = "The unique name of the task definition" + value = module.ecs_service.task_definition_family +} + +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 +} + +output "service_security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.ecs_service.security_group_arn +} + +output "service_security_group_id" { + description = "ID of the security group" + value = module.ecs_service.security_group_id +} + +################################################################################ +# Application Load Balancer +################################################################################ + +output "alb_dns_name" { + description = "The DNS name of the load balancer" + value = module.alb.dns_name +} diff --git a/examples/managed-instances/variables.tf b/examples/managed-instances/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/managed-instances/versions.tf b/examples/managed-instances/versions.tf new file mode 100644 index 00000000..7e5b918f --- /dev/null +++ b/examples/managed-instances/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.23" + } + } +} diff --git a/main.tf b/main.tf index 8833edff..cf9210b6 100644 --- a/main.tf +++ b/main.tf @@ -8,6 +8,8 @@ module "cluster" { create = var.create region = var.region + disable_v7_default_name_description = var.disable_v7_default_name_description + # Cluster configuration = var.cluster_configuration name = var.cluster_name @@ -23,7 +25,8 @@ module "cluster" { cloudwatch_log_group_tags = var.cloudwatch_log_group_tags # Cluster capacity providers - autoscaling_capacity_providers = var.autoscaling_capacity_providers + cluster_capacity_providers = var.cluster_capacity_providers + capacity_providers = var.capacity_providers default_capacity_provider_strategy = var.default_capacity_provider_strategy # Task execution IAM role @@ -42,6 +45,47 @@ module "cluster" { task_exec_secret_arns = var.task_exec_secret_arns task_exec_iam_statements = var.task_exec_iam_statements + # -- ECS Managed Instances -- + + # Infrastructure IAM role + create_infrastructure_iam_role = var.create_infrastructure_iam_role + infrastructure_iam_role_name = var.infrastructure_iam_role_name + infrastructure_iam_role_use_name_prefix = var.infrastructure_iam_role_use_name_prefix + infrastructure_iam_role_path = var.infrastructure_iam_role_path + infrastructure_iam_role_description = var.infrastructure_iam_role_description + infrastructure_iam_role_permissions_boundary = var.infrastructure_iam_role_permissions_boundary + infrastructure_iam_role_tags = var.infrastructure_iam_role_tags + + # Infrastructure IAM role policy + infrastructure_iam_role_source_policy_documents = var.infrastructure_iam_role_source_policy_documents + infrastructure_iam_role_override_policy_documents = var.infrastructure_iam_role_override_policy_documents + infrastructure_iam_role_statements = var.infrastructure_iam_role_statements + + # Node IAM role & instance profile + create_node_iam_instance_profile = var.create_node_iam_instance_profile + node_iam_role_name = var.node_iam_role_name + node_iam_role_use_name_prefix = var.node_iam_role_use_name_prefix + node_iam_role_path = var.node_iam_role_path + node_iam_role_description = var.node_iam_role_description + node_iam_role_permissions_boundary = var.node_iam_role_permissions_boundary + node_iam_role_additional_policies = var.node_iam_role_additional_policies + node_iam_role_tags = var.node_iam_role_tags + + # Node IAM role policy + node_iam_role_source_policy_documents = var.node_iam_role_source_policy_documents + node_iam_role_override_policy_documents = var.node_iam_role_override_policy_documents + node_iam_role_statements = var.node_iam_role_statements + + # Security Group + create_security_group = var.create_security_group + vpc_id = var.vpc_id + security_group_name = var.security_group_name + security_group_use_name_prefix = var.security_group_use_name_prefix + security_group_description = var.security_group_description + security_group_ingress_rules = var.security_group_ingress_rules + security_group_egress_rules = var.security_group_egress_rules + security_group_tags = var.security_group_tags + tags = merge(var.tags, var.cluster_tags) } @@ -58,6 +102,8 @@ module "service" { create_service = each.value.create_service region = var.region + disable_v7_default_name_description = var.disable_v7_default_name_description + # Service ignore_task_definition_changes = each.value.ignore_task_definition_changes alarms = each.value.alarms diff --git a/modules/cluster/README.md b/modules/cluster/README.md index 793a042d..7c59fbe9 100644 --- a/modules/cluster/README.md +++ b/modules/cluster/README.md @@ -5,6 +5,7 @@ Terraform module which creates Amazon ECS (Elastic Container Service) cluster re ## Available Features - ECS cluster +- ECS managed instances capacity providers including necessary IAM roles, permissions, and security group - Fargate capacity providers - EC2 AutoScaling Group capacity providers - ECS Service w/ task definition, task set, and container definition support @@ -13,6 +14,77 @@ For more details see the [design doc](https://github.com/terraform-aws-modules/t ## Usage +### ECS Managed Instances Capacity Providers + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + + name = "ecs-managed-instances" + + configuration = { + execute_command_configuration = { + logging = "OVERRIDE" + log_configuration = { + cloud_watch_log_group_name = "/aws/ecs/aws-managed-instances" + } + } + } + + capacity_providers = { + mi-example = { + managed_instances_provider = { + instance_launch_template = { + instance_requirements = { + instance_generations = ["current"] + cpu_manufacturers = ["intel", "amd"] + + memory_mib = { + max = 8192 + min = 1024 + } + + vcpu_count = { + max = 4 + min = 1 + } + } + + network_configuration = { + subnets = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + } + + storage_configuration = { + storage_size_gib = 30 + } + } + } + } + } + + # Managed instances security group + vpc_id = "vpc-1234556abcdef" + security_group_ingress_rules = { + alb-http = { + from_port = 4000 + description = "Service port" + referenced_security_group_id = "sg-12345678" + } + } + security_group_egress_rules = { + all = { + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "-1" + } + } + + tags = { + Environment = "Development" + Project = "EcsEc2" + } +} +``` + ### Fargate Capacity Providers ```hcl @@ -30,6 +102,7 @@ module "ecs_cluster" { } } + cluster_capacity_providers = ["FARGATE", "FARGATE_SPOT"] default_capacity_provider_strategy = { FARGATE = { weight = 50 @@ -74,29 +147,33 @@ module "ecs_cluster" { } } - autoscaling_capacity_providers = { + capacity_providers = { one = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" - managed_draining = "DISABLED" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 5 - minimum_scaling_step_size = 1 - status = "ENABLED" - target_capacity = 60 + auto_scaling_group_provider = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-one-20220603194933774300000011" + managed_draining = "DISABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 5 + minimum_scaling_step_size = 1 + status = "ENABLED" + target_capacity = 60 + } } } two = { - auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" - managed_draining = "ENABLED" - managed_termination_protection = "ENABLED" - - managed_scaling = { - maximum_scaling_step_size = 15 - minimum_scaling_step_size = 5 - status = "ENABLED" - target_capacity = 90 + auto_scaling_group_provider = { + auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" + managed_draining = "ENABLED" + managed_termination_protection = "ENABLED" + + managed_scaling = { + maximum_scaling_step_size = 15 + minimum_scaling_step_size = 5 + status = "ENABLED" + target_capacity = 90 + } } } } @@ -125,9 +202,11 @@ module "ecs_cluster" { ## 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) +- [ECS cluster w/ integrated service(s)](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS container definition](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/container-definition) +- [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) +- [ECS cluster w/ ECS managed instances capacity provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/managed-instances) ## Requirements @@ -135,13 +214,15 @@ module "ecs_cluster" { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | +| [time](#requirement\_time) | >= 0.13 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 6.21 | +| [aws](#provider\_aws) | >= 6.23 | +| [time](#provider\_time) | >= 0.13 | ## Modules @@ -155,31 +236,81 @@ No modules. | [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_instance_profile.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_instance_profile) | resource | +| [aws_iam_policy.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.node](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.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | +| [aws_iam_role.node](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_policy_attachment.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.node](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.node_additional](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_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_vpc_security_group_egress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | +| [time_sleep.this](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | +| [aws_iam_policy_document.infrastructure](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.infrastructure_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.node](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.node_assume_role_policy](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_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 | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster |
map(object({
auto_scaling_group_arn = string
managed_draining = optional(string, "ENABLED")
managed_scaling = optional(object({
instance_warmup_period = optional(number)
maximum_scaling_step_size = optional(number)
minimum_scaling_step_size = optional(number)
status = optional(string)
target_capacity = optional(number)
}))
managed_termination_protection = optional(string)
name = optional(string) # Will fall back to use map key if not set
tags = optional(map(string), {})
}))
| `null` | no | +| [capacity\_providers](#input\_capacity\_providers) | Map of capacity provider definitions to create |
map(object({
auto_scaling_group_provider = optional(object({
auto_scaling_group_arn = string
managed_draining = optional(string, "ENABLED")
managed_scaling = optional(object({
instance_warmup_period = optional(number)
maximum_scaling_step_size = optional(number)
minimum_scaling_step_size = optional(number)
status = optional(string)
target_capacity = optional(number)
}))
managed_termination_protection = optional(string)
}))
managed_instances_provider = optional(object({
infrastructure_role_arn = optional(string)
instance_launch_template = object({
ec2_instance_profile_arn = optional(string)
instance_requirements = optional(object({
accelerator_count = optional(object({
max = optional(number)
min = optional(number)
}))
accelerator_manufacturers = optional(list(string))
accelerator_names = optional(list(string))
accelerator_total_memory_mib = optional(object({
max = optional(number)
min = optional(number)
}))
accelerator_types = optional(list(string))
allowed_instance_types = optional(list(string))
bare_metal = optional(string)
baseline_ebs_bandwidth_mbps = optional(object({
max = optional(number)
min = optional(number)
}))
burstable_performance = optional(string)
cpu_manufacturers = optional(list(string))
excluded_instance_types = optional(list(string))
instance_generations = optional(list(string))
local_storage = optional(string)
local_storage_types = optional(list(string))
max_spot_price_as_percentage_of_optimal_on_demand_price = optional(number)
memory_gib_per_vcpu = optional(object({
max = optional(number)
min = optional(number)
}))
memory_mib = optional(object({
max = optional(number)
min = optional(number)
}))
network_bandwidth_gbps = optional(object({
max = optional(number)
min = optional(number)
}))
network_interface_count = optional(object({
max = optional(number)
min = optional(number)
}))
on_demand_max_price_percentage_over_lowest_price = optional(number)
require_hibernate_support = optional(bool)
spot_max_price_percentage_over_lowest_price = optional(number)
total_local_storage_gb = optional(object({
max = optional(number)
min = optional(number)
}))
vcpu_count = optional(object({
max = optional(number)
min = optional(number)
}))
}))
monitoring = optional(string)
network_configuration = optional(object({
security_groups = optional(list(string), [])
subnets = list(string)
}))
storage_configuration = optional(object({
storage_size_gib = number
}))
})
propagate_tags = optional(string, "CAPACITY_PROVIDER")
}))
name = optional(string) # Will fall back to use map key if not set
tags = optional(map(string), {})
}))
| `null` | no | | [cloudwatch\_log\_group\_class](#input\_cloudwatch\_log\_group\_class) | Specified the log class of the log group. Possible values are: `STANDARD` or `INFREQUENT_ACCESS` | `string` | `null` | 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\_name](#input\_cloudwatch\_log\_group\_name) | Custom name of CloudWatch Log Group for ECS cluster | `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\_capacity\_providers](#input\_cluster\_capacity\_providers) | List of capacity provider names to associate with the ECS cluster. Note: any capacity providers created by this module will be automatically added | `list(string)` | `[]` | no | +| [cluster\_capacity\_providers\_wait\_duration](#input\_cluster\_capacity\_providers\_wait\_duration) | Duration to wait after the ECS cluster has become active before attaching the cluster capacity providers | `string` | `"20s"` | no | | [configuration](#input\_configuration) | The execute command configuration for the cluster |
object({
execute_command_configuration = optional(object({
kms_key_id = optional(string)
log_configuration = optional(object({
cloud_watch_encryption_enabled = optional(bool)
cloud_watch_log_group_name = optional(string)
s3_bucket_encryption_enabled = optional(bool)
s3_bucket_name = optional(string)
s3_kms_key_id = optional(string)
s3_key_prefix = optional(string)
}))
logging = optional(string, "OVERRIDE")
}))
managed_storage_configuration = optional(object({
fargate_ephemeral_storage_kms_key_id = optional(string)
kms_key_id = optional(string)
}))
})
|
{
"execute_command_configuration": {
"log_configuration": {
"cloud_watch_log_group_name": "placeholder"
}
}
}
| 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\_infrastructure\_iam\_role](#input\_create\_infrastructure\_iam\_role) | Determines whether the ECS infrastructure IAM role should be created | `bool` | `true` | no | +| [create\_node\_iam\_instance\_profile](#input\_create\_node\_iam\_instance\_profile) | Determines whether an IAM instance profile is created or to use an existing IAM instance profile | `bool` | `true` | no | +| [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `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\_strategy](#input\_default\_capacity\_provider\_strategy) | Map of default capacity provider strategy definitions to use for the cluster |
map(object({
base = optional(number)
name = optional(string) # Will fall back to use map key if not set
weight = optional(number)
}))
| `{}` | no | +| [disable\_v7\_default\_name\_description](#input\_disable\_v7\_default\_name\_description) | [DEPRECATED - will be removed in next breaking change] Determines whether to disable the default postfix added to resource names | `bool` | `false` | no | +| [infrastructure\_iam\_role\_description](#input\_infrastructure\_iam\_role\_description) | Description of the role | `string` | `null` | no | +| [infrastructure\_iam\_role\_name](#input\_infrastructure\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | +| [infrastructure\_iam\_role\_override\_policy\_documents](#input\_infrastructure\_iam\_role\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [infrastructure\_iam\_role\_path](#input\_infrastructure\_iam\_role\_path) | IAM role path | `string` | `null` | no | +| [infrastructure\_iam\_role\_permissions\_boundary](#input\_infrastructure\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [infrastructure\_iam\_role\_source\_policy\_documents](#input\_infrastructure\_iam\_role\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [infrastructure\_iam\_role\_statements](#input\_infrastructure\_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 |
map(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string, "Allow")
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
variable = string
values = list(string)
})))
}))
| `null` | no | +| [infrastructure\_iam\_role\_tags](#input\_infrastructure\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | +| [infrastructure\_iam\_role\_use\_name\_prefix](#input\_infrastructure\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | | [name](#input\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [node\_iam\_role\_additional\_policies](#input\_node\_iam\_role\_additional\_policies) | Additional policies to be added to the IAM role | `map(string)` | `{}` | no | +| [node\_iam\_role\_description](#input\_node\_iam\_role\_description) | Description of the role | `string` | `"ECS Managed Instances node IAM role"` | no | +| [node\_iam\_role\_name](#input\_node\_iam\_role\_name) | Name to use on IAM role/instance profile created | `string` | `null` | no | +| [node\_iam\_role\_override\_policy\_documents](#input\_node\_iam\_role\_override\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid` | `list(string)` | `[]` | no | +| [node\_iam\_role\_path](#input\_node\_iam\_role\_path) | IAM role/instance profile path | `string` | `null` | no | +| [node\_iam\_role\_permissions\_boundary](#input\_node\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | +| [node\_iam\_role\_source\_policy\_documents](#input\_node\_iam\_role\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s | `list(string)` | `[]` | no | +| [node\_iam\_role\_statements](#input\_node\_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 |
map(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string, "Allow")
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
variable = string
values = list(string)
})))
}))
| `null` | no | +| [node\_iam\_role\_tags](#input\_node\_iam\_role\_tags) | A map of additional tags to add to the IAM role/instance profile created | `map(string)` | `{}` | no | +| [node\_iam\_role\_use\_name\_prefix](#input\_node\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role/instance profile name (`node_iam_role_name`) is used as a prefix | `bool` | `true` | no | | [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no | +| [security\_group\_description](#input\_security\_group\_description) | Description of the security group created | `string` | `null` | no | +| [security\_group\_egress\_rules](#input\_security\_group\_egress\_rules) | Security group egress rules to add to the security group created |
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
}))
|
{
"all_ipv4": {
"cidr_ipv4": "0.0.0.0/0",
"description": "Allow all IPv4 traffic",
"ip_protocol": "-1"
},
"all_ipv6": {
"cidr_ipv6": "::/0",
"description": "Allow all IPv6 traffic",
"ip_protocol": "-1"
}
}
| no | +| [security\_group\_ingress\_rules](#input\_security\_group\_ingress\_rules) | Security group ingress rules to add to the security group created |
map(object({
name = optional(string)

cidr_ipv4 = optional(string)
cidr_ipv6 = optional(string)
description = optional(string)
from_port = optional(string)
ip_protocol = optional(string, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
}))
| `{}` | no | +| [security\_group\_name](#input\_security\_group\_name) | Name to use on security group created | `string` | `null` | 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\_defaults](#input\_service\_connect\_defaults) | Configures a default Service Connect namespace |
object({
namespace = string
})
| `null` | no | | [setting](#input\_setting) | List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster |
list(object({
name = string
value = string
}))
|
[
{
"name": "containerInsights",
"value": "enabled"
}
]
| no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | @@ -193,21 +324,31 @@ No modules. | [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 |
map(object({
sid = optional(string)
actions = optional(list(string))
not_actions = optional(list(string))
effect = optional(string, "Allow")
resources = optional(list(string))
not_resources = optional(list(string))
principals = optional(list(object({
type = string
identifiers = list(string)
})))
not_principals = optional(list(object({
type = string
identifiers = list(string)
})))
condition = optional(list(object({
test = string
variable = string
values = list(string)
})))
}))
| `null` | 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)` | `[]` | 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)` | `[]` | no | +| [vpc\_id](#input\_vpc\_id) | The ID of the VPC where the security group will be created | `string` | `null` | 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 | +| [capacity\_providers](#output\_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 | +| [infrastructure\_iam\_role\_arn](#output\_infrastructure\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [infrastructure\_iam\_role\_name](#output\_infrastructure\_iam\_role\_name) | IAM role name | +| [infrastructure\_iam\_role\_unique\_id](#output\_infrastructure\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | | [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 | +| [node\_iam\_instance\_profile\_arn](#output\_node\_iam\_instance\_profile\_arn) | ARN assigned by AWS to the instance profile | +| [node\_iam\_instance\_profile\_id](#output\_node\_iam\_instance\_profile\_id) | Instance profile's ID | +| [node\_iam\_instance\_profile\_unique](#output\_node\_iam\_instance\_profile\_unique) | Stable and unique string identifying the IAM instance profile | +| [node\_iam\_role\_arn](#output\_node\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [node\_iam\_role\_name](#output\_node\_iam\_role\_name) | IAM role name | +| [node\_iam\_role\_unique\_id](#output\_node\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | +| [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | The Amazon Resource Name (ARN) specifying the IAM role | +| [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | IAM role name | +| [task\_exec\_iam\_role\_unique\_id](#output\_task\_exec\_iam\_role\_unique\_id) | Stable and unique string identifying the IAM role | ## License diff --git a/modules/cluster/main.tf b/modules/cluster/main.tf index 6eabcc3d..74c2d7df 100644 --- a/modules/cluster/main.tf +++ b/modules/cluster/main.tf @@ -1,3 +1,21 @@ +data "aws_region" "current" { + region = var.region + + count = var.create ? 1 : 0 +} +data "aws_partition" "current" { + count = var.create ? 1 : 0 +} +data "aws_caller_identity" "current" { + count = var.create ? 1 : 0 +} + +locals { + account_id = try(data.aws_caller_identity.current[0].account_id, "") + partition = try(data.aws_partition.current[0].partition, "") + region = try(data.aws_region.current[0].region, "") +} + ################################################################################ # Cluster ################################################################################ @@ -95,16 +113,29 @@ resource "aws_cloudwatch_log_group" "this" { # Cluster Capacity Providers ################################################################################ +# The managed instance capacity provider returns quickly in a `CREATING` state, +# but we need to wait for it to be in the `ACTIVE` state before associating it with the cluster. +resource "time_sleep" "this" { + count = var.create ? 1 : 0 + + create_duration = var.cluster_capacity_providers_wait_duration + + triggers = { + # Triggers wants a string so we have to do some cheap serialization/deserialization to transport correctly + capacity_provider_names = var.capacity_providers != null ? join(",", [for k, v in var.capacity_providers : aws_ecs_capacity_provider.this[k].name]) : "" + # This is done so that the output of `capacity_providers` also waits for them to be `ACTIVE` + # for the scenarios where users define separate cluster and service modules (serivce needs the provider to be ACTIVE) + capacity_providers = var.capacity_providers != null ? jsonencode(aws_ecs_capacity_provider.this) : "" + } +} + resource "aws_ecs_cluster_capacity_providers" "this" { count = var.create ? 1 : 0 region = var.region - cluster_name = aws_ecs_cluster.this[0].name - capacity_providers = distinct(concat( - [for k, v in var.default_capacity_provider_strategy : try(coalesce(v.name, k))], - var.autoscaling_capacity_providers != null ? [for k, v in var.autoscaling_capacity_providers : try(coalesce(v.name, k))] : [] - )) + cluster_name = aws_ecs_cluster.this[0].name + capacity_providers = distinct(concat(var.cluster_capacity_providers, compact(split(",", time_sleep.this[0].triggers["capacity_provider_names"])))) # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html#capacity-providers-considerations dynamic "default_capacity_provider_strategy" { @@ -113,50 +144,208 @@ resource "aws_ecs_cluster_capacity_providers" "this" { content { base = default_capacity_provider_strategy.value.base capacity_provider = try(coalesce(default_capacity_provider_strategy.value.name, default_capacity_provider_strategy.key)) - weight = default_capacity_provider_strategy.value.weight + weight = coalesce(default_capacity_provider_strategy.value.weight, 1) } } - - depends_on = [ - aws_ecs_capacity_provider.this - ] } ################################################################################ -# Capacity Provider - Autoscaling Group(s) +# Capacity Provider ################################################################################ +locals { + managed_instances_enabled = var.capacity_providers != null ? anytrue([for k, v in var.capacity_providers : v.managed_instances_provider != null]) : false +} + resource "aws_ecs_capacity_provider" "this" { - for_each = var.create && var.autoscaling_capacity_providers != null ? var.autoscaling_capacity_providers : {} + for_each = var.create && var.capacity_providers != null ? var.capacity_providers : {} region = var.region - auto_scaling_group_provider { - auto_scaling_group_arn = each.value.auto_scaling_group_arn - managed_draining = each.value.managed_draining + dynamic "auto_scaling_group_provider" { + for_each = each.value.auto_scaling_group_provider != null ? [each.value.auto_scaling_group_provider] : [] - dynamic "managed_scaling" { - for_each = each.value.managed_scaling != null ? [each.value.managed_scaling] : [] + content { + auto_scaling_group_arn = auto_scaling_group_provider.value.auto_scaling_group_arn + managed_draining = auto_scaling_group_provider.value.managed_draining + + dynamic "managed_scaling" { + for_each = auto_scaling_group_provider.value.managed_scaling != null ? [auto_scaling_group_provider.value.managed_scaling] : [] - content { - instance_warmup_period = managed_scaling.value.instance_warmup_period - maximum_scaling_step_size = managed_scaling.value.maximum_scaling_step_size - minimum_scaling_step_size = managed_scaling.value.minimum_scaling_step_size - status = managed_scaling.value.status - target_capacity = managed_scaling.value.target_capacity + content { + instance_warmup_period = managed_scaling.value.instance_warmup_period + maximum_scaling_step_size = managed_scaling.value.maximum_scaling_step_size + minimum_scaling_step_size = managed_scaling.value.minimum_scaling_step_size + status = managed_scaling.value.status + target_capacity = managed_scaling.value.target_capacity + } } + + # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work + managed_termination_protection = auto_scaling_group_provider.value.managed_scaling != null ? auto_scaling_group_provider.value.managed_termination_protection : "DISABLED" } + } + + dynamic "managed_instances_provider" { + for_each = each.value.managed_instances_provider != null ? [each.value.managed_instances_provider] : [] + + content { + infrastructure_role_arn = local.create_infrastructure_iam_role ? aws_iam_role.infrastructure[0].arn : managed_instances_provider.value.infrastructure_role_arn + + dynamic "instance_launch_template" { + for_each = managed_instances_provider.value.instance_launch_template != null ? [managed_instances_provider.value.instance_launch_template] : [] + + content { + ec2_instance_profile_arn = local.create_node_iam_instance_profile ? aws_iam_instance_profile.this[0].arn : instance_launch_template.value.ec2_instance_profile_arn + + dynamic "instance_requirements" { + for_each = instance_launch_template.value.instance_requirements != null ? [instance_launch_template.value.instance_requirements] : [] + + content { + dynamic "accelerator_count" { + for_each = instance_requirements.value.accelerator_count != null ? [instance_requirements.value.accelerator_count] : [] + + content { + max = accelerator_count.value.max + min = accelerator_count.value.min + } + } + + accelerator_manufacturers = instance_requirements.value.accelerator_manufacturers + accelerator_names = instance_requirements.value.accelerator_names + + dynamic "accelerator_total_memory_mib" { + for_each = instance_requirements.value.accelerator_total_memory_mib != null ? [instance_requirements.value.accelerator_total_memory_mib] : [] + + content { + max = accelerator_total_memory_mib.value.max + min = accelerator_total_memory_mib.value.min + } + } + + accelerator_types = instance_requirements.value.accelerator_types + allowed_instance_types = instance_requirements.value.allowed_instance_types + bare_metal = instance_requirements.value.bare_metal + + dynamic "baseline_ebs_bandwidth_mbps" { + for_each = instance_requirements.value.baseline_ebs_bandwidth_mbps != null ? [instance_requirements.value.baseline_ebs_bandwidth_mbps] : [] + + content { + max = baseline_ebs_bandwidth_mbps.value.max + min = baseline_ebs_bandwidth_mbps.value.min + } + } + + burstable_performance = instance_requirements.value.burstable_performance + cpu_manufacturers = instance_requirements.value.cpu_manufacturers + excluded_instance_types = instance_requirements.value.excluded_instance_types + instance_generations = instance_requirements.value.instance_generations + local_storage = instance_requirements.value.local_storage + local_storage_types = instance_requirements.value.local_storage_types + max_spot_price_as_percentage_of_optimal_on_demand_price = instance_requirements.value.max_spot_price_as_percentage_of_optimal_on_demand_price + + dynamic "memory_gib_per_vcpu" { + for_each = instance_requirements.value.memory_gib_per_vcpu != null ? [instance_requirements.value.memory_gib_per_vcpu] : [] + + content { + max = memory_gib_per_vcpu.value.max + min = memory_gib_per_vcpu.value.min + } + } + + dynamic "memory_mib" { + for_each = instance_requirements.value.memory_mib != null ? [instance_requirements.value.memory_mib] : [] + + content { + max = memory_mib.value.max + min = memory_mib.value.min + } + } + + dynamic "network_bandwidth_gbps" { + for_each = instance_requirements.value.network_bandwidth_gbps != null ? [instance_requirements.value.network_bandwidth_gbps] : [] + + content { + max = network_bandwidth_gbps.value.max + min = network_bandwidth_gbps.value.min + } + } + + dynamic "network_interface_count" { + for_each = instance_requirements.value.network_interface_count != null ? [instance_requirements.value.network_interface_count] : [] + + content { + max = network_interface_count.value.max + min = network_interface_count.value.min + } + } + + on_demand_max_price_percentage_over_lowest_price = instance_requirements.value.on_demand_max_price_percentage_over_lowest_price + require_hibernate_support = instance_requirements.value.require_hibernate_support + spot_max_price_percentage_over_lowest_price = instance_requirements.value.spot_max_price_percentage_over_lowest_price + + dynamic "total_local_storage_gb" { + for_each = instance_requirements.value.total_local_storage_gb != null ? [instance_requirements.value.total_local_storage_gb] : [] + + content { + max = total_local_storage_gb.value.max + min = total_local_storage_gb.value.min + } + } + + dynamic "vcpu_count" { + for_each = instance_requirements.value.vcpu_count != null ? [instance_requirements.value.vcpu_count] : [] + + content { + max = vcpu_count.value.max + min = vcpu_count.value.min + } + } + } + } + + monitoring = instance_launch_template.value.monitoring - # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work - managed_termination_protection = each.value.managed_scaling != null ? each.value.managed_termination_protection : "DISABLED" + dynamic "network_configuration" { + for_each = instance_launch_template.value.network_configuration != null ? [instance_launch_template.value.network_configuration] : [] + + content { + security_groups = local.create_security_group ? flatten(concat(aws_security_group.this[*].id, network_configuration.value.security_groups)) : network_configuration.value.security_groups + subnets = network_configuration.value.subnets + } + } + + dynamic "storage_configuration" { + for_each = instance_launch_template.value.storage_configuration != null ? [instance_launch_template.value.storage_configuration] : [] + + content { + storage_size_gib = storage_configuration.value.storage_size_gib + } + } + } + } + + propagate_tags = managed_instances_provider.value.propagate_tags + } } + cluster = each.value.managed_instances_provider != null ? aws_ecs_cluster.this[0].name : null + name = try(coalesce(each.value.name, each.key), "") tags = merge( var.tags, each.value.tags, ) + + # What an awful friggin service API they created with managed instances + depends_on = [ + aws_iam_role_policy_attachment.task_exec, + aws_iam_role_policy_attachment.infrastructure, + aws_iam_role_policy_attachment.node, + aws_vpc_security_group_ingress_rule.this, + aws_vpc_security_group_egress_rule.this, + ] } ################################################################################ @@ -165,7 +354,7 @@ resource "aws_ecs_capacity_provider" "this" { ################################################################################ locals { - task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.name), "") + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, "${var.name}${var.disable_v7_default_name_description ? "" : "-task-exec"}"), "") 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 @@ -311,3 +500,523 @@ resource "aws_iam_role_policy_attachment" "task_exec" { role = aws_iam_role.task_exec[0].name policy_arn = aws_iam_policy.task_exec[0].arn } + +############################################################################################ +# Infrastructure IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/infrastructure_IAM_role.html +############################################################################################ + +locals { + create_infrastructure_iam_role = var.create && var.create_infrastructure_iam_role && local.managed_instances_enabled + + infrastructure_iam_role_name = coalesce(var.infrastructure_iam_role_name, "${var.name}-infra") +} + +data "aws_iam_policy_document" "infrastructure_assume" { + count = local.create_infrastructure_iam_role ? 1 : 0 + + statement { + sid = "ECSServiceAssumeRole" + actions = [ + "sts:AssumeRole", + "sts:TagSession", + ] + + principals { + type = "Service" + identifiers = ["ecs.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "infrastructure" { + count = local.create_infrastructure_iam_role ? 1 : 0 + + name = var.infrastructure_iam_role_use_name_prefix ? null : local.infrastructure_iam_role_name + name_prefix = var.infrastructure_iam_role_use_name_prefix ? "${local.infrastructure_iam_role_name}-" : null + path = var.infrastructure_iam_role_path + description = coalesce(var.infrastructure_iam_role_description, "Amazon ECS infrastructure IAM role that is used to manage your infrastructure (managed instances)") + + assume_role_policy = data.aws_iam_policy_document.infrastructure_assume[0].json + permissions_boundary = var.infrastructure_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.infrastructure_iam_role_tags) +} + +################################################################################ +# Infrastructure IAM role policy +# +# The managed policy requires role names to start with `ecsInstanceRole` +# So we are duplicating the policy here to avoid that unfortunate and surprising requirement +# +# Ref: https://docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonECSInfrastructureRolePolicyForManagedInstances.html +################################################################################ + +data "aws_iam_policy_document" "infrastructure" { + count = local.create_infrastructure_iam_role ? 1 : 0 + + source_policy_documents = var.infrastructure_iam_role_source_policy_documents + override_policy_documents = var.infrastructure_iam_role_override_policy_documents + + statement { + sid = "CreateLaunchTemplateForManagedInstances" + actions = ["ec2:CreateLaunchTemplate"] + resources = ["arn:${local.partition}:ec2:${local.region}:${local.account_id}:launch-template/*"] + + condition { + test = "StringEquals" + variable = "aws:RequestTag/AmazonECSManaged" + values = [true] + } + } + + statement { + sid = "CreateLaunchTemplateVersionsForManagedInstances" + actions = [ + "ec2:CreateLaunchTemplateVersion", + "ec2:ModifyLaunchTemplate", + ] + resources = ["arn:${local.partition}:ec2:${local.region}:${local.account_id}:launch-template/*"] + + condition { + test = "StringEquals" + variable = "ec2:ManagedResourceOperator" + values = ["ecs.amazonaws.com"] + } + } + + statement { + sid = "ProvisionEC2InstancesForManagedInstances" + actions = ["ec2:CreateFleet"] + resources = [ + "arn:${local.partition}:ec2:${local.region}:*:fleet/*", + "arn:${local.partition}:ec2:${local.region}:*:instance/*", + "arn:${local.partition}:ec2:${local.region}:*:network-interface/*", + "arn:${local.partition}:ec2:${local.region}:*:launch-template/*", + "arn:${local.partition}:ec2:${local.region}:*:security-group/*", + "arn:${local.partition}:ec2:${local.region}:*:subnet/*", + "arn:${local.partition}:ec2:${local.region}:*:volume/*", + "arn:${local.partition}:ec2:${local.region}:*:image/*", + ] + + condition { + test = "StringEquals" + variable = "aws:RequestTag/AmazonECSManaged" + values = [true] + } + } + + statement { + sid = "RunInstancesForManagedInstances" + actions = ["ec2:RunInstances"] + resources = [ + "arn:${local.partition}:ec2:${local.region}:*:instance/*", + "arn:${local.partition}:ec2:${local.region}:*:volume/*", + "arn:${local.partition}:ec2:${local.region}:*:network-interface/*", + ] + + condition { + test = "StringEquals" + variable = "aws:RequestTag/AmazonECSManaged" + values = [true] + } + } + + statement { + sid = "RunInstancesForECSManagedLaunchTemplates" + actions = ["ec2:RunInstances"] + resources = ["arn:${local.partition}:ec2:${local.region}:*:launch-template/*"] + + condition { + test = "StringEquals" + variable = "ec2:ResourceTag/AmazonECSManaged" + values = [true] + } + } + + statement { + sid = "RunInstancesForSupportingResources" + actions = ["ec2:RunInstances"] + resources = [ + "arn:${local.partition}:ec2:${local.region}:*:subnet/*", + "arn:${local.partition}:ec2:${local.region}:*:security-group/*", + "arn:${local.partition}:ec2:${local.region}:*:image/*", + ] + } + + statement { + sid = "TagOnCreateEC2ResourcesForManagedInstances" + actions = ["ec2:CreateTags"] + resources = [ + "arn:${local.partition}:ec2:${local.region}:*:fleet/*", + "arn:${local.partition}:ec2:${local.region}:*:launch-template/*", + "arn:${local.partition}:ec2:${local.region}:*:network-interface/*", + "arn:${local.partition}:ec2:${local.region}:*:instance/*", + "arn:${local.partition}:ec2:${local.region}:*:volume/*", + ] + + condition { + test = "StringEquals" + variable = "ec2:CreateAction" + values = [ + "CreateFleet", + "CreateLaunchTemplate", + "RunInstances", + ] + } + } + + statement { + sid = "PassInstanceRoleForManagedInstances" + actions = ["iam:PassRole"] + resources = [aws_iam_role.node[0].arn] + + condition { + test = "StringEquals" + variable = "iam:PassedToService" + values = ["ec2.amazonaws.com"] + } + } + + statement { + sid = "CreateServiceLinkedRoleForEC2Spot" + actions = ["iam:CreateServiceLinkedRole"] + resources = ["arn:${local.partition}:iam::${local.account_id}:role/aws-service-role/spot.amazonaws.com/AWSServiceRoleForEC2Spot"] + } + + statement { + sid = "DescribeEC2ResourcesManagedByECS" + actions = [ + "ec2:DescribeInstances", + "ec2:DescribeInstanceTypes", + "ec2:DescribeLaunchTemplates", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeInstanceTypeOfferings", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + ] + resources = ["*"] + } + + dynamic "statement" { + for_each = var.infrastructure_iam_role_statements != null ? var.infrastructure_iam_role_statements : {} + + content { + sid = try(coalesce(statement.value.sid, statement.key)) + actions = statement.value.actions + not_actions = statement.value.not_actions + effect = statement.value.effect + resources = statement.value.resources + not_resources = statement.value.not_resources + + dynamic "principals" { + for_each = statement.value.principals != null ? statement.value.principals : [] + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = statement.value.not_principals != null ? statement.value.not_principals : [] + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = statement.value.condition != null ? statement.value.condition : [] + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "infrastructure" { + count = local.create_infrastructure_iam_role ? 1 : 0 + + name = var.infrastructure_iam_role_use_name_prefix ? null : local.infrastructure_iam_role_name + name_prefix = var.infrastructure_iam_role_use_name_prefix ? "${local.infrastructure_iam_role_name}-" : null + description = coalesce(var.infrastructure_iam_role_description, "ECS Managed Instances infrastructure role permissions") + policy = data.aws_iam_policy_document.infrastructure[0].json + + tags = merge(var.tags, var.infrastructure_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "infrastructure" { + count = local.create_infrastructure_iam_role ? 1 : 0 + + policy_arn = aws_iam_policy.infrastructure[0].arn + role = aws_iam_role.infrastructure[0].name +} + +################################################################################ +# Node IAM role +################################################################################ + +locals { + create_node_iam_instance_profile = var.create && var.create_node_iam_instance_profile && local.managed_instances_enabled + + node_iam_role_name = coalesce(var.node_iam_role_name, "${var.name}-node") +} + +data "aws_iam_policy_document" "node_assume_role_policy" { + count = local.create_node_iam_instance_profile ? 1 : 0 + + statement { + sid = "ECSNodeAssumeRole" + actions = [ + "sts:AssumeRole", + "sts:TagSession", + ] + + principals { + type = "Service" + identifiers = ["ec2.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "node" { + count = local.create_node_iam_instance_profile ? 1 : 0 + + name = var.node_iam_role_use_name_prefix ? null : local.node_iam_role_name + name_prefix = var.node_iam_role_use_name_prefix ? "${local.node_iam_role_name}-" : null + path = var.node_iam_role_path + description = coalesce(var.node_iam_role_description, "Amazon ECS managed instance node role for ECS cluster ${var.name}") + + assume_role_policy = data.aws_iam_policy_document.node_assume_role_policy[0].json + permissions_boundary = var.node_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.node_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "node_additional" { + for_each = { for k, v in var.node_iam_role_additional_policies : k => v if local.create_node_iam_instance_profile } + + policy_arn = each.value + role = aws_iam_role.node[0].name +} + +################################################################################ +# Node IAM role policy +# +# Due to this warning from ECS documentation +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/managed-instances-instance-profile.html +# +# > If you are using Amazon ECS Managed Instances with the AWS-managed Infrastructure policy, +# > the instance profile must be named ecsInstanceRole. If you are using a custom policy for +# > the Infrastructure role, the instance profile can have an alternative name. +# +# We default to creating the policy in order to remove this "surprising" requirement +# Ref: docs.aws.amazon.com/aws-managed-policy/latest/reference/AmazonECSInstanceRolePolicyForManagedInstances.html +################################################################################ + +data "aws_iam_policy_document" "node" { + count = local.create_node_iam_instance_profile ? 1 : 0 + + source_policy_documents = var.node_iam_role_source_policy_documents + override_policy_documents = var.node_iam_role_override_policy_documents + + statement { + sid = "ECSAgentDiscoverPollEndpointPermissions" + actions = ["ecs:DiscoverPollEndpoint"] + resources = ["*"] + } + + statement { + sid = "ECSAgentRegisterPermissions" + actions = ["ecs:RegisterContainerInstance"] + resources = [aws_ecs_cluster.this[0].arn] + } + + statement { + sid = "ECSAgentPollPermissions" + actions = ["ecs:Poll"] + resources = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:container-instance/*"] + } + + statement { + sid = "ECSAgentTelemetryPermissions" + actions = [ + "ecs:StartTelemetrySession", + "ecs:PutSystemLogEvents", + ] + resources = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:container-instance/*"] + } + + statement { + sid = "ECSAgentStateChangePermissions" + actions = [ + "ecs:SubmitAttachmentStateChanges", + "ecs:SubmitTaskStateChange", + ] + resources = [aws_ecs_cluster.this[0].arn] + } + + dynamic "statement" { + for_each = var.node_iam_role_statements != null ? var.node_iam_role_statements : {} + + content { + sid = try(coalesce(statement.value.sid, statement.key)) + actions = statement.value.actions + not_actions = statement.value.not_actions + effect = statement.value.effect + resources = statement.value.resources + not_resources = statement.value.not_resources + + dynamic "principals" { + for_each = statement.value.principals != null ? statement.value.principals : [] + + content { + type = principals.value.type + identifiers = principals.value.identifiers + } + } + + dynamic "not_principals" { + for_each = statement.value.not_principals != null ? statement.value.not_principals : [] + + content { + type = not_principals.value.type + identifiers = not_principals.value.identifiers + } + } + + dynamic "condition" { + for_each = statement.value.condition != null ? statement.value.condition : [] + + content { + test = condition.value.test + values = condition.value.values + variable = condition.value.variable + } + } + } + } +} + +resource "aws_iam_policy" "node" { + count = local.create_node_iam_instance_profile ? 1 : 0 + + name = var.node_iam_role_use_name_prefix ? null : local.node_iam_role_name + name_prefix = var.node_iam_role_use_name_prefix ? "${local.node_iam_role_name}-" : null + description = coalesce(var.node_iam_role_description, "ECS Managed Instances permissions") + policy = data.aws_iam_policy_document.node[0].json + + tags = merge(var.tags, var.node_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "node" { + count = local.create_node_iam_instance_profile ? 1 : 0 + + policy_arn = aws_iam_policy.node[0].arn + role = aws_iam_role.node[0].name +} + +################################################################################ +# Node Instance Profile +################################################################################ + +resource "aws_iam_instance_profile" "this" { + count = local.create_node_iam_instance_profile ? 1 : 0 + + role = aws_iam_role.node[0].name + + name = var.node_iam_role_use_name_prefix ? null : local.node_iam_role_name + name_prefix = var.node_iam_role_use_name_prefix ? "${local.node_iam_role_name}-" : null + path = var.node_iam_role_path + + tags = merge(var.tags, var.node_iam_role_tags) + + lifecycle { + create_before_destroy = true + } +} + +################################################################################ +# Security Group +################################################################################ + +locals { + create_security_group = var.create && var.create_security_group && local.managed_instances_enabled + + security_group_name = coalesce(var.security_group_name, "${var.name}${var.disable_v7_default_name_description ? "" : "-cluster"}") +} + +resource "aws_security_group" "this" { + count = local.create_security_group ? 1 : 0 + + region = var.region + + 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 = coalesce(var.security_group_description, "Security group for ECS managed instances in cluster ${aws_ecs_cluster.this[0].name}") + vpc_id = var.vpc_id + + tags = merge( + var.tags, + { Name = local.security_group_name }, + var.security_group_tags + ) + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_vpc_security_group_ingress_rule" "this" { + for_each = { for k, v in var.security_group_ingress_rules : k => v if var.security_group_ingress_rules != null && local.create_security_group } + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = each.value.from_port + ip_protocol = each.value.ip_protocol + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id == "self" ? aws_security_group.this[0].id : each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = coalesce(each.value.name, "${local.security_group_name}-${each.key}") }, + each.value.tags + ) + to_port = try(coalesce(each.value.to_port, each.value.from_port), null) +} + +resource "aws_vpc_security_group_egress_rule" "this" { + for_each = { for k, v in var.security_group_egress_rules : k => v if var.security_group_egress_rules != null && local.create_security_group } + + region = var.region + + cidr_ipv4 = each.value.cidr_ipv4 + cidr_ipv6 = each.value.cidr_ipv6 + description = each.value.description + from_port = try(coalesce(each.value.from_port, each.value.to_port), null) + ip_protocol = each.value.ip_protocol + prefix_list_id = each.value.prefix_list_id + referenced_security_group_id = each.value.referenced_security_group_id == "self" ? aws_security_group.this[0].id : each.value.referenced_security_group_id + security_group_id = aws_security_group.this[0].id + tags = merge( + var.tags, + var.security_group_tags, + { "Name" = coalesce(each.value.name, "${local.security_group_name}-${each.key}") }, + each.value.tags + ) + to_port = each.value.to_port +} diff --git a/modules/cluster/outputs.tf b/modules/cluster/outputs.tf index 4e05381f..5a9e505a 100644 --- a/modules/cluster/outputs.tf +++ b/modules/cluster/outputs.tf @@ -44,9 +44,9 @@ output "cluster_capacity_providers" { # Capacity Provider - Autoscaling Group(s) ################################################################################ -output "autoscaling_capacity_providers" { +output "capacity_providers" { description = "Map of autoscaling capacity providers created and their attributes" - value = aws_ecs_capacity_provider.this + value = var.capacity_providers != null ? jsondecode(time_sleep.this[0].triggers["capacity_providers"]) : {} } ################################################################################ @@ -54,17 +54,74 @@ output "autoscaling_capacity_providers" { # 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" + description = "The Amazon Resource Name (ARN) specifying the IAM role" value = try(aws_iam_role.task_exec[0].arn, null) } +output "task_exec_iam_role_name" { + description = "IAM role name" + value = try(aws_iam_role.task_exec[0].name, null) +} + output "task_exec_iam_role_unique_id" { - description = "Stable and unique string identifying the task execution IAM role" + description = "Stable and unique string identifying the IAM role" value = try(aws_iam_role.task_exec[0].unique_id, null) } + +############################################################################################ +# Infrastructure IAM role +############################################################################################ + +output "infrastructure_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.infrastructure[0].arn, null) +} + +output "infrastructure_iam_role_name" { + description = "IAM role name" + value = try(aws_iam_role.infrastructure[0].name, null) +} + +output "infrastructure_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.infrastructure[0].unique_id, null) +} + +################################################################################ +# Node IAM role +################################################################################ + +output "node_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = try(aws_iam_role.node[0].arn, null) +} + +output "node_iam_role_name" { + description = "IAM role name" + value = try(aws_iam_role.node[0].name, null) +} + +output "node_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = try(aws_iam_role.node[0].unique_id, null) +} + +################################################################################ +# IAM Instance Profile +################################################################################ + +output "node_iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = try(aws_iam_instance_profile.this[0].arn, null) +} + +output "node_iam_instance_profile_id" { + description = "Instance profile's ID" + value = try(aws_iam_instance_profile.this[0].id, null) +} + +output "node_iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = try(aws_iam_instance_profile.this[0].unique_id, null) +} diff --git a/modules/cluster/variables.tf b/modules/cluster/variables.tf index 495e5dda..a7e49761 100644 --- a/modules/cluster/variables.tf +++ b/modules/cluster/variables.tf @@ -16,6 +16,12 @@ variable "tags" { default = {} } +variable "disable_v7_default_name_description" { + description = "[DEPRECATED - will be removed in next breaking change] Determines whether to disable the default postfix added to resource names" + type = bool + default = false +} + ################################################################################ # Cluster ################################################################################ @@ -121,21 +127,103 @@ variable "cloudwatch_log_group_tags" { # Capacity Providers ################################################################################ -variable "autoscaling_capacity_providers" { - description = "Map of autoscaling capacity provider definitions to create for the cluster" +variable "cluster_capacity_providers_wait_duration" { + description = "Duration to wait after the ECS cluster has become active before attaching the cluster capacity providers" + type = string + default = "20s" +} + +variable "cluster_capacity_providers" { + description = "List of capacity provider names to associate with the ECS cluster. Note: any capacity providers created by this module will be automatically added" + type = list(string) + default = [] +} + +variable "capacity_providers" { + description = "Map of capacity provider definitions to create" type = map(object({ - auto_scaling_group_arn = string - managed_draining = optional(string, "ENABLED") - managed_scaling = optional(object({ - instance_warmup_period = optional(number) - maximum_scaling_step_size = optional(number) - minimum_scaling_step_size = optional(number) - status = optional(string) - target_capacity = optional(number) + auto_scaling_group_provider = optional(object({ + auto_scaling_group_arn = string + managed_draining = optional(string, "ENABLED") + managed_scaling = optional(object({ + instance_warmup_period = optional(number) + maximum_scaling_step_size = optional(number) + minimum_scaling_step_size = optional(number) + status = optional(string) + target_capacity = optional(number) + })) + managed_termination_protection = optional(string) + })) + managed_instances_provider = optional(object({ + infrastructure_role_arn = optional(string) + instance_launch_template = object({ + ec2_instance_profile_arn = optional(string) + instance_requirements = optional(object({ + accelerator_count = optional(object({ + max = optional(number) + min = optional(number) + })) + accelerator_manufacturers = optional(list(string)) + accelerator_names = optional(list(string)) + accelerator_total_memory_mib = optional(object({ + max = optional(number) + min = optional(number) + })) + accelerator_types = optional(list(string)) + allowed_instance_types = optional(list(string)) + bare_metal = optional(string) + baseline_ebs_bandwidth_mbps = optional(object({ + max = optional(number) + min = optional(number) + })) + burstable_performance = optional(string) + cpu_manufacturers = optional(list(string)) + excluded_instance_types = optional(list(string)) + instance_generations = optional(list(string)) + local_storage = optional(string) + local_storage_types = optional(list(string)) + max_spot_price_as_percentage_of_optimal_on_demand_price = optional(number) + memory_gib_per_vcpu = optional(object({ + max = optional(number) + min = optional(number) + })) + memory_mib = optional(object({ + max = optional(number) + min = optional(number) + })) + network_bandwidth_gbps = optional(object({ + max = optional(number) + min = optional(number) + })) + network_interface_count = optional(object({ + max = optional(number) + min = optional(number) + })) + on_demand_max_price_percentage_over_lowest_price = optional(number) + require_hibernate_support = optional(bool) + spot_max_price_percentage_over_lowest_price = optional(number) + total_local_storage_gb = optional(object({ + max = optional(number) + min = optional(number) + })) + vcpu_count = optional(object({ + max = optional(number) + min = optional(number) + })) + })) + monitoring = optional(string) + network_configuration = optional(object({ + security_groups = optional(list(string), []) + subnets = list(string) + })) + storage_configuration = optional(object({ + storage_size_gib = number + })) + }) + propagate_tags = optional(string, "CAPACITY_PROVIDER") })) - managed_termination_protection = optional(string) - name = optional(string) # Will fall back to use map key if not set - tags = optional(map(string), {}) + name = optional(string) # Will fall back to use map key if not set + tags = optional(map(string), {}) })) default = null } @@ -247,3 +335,285 @@ variable "task_exec_iam_statements" { })) default = null } + +############################################################################################ +# Infrastructure IAM role +############################################################################################ + +variable "create_infrastructure_iam_role" { + description = "Determines whether the ECS infrastructure IAM role should be created" + type = bool + default = true + nullable = false +} + +variable "infrastructure_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "infrastructure_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true + nullable = false +} + +variable "infrastructure_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "infrastructure_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "infrastructure_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 "infrastructure_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} + nullable = false +} + +################################################################################ +# Infrastructure IAM role policy +################################################################################ + +variable "infrastructure_iam_role_source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "infrastructure_iam_role_override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "infrastructure_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 = map(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string, "Allow") + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + variable = string + values = list(string) + }))) + })) + default = null +} + +################################################################################ +# Node IAM role & instance profile +################################################################################ + +variable "create_node_iam_instance_profile" { + description = "Determines whether an IAM instance profile is created or to use an existing IAM instance profile" + type = bool + default = true + nullable = false +} + +variable "node_iam_role_name" { + description = "Name to use on IAM role/instance profile created" + type = string + default = null +} + +variable "node_iam_role_use_name_prefix" { + description = "Determines whether the IAM role/instance profile name (`node_iam_role_name`) is used as a prefix" + type = bool + default = true + nullable = false +} + +variable "node_iam_role_path" { + description = "IAM role/instance profile path" + type = string + default = null +} + +variable "node_iam_role_description" { + description = "Description of the role" + type = string + default = "ECS Managed Instances node IAM role" + nullable = false +} + +variable "node_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 "node_iam_role_additional_policies" { + description = "Additional policies to be added to the IAM role" + type = map(string) + default = {} + nullable = false +} + +variable "node_iam_role_tags" { + description = "A map of additional tags to add to the IAM role/instance profile created" + type = map(string) + default = {} + nullable = false +} + +################################################################################ +# Node IAM role policy +################################################################################ + +variable "node_iam_role_source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "node_iam_role_override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "node_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 = map(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string, "Allow") + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + variable = string + values = list(string) + }))) + })) + default = null +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines if a security group is created" + type = bool + default = true + nullable = false +} + +variable "vpc_id" { + description = "The ID of the VPC where the security group will be created" + type = string + default = null +} + +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 + nullable = false +} + +variable "security_group_description" { + description = "Description of the security group created" + type = string + default = null +} + +variable "security_group_ingress_rules" { + description = "Security group ingress rules to add to the security group created" + type = map(object({ + name = optional(string) + + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(string) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })) + default = {} + nullable = false +} + +variable "security_group_egress_rules" { + description = "Security group egress rules to add to the security group created" + type = map(object({ + name = optional(string) + + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(string) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })) + default = { + all_ipv4 = { + cidr_ipv4 = "0.0.0.0/0" + description = "Allow all IPv4 traffic" + ip_protocol = "-1" + } + all_ipv6 = { + cidr_ipv6 = "::/0" + description = "Allow all IPv6 traffic" + ip_protocol = "-1" + } + } + nullable = false +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} + nullable = false +} diff --git a/modules/cluster/versions.tf b/modules/cluster/versions.tf index 70f23a44..bceb79a7 100644 --- a/modules/cluster/versions.tf +++ b/modules/cluster/versions.tf @@ -4,7 +4,11 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" + } + time = { + source = "hashicorp/time" + version = ">= 0.13" } } } diff --git a/modules/container-definition/README.md b/modules/container-definition/README.md index 2eab9fec..acbbc571 100644 --- a/modules/container-definition/README.md +++ b/modules/container-definition/README.md @@ -106,9 +106,11 @@ module "example_ecs_container_definition" { ## 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) +- [ECS cluster w/ integrated service(s)](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS container definition](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/container-definition) +- [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) +- [ECS cluster w/ ECS managed instances capacity provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/managed-instances) ## Requirements @@ -116,13 +118,13 @@ module "example_ecs_container_definition" { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 6.21 | +| [aws](#provider\_aws) | >= 6.23 | ## Modules diff --git a/modules/container-definition/versions.tf b/modules/container-definition/versions.tf index 70f23a44..7e5b918f 100644 --- a/modules/container-definition/versions.tf +++ b/modules/container-definition/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/modules/service/README.md b/modules/service/README.md index f0940eb1..c4ecd038 100644 --- a/modules/service/README.md +++ b/modules/service/README.md @@ -3,6 +3,7 @@ Configuration in this directory creates an Amazon ECS Service and associated resources. 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 @@ -160,9 +161,11 @@ module "ecs_service" { ## 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) +- [ECS cluster w/ integrated service(s)](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/complete) +- [ECS container definition](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/container-definition) +- [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) +- [ECS cluster w/ ECS managed instances capacity provider](https://github.com/terraform-aws-modules/terraform-aws-ecs/tree/master/examples/managed-instances) ## Requirements @@ -170,13 +173,13 @@ module "ecs_service" { | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.5.7 | -| [aws](#requirement\_aws) | >= 6.21 | +| [aws](#requirement\_aws) | >= 6.23 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 6.21 | +| [aws](#provider\_aws) | >= 6.23 | ## Modules @@ -204,6 +207,7 @@ module "ecs_service" { | [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_attachment.infrastructure_iam_role_ebs_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.infrastructure_iam_role_load_balancer_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.infrastructure_iam_role_vpc_lattice_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | 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 | @@ -251,11 +255,12 @@ module "ecs_service" { | [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 |
object({
enable = bool
rollback = bool
})
| `null` | no | -| [deployment\_configuration](#input\_deployment\_configuration) | Configuration block for deployment settings |
object({
strategy = optional(string)
bake_time_in_minutes = optional(string)
canary_configuration = optional(object({
canary_bake_time_in_minutes = optional(string)
canary_percent = optional(string)
}))
linear_configuration = optional(object({
step_bake_time_in_minutes = optional(string)
step_percent = optional(string)
}))
lifecycle_hook = optional(map(object({
hook_target_arn = string
role_arn = string
lifecycle_stages = list(string)
hook_details = optional(string)
})))
})
| `null` | no | +| [deployment\_configuration](#input\_deployment\_configuration) | Configuration block for deployment settings |
object({
strategy = optional(string)
bake_time_in_minutes = optional(string)
canary_configuration = optional(object({
canary_bake_time_in_minutes = optional(string)
canary_percent = optional(string)
}))
linear_configuration = optional(object({
step_bake_time_in_minutes = optional(string)
step_percent = optional(string)
}))
lifecycle_hook = optional(map(object({
hook_target_arn = string
role_arn = optional(string)
lifecycle_stages = list(string)
hook_details = optional(string)
})))
})
| `null` | no | | [deployment\_controller](#input\_deployment\_controller) | Configuration block for deployment controller configuration |
object({
type = optional(string)
})
| `null` | 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 | `number` | `1` | no | +| [disable\_v7\_default\_name\_description](#input\_disable\_v7\_default\_name\_description) | [DEPRECATED - will be removed in next breaking change] Determines whether to disable the default postfix added to resource names | `bool` | `false` | 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 | @@ -284,18 +289,18 @@ module "ecs_service" { | [infrastructure\_iam\_role\_use\_name\_prefix](#input\_infrastructure\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | 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 |
map(object({
container_name = string
container_port = number
elb_name = optional(string)
target_group_arn = optional(string)
advanced_configuration = optional(object({
alternate_target_group_arn = string
production_listener_rule = string
role_arn = string
test_listener_rule = optional(string)
}))
}))
| `null` | no | +| [load\_balancer](#input\_load\_balancer) | Configuration block for load balancers |
map(object({
container_name = string
container_port = number
elb_name = optional(string)
target_group_arn = optional(string)
advanced_configuration = optional(object({
alternate_target_group_arn = string
production_listener_rule = string # Should be optional but bug in provider
role_arn = optional(string)
test_listener_rule = optional(string)
}))
}))
| `null` | 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 | +| [name](#input\_name) | Name of the service (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | 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 |
map(object({
field = optional(string)
type = string
}))
| `null` | 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 |
list(object({
field = optional(string)
type = string
}))
| `null` | 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). This is set at the service, see `task_definition_placement_constraints` for setting at the task definition |
map(object({
expression = optional(string)
type = string
}))
| `null` | 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 |
object({
container_name = string
properties = optional(map(string))
type = optional(string)
})
| `null` | no | | [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | 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 | +| [requires\_compatibilities](#input\_requires\_compatibilities) | Set of launch types required by the task. The valid values are `EC2`, `FARGATE`, `EXTERNAL`, and `MANAGED_INSTANCES` | `list(string)` |
[
"FARGATE"
]
| no | | [runtime\_platform](#input\_runtime\_platform) | Configuration block for `runtime_platform` that containers in your task may use |
object({
cpu_architecture = optional(string, "X86_64")
operating_system_family = optional(string, "LINUX")
})
|
{
"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 |
object({
unit = optional(string)
value = optional(number)
})
| `null` | 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 | diff --git a/modules/service/main.tf b/modules/service/main.tf index b97f7042..a10798f4 100644 --- a/modules/service/main.tf +++ b/modules/service/main.tf @@ -60,7 +60,7 @@ resource "aws_ecs_service" "this" { content { base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = capacity_provider_strategy.value.weight + weight = coalesce(capacity_provider_strategy.value.weight, 1) } } @@ -105,7 +105,7 @@ resource "aws_ecs_service" "this" { content { hook_target_arn = lifecycle_hook.value.hook_target_arn - role_arn = lifecycle_hook.value.role_arn + role_arn = try(coalesce(lifecycle_hook.value.role_arn, local.infrastructure_iam_role_arn)) lifecycle_stages = lifecycle_hook.value.lifecycle_stages hook_details = lifecycle_hook.value.hook_details } @@ -148,7 +148,7 @@ resource "aws_ecs_service" "this" { content { alternate_target_group_arn = advanced_configuration.value.alternate_target_group_arn production_listener_rule = advanced_configuration.value.production_listener_rule - role_arn = advanced_configuration.value.role_arn + role_arn = try(coalesce(advanced_configuration.value.role_arn, local.infrastructure_iam_role_arn)) test_listener_rule = advanced_configuration.value.test_listener_rule } } @@ -169,7 +169,7 @@ resource "aws_ecs_service" "this" { } dynamic "ordered_placement_strategy" { - for_each = var.ordered_placement_strategy != null ? var.ordered_placement_strategy : {} + for_each = var.ordered_placement_strategy != null ? var.ordered_placement_strategy : [] content { field = ordered_placement_strategy.value.field @@ -364,6 +364,8 @@ resource "aws_ecs_service" "this" { depends_on = [ aws_iam_role_policy_attachment.service, aws_iam_role_policy_attachment.infrastructure_iam_role_ebs_policy, + aws_vpc_security_group_ingress_rule.this, + aws_vpc_security_group_egress_rule.this, ] lifecycle { @@ -401,7 +403,7 @@ resource "aws_ecs_service" "ignore_task_definition" { content { base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = capacity_provider_strategy.value.weight + weight = coalesce(capacity_provider_strategy.value.weight, 1) } } @@ -427,8 +429,8 @@ resource "aws_ecs_service" "ignore_task_definition" { for_each = deployment_configuration.value.linear_configuration != null ? [deployment_configuration.value.linear_configuration] : [] content { - step_percent = linear_configuration.value.step_percent step_bake_time_in_minutes = linear_configuration.value.step_bake_time_in_minutes + step_percent = linear_configuration.value.step_percent } } @@ -436,8 +438,8 @@ resource "aws_ecs_service" "ignore_task_definition" { for_each = deployment_configuration.value.canary_configuration != null ? [deployment_configuration.value.canary_configuration] : [] content { - canary_percent = canary_configuration.value.canary_percent canary_bake_time_in_minutes = canary_configuration.value.canary_bake_time_in_minutes + canary_percent = canary_configuration.value.canary_percent } } @@ -446,7 +448,7 @@ resource "aws_ecs_service" "ignore_task_definition" { content { hook_target_arn = lifecycle_hook.value.hook_target_arn - role_arn = lifecycle_hook.value.role_arn + role_arn = try(coalesce(lifecycle_hook.value.role_arn, local.infrastructure_iam_role_arn)) lifecycle_stages = lifecycle_hook.value.lifecycle_stages hook_details = lifecycle_hook.value.hook_details } @@ -489,7 +491,7 @@ resource "aws_ecs_service" "ignore_task_definition" { content { alternate_target_group_arn = advanced_configuration.value.alternate_target_group_arn production_listener_rule = advanced_configuration.value.production_listener_rule - role_arn = advanced_configuration.value.role_arn + role_arn = try(coalesce(advanced_configuration.value.role_arn, local.infrastructure_iam_role_arn)) test_listener_rule = advanced_configuration.value.test_listener_rule } } @@ -510,7 +512,7 @@ resource "aws_ecs_service" "ignore_task_definition" { } dynamic "ordered_placement_strategy" { - for_each = var.ordered_placement_strategy != null ? var.ordered_placement_strategy : {} + for_each = var.ordered_placement_strategy != null ? var.ordered_placement_strategy : [] content { field = ordered_placement_strategy.value.field @@ -639,6 +641,8 @@ resource "aws_ecs_service" "ignore_task_definition" { } } + sigint_rollback = try(var.deployment_configuration.strategy, null) == "BLUE_GREEN" ? var.sigint_rollback : null + tags = merge(var.tags, var.service_tags) task_definition = local.task_definition triggers = var.triggers @@ -647,7 +651,7 @@ resource "aws_ecs_service" "ignore_task_definition" { for_each = var.volume_configuration != null ? [var.volume_configuration] : [] content { - name = volume_configuration.value.name + name = try(volume_configuration.value.name, volume_configuration.key) dynamic "managed_ebs_volume" { for_each = [volume_configuration.value.managed_ebs_volume] @@ -703,6 +707,8 @@ resource "aws_ecs_service" "ignore_task_definition" { depends_on = [ aws_iam_role_policy_attachment.service, aws_iam_role_policy_attachment.infrastructure_iam_role_ebs_policy, + aws_vpc_security_group_ingress_rule.this, + aws_vpc_security_group_egress_rule.this, ] lifecycle { @@ -747,7 +753,7 @@ resource "aws_iam_role" "service" { 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 + description = try(coalesce(var.iam_role_description, (var.disable_v7_default_name_description ? null : "IAM role for ECS Service ${var.name}")), null) assume_role_policy = data.aws_iam_policy_document.service_assume[0].json permissions_boundary = var.iam_role_permissions_boundary @@ -1053,7 +1059,7 @@ resource "aws_ecs_task_definition" "this" { ################################################################################ locals { - task_exec_iam_role_name = coalesce(var.task_exec_iam_role_name, var.name, "NotProvided") + task_exec_iam_role_name = coalesce(var.task_exec_iam_role_name, "${var.name}${var.disable_v7_default_name_description ? "" : "-task-exec"}") 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 @@ -1207,7 +1213,7 @@ resource "aws_iam_role_policy_attachment" "task_exec" { ################################################################################ locals { - tasks_iam_role_name = coalesce(var.tasks_iam_role_name, var.name, "NotProvided") + tasks_iam_role_name = coalesce(var.tasks_iam_role_name, "${var.name}${var.disable_v7_default_name_description ? "" : "-tasks"}") create_tasks_iam_role = local.create_task_definition && var.create_tasks_iam_role } @@ -1244,7 +1250,7 @@ resource "aws_iam_role" "tasks" { 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 + description = try(coalesce(var.tasks_iam_role_description, (var.disable_v7_default_name_description ? null : "IAM role for ECS tasks in Service ${var.name}")), null) assume_role_policy = data.aws_iam_policy_document.tasks_assume[0].json permissions_boundary = var.tasks_iam_role_permissions_boundary @@ -1393,7 +1399,7 @@ resource "aws_ecs_task_set" "this" { content { base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = capacity_provider_strategy.value.weight + weight = coalesce(capacity_provider_strategy.value.weight, 1) } } @@ -1476,7 +1482,7 @@ resource "aws_ecs_task_set" "ignore_task_definition" { content { base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = capacity_provider_strategy.value.weight + weight = coalesce(capacity_provider_strategy.value.weight, 1) } } @@ -1864,7 +1870,7 @@ resource "aws_appautoscaling_scheduled_action" "this" { locals { create_security_group = var.create && var.create_security_group && var.network_mode == "awsvpc" - security_group_name = coalesce(var.security_group_name, var.name, "NotProvided") + security_group_name = coalesce(var.security_group_name, "${var.name}${var.disable_v7_default_name_description ? "" : "-service"}") } data "aws_subnet" "this" { @@ -1882,7 +1888,7 @@ resource "aws_security_group" "this" { 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 + description = try(coalesce(var.security_group_description, (var.disable_v7_default_name_description ? null : "Security group for ECS Service ${var.name}")), null) vpc_id = var.vpc_id != null ? var.vpc_id : data.aws_subnet.this[0].vpc_id tags = merge( @@ -1907,7 +1913,7 @@ resource "aws_vpc_security_group_ingress_rule" "this" { from_port = each.value.from_port ip_protocol = each.value.ip_protocol prefix_list_id = each.value.prefix_list_id - referenced_security_group_id = each.value.referenced_security_group_id + referenced_security_group_id = each.value.referenced_security_group_id == "self" ? aws_security_group.this[0].id : each.value.referenced_security_group_id security_group_id = aws_security_group.this[0].id tags = merge( var.tags, @@ -1929,7 +1935,7 @@ resource "aws_vpc_security_group_egress_rule" "this" { from_port = try(coalesce(each.value.from_port, each.value.to_port), null) ip_protocol = each.value.ip_protocol prefix_list_id = each.value.prefix_list_id - referenced_security_group_id = each.value.referenced_security_group_id + referenced_security_group_id = each.value.referenced_security_group_id == "self" ? aws_security_group.this[0].id : each.value.referenced_security_group_id security_group_id = aws_security_group.this[0].id tags = merge( var.tags, @@ -1946,10 +1952,10 @@ resource "aws_vpc_security_group_egress_rule" "this" { ############################################################################################ locals { - needs_infrastructure_iam_role = var.volume_configuration != null || var.vpc_lattice_configurations != null + needs_infrastructure_iam_role = var.volume_configuration != null || var.vpc_lattice_configurations != null || var.deployment_configuration != null create_infrastructure_iam_role = var.create && var.create_infrastructure_iam_role && local.needs_infrastructure_iam_role infrastructure_iam_role_arn = local.needs_infrastructure_iam_role ? try(aws_iam_role.infrastructure_iam_role[0].arn, var.infrastructure_iam_role_arn) : null - infrastructure_iam_role_name = coalesce(var.infrastructure_iam_role_name, var.name, "NotProvided") + infrastructure_iam_role_name = coalesce(var.infrastructure_iam_role_name, "${var.name}${var.disable_v7_default_name_description ? "" : "-infra"}") } data "aws_iam_policy_document" "infrastructure_iam_role" { @@ -1994,3 +2000,10 @@ resource "aws_iam_role_policy_attachment" "infrastructure_iam_role_vpc_lattice_p role = aws_iam_role.infrastructure_iam_role[0].name policy_arn = "arn:${local.partition}:iam::aws:policy/service-role/AmazonECSInfrastructureRolePolicyForVpcLattice" } + +resource "aws_iam_role_policy_attachment" "infrastructure_iam_role_load_balancer_policy" { + count = local.create_infrastructure_iam_role && var.deployment_configuration != null ? 1 : 0 + + role = aws_iam_role.infrastructure_iam_role[0].name + policy_arn = "arn:${local.partition}:iam::aws:policy/AmazonECSInfrastructureRolePolicyForLoadBalancers" +} diff --git a/modules/service/variables.tf b/modules/service/variables.tf index b42dfe40..2f711ed8 100644 --- a/modules/service/variables.tf +++ b/modules/service/variables.tf @@ -25,6 +25,12 @@ variable "tags" { nullable = false } +variable "disable_v7_default_name_description" { + description = "[DEPRECATED - will be removed in next breaking change] Determines whether to disable the default postfix added to resource names" + type = bool + default = false +} + ################################################################################ # Service ################################################################################ @@ -93,7 +99,7 @@ variable "deployment_configuration" { })) lifecycle_hook = optional(map(object({ hook_target_arn = string - role_arn = string + role_arn = optional(string) lifecycle_stages = list(string) hook_details = optional(string) }))) @@ -176,8 +182,8 @@ variable "load_balancer" { target_group_arn = optional(string) advanced_configuration = optional(object({ alternate_target_group_arn = string - production_listener_rule = string - role_arn = string + production_listener_rule = string # Should be optional but bug in provider + role_arn = optional(string) test_listener_rule = optional(string) })) })) @@ -187,7 +193,7 @@ variable "load_balancer" { variable "name" { description = "Name of the service (up to 255 letters, numbers, hyphens, and underscores)" type = string - default = null + default = "" } variable "assign_public_ip" { @@ -225,7 +231,7 @@ variable "vpc_id" { 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 = map(object({ + type = list(object({ field = optional(string) type = string })) @@ -683,7 +689,7 @@ variable "proxy_configuration" { } variable "requires_compatibilities" { - description = "Set of launch types required by the task. The valid values are `EC2` and `FARGATE`" + description = "Set of launch types required by the task. The valid values are `EC2`, `FARGATE`, `EXTERNAL`, and `MANAGED_INSTANCES`" type = list(string) default = ["FARGATE"] nullable = false @@ -1292,7 +1298,7 @@ variable "security_group_tags" { } ############################################################################################ -# ECS Infrastructure IAM role +# Infrastructure IAM role ############################################################################################ variable "create_infrastructure_iam_role" { diff --git a/modules/service/versions.tf b/modules/service/versions.tf index 70f23a44..7e5b918f 100644 --- a/modules/service/versions.tf +++ b/modules/service/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/outputs.tf b/outputs.tf index 6e462c6e..ecb6f90d 100644 --- a/outputs.tf +++ b/outputs.tf @@ -32,9 +32,9 @@ output "cluster_capacity_providers" { value = module.cluster.cluster_capacity_providers } -output "autoscaling_capacity_providers" { +output "capacity_providers" { description = "Map of autoscaling capacity providers created and their attributes" - value = module.cluster.autoscaling_capacity_providers + value = module.cluster.capacity_providers } output "task_exec_iam_role_name" { @@ -52,6 +52,51 @@ output "task_exec_iam_role_unique_id" { value = module.cluster.task_exec_iam_role_unique_id } +output "infrastructure_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.cluster.infrastructure_iam_role_arn +} + +output "infrastructure_iam_role_name" { + description = "IAM role name" + value = module.cluster.infrastructure_iam_role_name +} + +output "infrastructure_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.cluster.infrastructure_iam_role_unique_id +} + +output "node_iam_role_arn" { + description = "The Amazon Resource Name (ARN) specifying the IAM role" + value = module.cluster.node_iam_role_arn +} + +output "node_iam_role_name" { + description = "IAM role name" + value = module.cluster.node_iam_role_name +} + +output "node_iam_role_unique_id" { + description = "Stable and unique string identifying the IAM role" + value = module.cluster.node_iam_role_unique_id +} + +output "node_iam_instance_profile_arn" { + description = "ARN assigned by AWS to the instance profile" + value = module.cluster.node_iam_instance_profile_arn +} + +output "node_iam_instance_profile_id" { + description = "Instance profile's ID" + value = module.cluster.node_iam_instance_profile_id +} + +output "node_iam_instance_profile_unique" { + description = "Stable and unique string identifying the IAM instance profile" + value = module.cluster.node_iam_instance_profile_unique +} + ################################################################################ # Service(s) ################################################################################ diff --git a/variables.tf b/variables.tf index 5c354d6f..76b6e4e7 100644 --- a/variables.tf +++ b/variables.tf @@ -16,6 +16,12 @@ variable "tags" { default = {} } +variable "disable_v7_default_name_description" { + description = "[DEPRECATED - will be removed in v9.0] Determines whether to disable the default postfix added to resource names and descriptions added in v7.0" + type = bool + default = false +} + ################################################################################ # Cluster ################################################################################ @@ -126,21 +132,97 @@ variable "cloudwatch_log_group_tags" { # Capacity Providers ################################################################################ -variable "autoscaling_capacity_providers" { - description = "Map of autoscaling capacity provider definitions to create for the cluster" +variable "cluster_capacity_providers" { + description = "List of capacity provider names to associate with the ECS cluster. Note: any capacity providers created by this module will be automatically added" + type = list(string) + default = [] +} + +variable "capacity_providers" { + description = "Map of capacity provider definitions to create for the cluster" type = map(object({ - auto_scaling_group_arn = string - managed_draining = optional(string, "ENABLED") - managed_scaling = optional(object({ - instance_warmup_period = optional(number) - maximum_scaling_step_size = optional(number) - minimum_scaling_step_size = optional(number) - status = optional(string) - target_capacity = optional(number) + auto_scaling_group_provider = optional(object({ + auto_scaling_group_arn = string + managed_draining = optional(string, "ENABLED") + managed_scaling = optional(object({ + instance_warmup_period = optional(number) + maximum_scaling_step_size = optional(number) + minimum_scaling_step_size = optional(number) + status = optional(string) + target_capacity = optional(number) + })) + managed_termination_protection = optional(string) + })) + managed_instances_provider = optional(object({ + infrastructure_role_arn = optional(string) + instance_launch_template = object({ + ec2_instance_profile_arn = optional(string) + instance_requirements = optional(object({ + accelerator_count = optional(object({ + max = optional(number) + min = optional(number) + })) + accelerator_manufacturers = optional(list(string)) + accelerator_names = optional(list(string)) + accelerator_total_memory_mib = optional(object({ + max = optional(number) + min = optional(number) + })) + accelerator_types = optional(list(string)) + allowed_instance_types = optional(list(string)) + bare_metal = optional(string) + baseline_ebs_bandwidth_mbps = optional(object({ + max = optional(number) + min = optional(number) + })) + burstable_performance = optional(string) + cpu_manufacturers = optional(list(string)) + excluded_instance_types = optional(list(string)) + instance_generations = optional(list(string)) + local_storage = optional(string) + local_storage_types = optional(list(string)) + max_spot_price_as_percentage_of_optimal_on_demand_price = optional(number) + memory_gib_per_vcpu = optional(object({ + max = optional(number) + min = optional(number) + })) + memory_mib = optional(object({ + max = optional(number) + min = optional(number) + })) + network_bandwidth_gbps = optional(object({ + max = optional(number) + min = optional(number) + })) + network_interface_count = optional(object({ + max = optional(number) + min = optional(number) + })) + on_demand_max_price_percentage_over_lowest_price = optional(number) + require_hibernate_support = optional(bool) + spot_max_price_percentage_over_lowest_price = optional(number) + total_local_storage_gb = optional(object({ + max = optional(number) + min = optional(number) + })) + vcpu_count = optional(object({ + max = optional(number) + min = optional(number) + })) + })) + monitoring = optional(string) + network_configuration = optional(object({ + security_groups = optional(list(string), []) + subnets = list(string) + })) + storage_configuration = optional(object({ + storage_size_gib = number + })) + }) + propagate_tags = optional(string, "CAPACITY_PROVIDER") })) - managed_termination_protection = optional(string) - name = optional(string) # Will fall back to use map key if not set - tags = optional(map(string), {}) + name = optional(string) # Will fall back to use map key if not set + tags = optional(map(string), {}) })) default = null } @@ -252,6 +334,288 @@ variable "task_exec_iam_statements" { default = null } +############################################################################################ +# Infrastructure IAM role +############################################################################################ + +variable "create_infrastructure_iam_role" { + description = "Determines whether the ECS infrastructure IAM role should be created" + type = bool + default = true + nullable = false +} + +variable "infrastructure_iam_role_name" { + description = "Name to use on IAM role created" + type = string + default = null +} + +variable "infrastructure_iam_role_use_name_prefix" { + description = "Determines whether the IAM role name (`iam_role_name`) is used as a prefix" + type = bool + default = true + nullable = false +} + +variable "infrastructure_iam_role_path" { + description = "IAM role path" + type = string + default = null +} + +variable "infrastructure_iam_role_description" { + description = "Description of the role" + type = string + default = null +} + +variable "infrastructure_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 "infrastructure_iam_role_tags" { + description = "A map of additional tags to add to the IAM role created" + type = map(string) + default = {} + nullable = false +} + +################################################################################ +# Infrastructure IAM role policy +################################################################################ + +variable "infrastructure_iam_role_source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "infrastructure_iam_role_override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "infrastructure_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 = map(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string, "Allow") + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + variable = string + values = list(string) + }))) + })) + default = null +} + +################################################################################ +# Node IAM role & instance profile +################################################################################ + +variable "create_node_iam_instance_profile" { + description = "Determines whether an IAM instance profile is created or to use an existing IAM instance profile" + type = bool + default = true + nullable = false +} + +variable "node_iam_role_name" { + description = "Name to use on IAM role/instance profile created" + type = string + default = null +} + +variable "node_iam_role_use_name_prefix" { + description = "Determines whether the IAM role/instance profile name (`node_iam_role_name`) is used as a prefix" + type = bool + default = true + nullable = false +} + +variable "node_iam_role_path" { + description = "IAM role/instance profile path" + type = string + default = null +} + +variable "node_iam_role_description" { + description = "Description of the role" + type = string + default = "ECS Managed Instances node IAM role" + nullable = false +} + +variable "node_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 "node_iam_role_additional_policies" { + description = "Additional policies to be added to the IAM role" + type = map(string) + default = {} + nullable = false +} + +variable "node_iam_role_tags" { + description = "A map of additional tags to add to the IAM role/instance profile created" + type = map(string) + default = {} + nullable = false +} + +################################################################################ +# Node IAM role policy +################################################################################ + +variable "node_iam_role_source_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. Statements must have unique `sid`s" + type = list(string) + default = [] +} + +variable "node_iam_role_override_policy_documents" { + description = "List of IAM policy documents that are merged together into the exported document. In merging, statements with non-blank `sid`s will override statements with the same `sid`" + type = list(string) + default = [] +} + +variable "node_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 = map(object({ + sid = optional(string) + actions = optional(list(string)) + not_actions = optional(list(string)) + effect = optional(string, "Allow") + resources = optional(list(string)) + not_resources = optional(list(string)) + principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + not_principals = optional(list(object({ + type = string + identifiers = list(string) + }))) + condition = optional(list(object({ + test = string + variable = string + values = list(string) + }))) + })) + default = null +} + +################################################################################ +# Security Group +################################################################################ + +variable "create_security_group" { + description = "Determines if a security group is created" + type = bool + default = true + nullable = false +} + +variable "vpc_id" { + description = "The ID of the VPC where the security group will be created" + type = string + default = null +} + +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 + nullable = false +} + +variable "security_group_description" { + description = "Description of the security group created" + type = string + default = null +} + +variable "security_group_ingress_rules" { + description = "Security group ingress rules to add to the security group created" + type = map(object({ + name = optional(string) + + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(string) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })) + default = {} + nullable = false +} + +variable "security_group_egress_rules" { + description = "Security group egress rules to add to the security group created" + type = map(object({ + name = optional(string) + + cidr_ipv4 = optional(string) + cidr_ipv6 = optional(string) + description = optional(string) + from_port = optional(string) + ip_protocol = optional(string, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })) + default = { + all_ipv4 = { + cidr_ipv4 = "0.0.0.0/0" + description = "Allow all IPv4 traffic" + ip_protocol = "-1" + } + all_ipv6 = { + cidr_ipv6 = "::/0" + description = "Allow all IPv6 traffic" + ip_protocol = "-1" + } + } + nullable = false +} + +variable "security_group_tags" { + description = "A map of additional tags to add to the security group created" + type = map(string) + default = {} + nullable = false +} + ################################################################################ # Service(s) ################################################################################ @@ -293,7 +657,7 @@ variable "services" { })) lifecycle_hook = optional(map(object({ hook_target_arn = string - role_arn = string + role_arn = optional(string) lifecycle_stages = list(string) hook_details = optional(string) }))) @@ -317,8 +681,8 @@ variable "services" { target_group_arn = optional(string) advanced_configuration = optional(object({ alternate_target_group_arn = string - production_listener_rule = string - role_arn = string + production_listener_rule = string # Should be optional but bug in provider + role_arn = optional(string) test_listener_rule = optional(string) })) }))) @@ -326,7 +690,7 @@ variable "services" { assign_public_ip = optional(bool) security_group_ids = optional(list(string)) subnet_ids = optional(list(string)) - ordered_placement_strategy = optional(map(object({ + ordered_placement_strategy = optional(list(object({ field = optional(string) type = string }))) diff --git a/versions.tf b/versions.tf index 70f23a44..7e5b918f 100644 --- a/versions.tf +++ b/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/wrappers/cluster/main.tf b/wrappers/cluster/main.tf index bbd27c5d..40f33ef5 100644 --- a/wrappers/cluster/main.tf +++ b/wrappers/cluster/main.tf @@ -3,12 +3,14 @@ module "wrapper" { for_each = var.items - autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, null) - cloudwatch_log_group_class = try(each.value.cloudwatch_log_group_class, var.defaults.cloudwatch_log_group_class, null) - 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_name = try(each.value.cloudwatch_log_group_name, var.defaults.cloudwatch_log_group_name, null) - 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_tags = try(each.value.cloudwatch_log_group_tags, var.defaults.cloudwatch_log_group_tags, {}) + capacity_providers = try(each.value.capacity_providers, var.defaults.capacity_providers, null) + cloudwatch_log_group_class = try(each.value.cloudwatch_log_group_class, var.defaults.cloudwatch_log_group_class, null) + 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_name = try(each.value.cloudwatch_log_group_name, var.defaults.cloudwatch_log_group_name, null) + 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_tags = try(each.value.cloudwatch_log_group_tags, var.defaults.cloudwatch_log_group_tags, {}) + cluster_capacity_providers = try(each.value.cluster_capacity_providers, var.defaults.cluster_capacity_providers, []) + cluster_capacity_providers_wait_duration = try(each.value.cluster_capacity_providers_wait_duration, var.defaults.cluster_capacity_providers_wait_duration, "20s") configuration = try(each.value.configuration, var.defaults.configuration, { execute_command_configuration = { log_configuration = { @@ -16,14 +18,54 @@ module "wrapper" { } } }) - create = try(each.value.create, var.defaults.create, true) - create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) - create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) - create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) - default_capacity_provider_strategy = try(each.value.default_capacity_provider_strategy, var.defaults.default_capacity_provider_strategy, {}) - name = try(each.value.name, var.defaults.name, "") - region = try(each.value.region, var.defaults.region, null) - service_connect_defaults = try(each.value.service_connect_defaults, var.defaults.service_connect_defaults, null) + create = try(each.value.create, var.defaults.create, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + create_infrastructure_iam_role = try(each.value.create_infrastructure_iam_role, var.defaults.create_infrastructure_iam_role, true) + create_node_iam_instance_profile = try(each.value.create_node_iam_instance_profile, var.defaults.create_node_iam_instance_profile, true) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + default_capacity_provider_strategy = try(each.value.default_capacity_provider_strategy, var.defaults.default_capacity_provider_strategy, {}) + disable_v7_default_name_description = try(each.value.disable_v7_default_name_description, var.defaults.disable_v7_default_name_description, false) + infrastructure_iam_role_description = try(each.value.infrastructure_iam_role_description, var.defaults.infrastructure_iam_role_description, null) + infrastructure_iam_role_name = try(each.value.infrastructure_iam_role_name, var.defaults.infrastructure_iam_role_name, null) + infrastructure_iam_role_override_policy_documents = try(each.value.infrastructure_iam_role_override_policy_documents, var.defaults.infrastructure_iam_role_override_policy_documents, []) + infrastructure_iam_role_path = try(each.value.infrastructure_iam_role_path, var.defaults.infrastructure_iam_role_path, null) + infrastructure_iam_role_permissions_boundary = try(each.value.infrastructure_iam_role_permissions_boundary, var.defaults.infrastructure_iam_role_permissions_boundary, null) + infrastructure_iam_role_source_policy_documents = try(each.value.infrastructure_iam_role_source_policy_documents, var.defaults.infrastructure_iam_role_source_policy_documents, []) + infrastructure_iam_role_statements = try(each.value.infrastructure_iam_role_statements, var.defaults.infrastructure_iam_role_statements, null) + infrastructure_iam_role_tags = try(each.value.infrastructure_iam_role_tags, var.defaults.infrastructure_iam_role_tags, {}) + infrastructure_iam_role_use_name_prefix = try(each.value.infrastructure_iam_role_use_name_prefix, var.defaults.infrastructure_iam_role_use_name_prefix, true) + name = try(each.value.name, var.defaults.name, "") + node_iam_role_additional_policies = try(each.value.node_iam_role_additional_policies, var.defaults.node_iam_role_additional_policies, {}) + node_iam_role_description = try(each.value.node_iam_role_description, var.defaults.node_iam_role_description, "ECS Managed Instances node IAM role") + node_iam_role_name = try(each.value.node_iam_role_name, var.defaults.node_iam_role_name, null) + node_iam_role_override_policy_documents = try(each.value.node_iam_role_override_policy_documents, var.defaults.node_iam_role_override_policy_documents, []) + node_iam_role_path = try(each.value.node_iam_role_path, var.defaults.node_iam_role_path, null) + node_iam_role_permissions_boundary = try(each.value.node_iam_role_permissions_boundary, var.defaults.node_iam_role_permissions_boundary, null) + node_iam_role_source_policy_documents = try(each.value.node_iam_role_source_policy_documents, var.defaults.node_iam_role_source_policy_documents, []) + node_iam_role_statements = try(each.value.node_iam_role_statements, var.defaults.node_iam_role_statements, null) + node_iam_role_tags = try(each.value.node_iam_role_tags, var.defaults.node_iam_role_tags, {}) + node_iam_role_use_name_prefix = try(each.value.node_iam_role_use_name_prefix, var.defaults.node_iam_role_use_name_prefix, true) + region = try(each.value.region, var.defaults.region, null) + security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) + security_group_egress_rules = try(each.value.security_group_egress_rules, var.defaults.security_group_egress_rules, { + all_ipv4 = { + cidr_ipv4 = "0.0.0.0/0" + description = "Allow all IPv4 traffic" + ip_protocol = "-1" + } + all_ipv6 = { + cidr_ipv6 = "::/0" + description = "Allow all IPv6 traffic" + ip_protocol = "-1" + } + }) + security_group_ingress_rules = try(each.value.security_group_ingress_rules, var.defaults.security_group_ingress_rules, {}) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) + security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) + service_connect_defaults = try(each.value.service_connect_defaults, var.defaults.service_connect_defaults, null) setting = try(each.value.setting, var.defaults.setting, [ { name = "containerInsights" @@ -41,4 +83,5 @@ module "wrapper" { task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, null) task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, []) task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, []) + vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) } diff --git a/wrappers/cluster/versions.tf b/wrappers/cluster/versions.tf index 70f23a44..bceb79a7 100644 --- a/wrappers/cluster/versions.tf +++ b/wrappers/cluster/versions.tf @@ -4,7 +4,11 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" + } + time = { + source = "hashicorp/time" + version = ">= 0.13" } } } diff --git a/wrappers/container-definition/versions.tf b/wrappers/container-definition/versions.tf index 70f23a44..7e5b918f 100644 --- a/wrappers/container-definition/versions.tf +++ b/wrappers/container-definition/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 13659ca0..5068926a 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -3,12 +3,13 @@ module "wrapper" { for_each = var.items - autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, null) + capacity_providers = try(each.value.capacity_providers, var.defaults.capacity_providers, null) cloudwatch_log_group_class = try(each.value.cloudwatch_log_group_class, var.defaults.cloudwatch_log_group_class, null) 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_name = try(each.value.cloudwatch_log_group_name, var.defaults.cloudwatch_log_group_name, null) 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_tags = try(each.value.cloudwatch_log_group_tags, var.defaults.cloudwatch_log_group_tags, {}) + cluster_capacity_providers = try(each.value.cluster_capacity_providers, var.defaults.cluster_capacity_providers, []) cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, { execute_command_configuration = { log_configuration = { @@ -24,13 +25,53 @@ module "wrapper" { value = "enabled" } ]) - cluster_tags = try(each.value.cluster_tags, var.defaults.cluster_tags, {}) - create = try(each.value.create, var.defaults.create, true) - create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) - create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) - create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) - default_capacity_provider_strategy = try(each.value.default_capacity_provider_strategy, var.defaults.default_capacity_provider_strategy, null) - region = try(each.value.region, var.defaults.region, null) + cluster_tags = try(each.value.cluster_tags, var.defaults.cluster_tags, {}) + create = try(each.value.create, var.defaults.create, true) + create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) + create_infrastructure_iam_role = try(each.value.create_infrastructure_iam_role, var.defaults.create_infrastructure_iam_role, true) + create_node_iam_instance_profile = try(each.value.create_node_iam_instance_profile, var.defaults.create_node_iam_instance_profile, true) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, false) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + default_capacity_provider_strategy = try(each.value.default_capacity_provider_strategy, var.defaults.default_capacity_provider_strategy, null) + disable_v7_default_name_description = try(each.value.disable_v7_default_name_description, var.defaults.disable_v7_default_name_description, false) + infrastructure_iam_role_description = try(each.value.infrastructure_iam_role_description, var.defaults.infrastructure_iam_role_description, null) + infrastructure_iam_role_name = try(each.value.infrastructure_iam_role_name, var.defaults.infrastructure_iam_role_name, null) + infrastructure_iam_role_override_policy_documents = try(each.value.infrastructure_iam_role_override_policy_documents, var.defaults.infrastructure_iam_role_override_policy_documents, []) + infrastructure_iam_role_path = try(each.value.infrastructure_iam_role_path, var.defaults.infrastructure_iam_role_path, null) + infrastructure_iam_role_permissions_boundary = try(each.value.infrastructure_iam_role_permissions_boundary, var.defaults.infrastructure_iam_role_permissions_boundary, null) + infrastructure_iam_role_source_policy_documents = try(each.value.infrastructure_iam_role_source_policy_documents, var.defaults.infrastructure_iam_role_source_policy_documents, []) + infrastructure_iam_role_statements = try(each.value.infrastructure_iam_role_statements, var.defaults.infrastructure_iam_role_statements, null) + infrastructure_iam_role_tags = try(each.value.infrastructure_iam_role_tags, var.defaults.infrastructure_iam_role_tags, {}) + infrastructure_iam_role_use_name_prefix = try(each.value.infrastructure_iam_role_use_name_prefix, var.defaults.infrastructure_iam_role_use_name_prefix, true) + node_iam_role_additional_policies = try(each.value.node_iam_role_additional_policies, var.defaults.node_iam_role_additional_policies, {}) + node_iam_role_description = try(each.value.node_iam_role_description, var.defaults.node_iam_role_description, "ECS Managed Instances node IAM role") + node_iam_role_name = try(each.value.node_iam_role_name, var.defaults.node_iam_role_name, null) + node_iam_role_override_policy_documents = try(each.value.node_iam_role_override_policy_documents, var.defaults.node_iam_role_override_policy_documents, []) + node_iam_role_path = try(each.value.node_iam_role_path, var.defaults.node_iam_role_path, null) + node_iam_role_permissions_boundary = try(each.value.node_iam_role_permissions_boundary, var.defaults.node_iam_role_permissions_boundary, null) + node_iam_role_source_policy_documents = try(each.value.node_iam_role_source_policy_documents, var.defaults.node_iam_role_source_policy_documents, []) + node_iam_role_statements = try(each.value.node_iam_role_statements, var.defaults.node_iam_role_statements, null) + node_iam_role_tags = try(each.value.node_iam_role_tags, var.defaults.node_iam_role_tags, {}) + node_iam_role_use_name_prefix = try(each.value.node_iam_role_use_name_prefix, var.defaults.node_iam_role_use_name_prefix, true) + region = try(each.value.region, var.defaults.region, null) + security_group_description = try(each.value.security_group_description, var.defaults.security_group_description, null) + security_group_egress_rules = try(each.value.security_group_egress_rules, var.defaults.security_group_egress_rules, { + all_ipv4 = { + cidr_ipv4 = "0.0.0.0/0" + description = "Allow all IPv4 traffic" + ip_protocol = "-1" + } + all_ipv6 = { + cidr_ipv6 = "::/0" + description = "Allow all IPv6 traffic" + ip_protocol = "-1" + } + }) + security_group_ingress_rules = try(each.value.security_group_ingress_rules, var.defaults.security_group_ingress_rules, {}) + security_group_name = try(each.value.security_group_name, var.defaults.security_group_name, null) + security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) + security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) services = try(each.value.services, var.defaults.services, null) tags = try(each.value.tags, var.defaults.tags, {}) task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) @@ -43,4 +84,5 @@ module "wrapper" { task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, null) task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, []) task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, []) + vpc_id = try(each.value.vpc_id, var.defaults.vpc_id, null) } diff --git a/wrappers/service/main.tf b/wrappers/service/main.tf index 44ebf6be..a8b2d841 100644 --- a/wrappers/service/main.tf +++ b/wrappers/service/main.tf @@ -49,6 +49,7 @@ module "wrapper" { 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) + disable_v7_default_name_description = try(each.value.disable_v7_default_name_description, var.defaults.disable_v7_default_name_description, false) enable_autoscaling = try(each.value.enable_autoscaling, var.defaults.enable_autoscaling, true) 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) @@ -79,7 +80,7 @@ module "wrapper" { launch_type = try(each.value.launch_type, var.defaults.launch_type, "FARGATE") load_balancer = try(each.value.load_balancer, var.defaults.load_balancer, null) memory = try(each.value.memory, var.defaults.memory, 2048) - name = try(each.value.name, var.defaults.name, null) + name = try(each.value.name, var.defaults.name, "") network_mode = try(each.value.network_mode, var.defaults.network_mode, "awsvpc") ordered_placement_strategy = try(each.value.ordered_placement_strategy, var.defaults.ordered_placement_strategy, null) pid_mode = try(each.value.pid_mode, var.defaults.pid_mode, null) diff --git a/wrappers/service/versions.tf b/wrappers/service/versions.tf index 70f23a44..7e5b918f 100644 --- a/wrappers/service/versions.tf +++ b/wrappers/service/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } } diff --git a/wrappers/versions.tf b/wrappers/versions.tf index 70f23a44..7e5b918f 100644 --- a/wrappers/versions.tf +++ b/wrappers/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 6.21" + version = ">= 6.23" } } }