diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7900442e..b84d048d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.99.0 + rev: v1.99.4 hooks: - id: terraform_fmt - id: terraform_wrapper_module_for_each diff --git a/README.md b/README.md index 8e20fbc0..f5d0b920 100644 --- a/README.md +++ b/README.md @@ -33,16 +33,14 @@ module "ecs" { } } - fargate_capacity_providers = { + # Cluster capacity providers + default_capacity_provider_strategy = { FARGATE = { - default_capacity_provider_strategy = { - weight = 50 - } + weight = 50 + base = 20 } FARGATE_SPOT = { - default_capacity_provider_strategy = { - weight = 50 - } + weight = 50 } } @@ -59,10 +57,10 @@ module "ecs" { memory = 1024 essential = true image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" - firelens_configuration = { + firelensConfiguration = { type = "fluentbit" } - memory_reservation = 50 + memoryReservation = 50 } ecs-sample = { @@ -70,7 +68,7 @@ module "ecs" { memory = 1024 essential = true image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ + portMappings = [ { name = "ecs-sample" containerPort = 80 @@ -79,15 +77,15 @@ module "ecs" { ] # Example image used requires access to write to root filesystem - readonly_root_filesystem = false + readonlyRootFilesystem = false - dependencies = [{ + dependsOn = [{ containerName = "fluent-bit" condition = "START" }] enable_cloudwatch_logging = false - log_configuration = { + logConfiguration = { logDriver = "awsfirelens" options = { Name = "firehose" @@ -96,7 +94,7 @@ module "ecs" { log-driver-buffer-limit = "2097152" } } - memory_reservation = 100 + memoryReservation = 100 } } @@ -121,21 +119,18 @@ module "ecs" { } subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] - security_group_rules = { - alb_ingress_3000 = { - type = "ingress" - from_port = 80 - to_port = 80 - protocol = "tcp" - description = "Service port" - source_security_group_id = "sg-12345678" + security_group_ingress_rules = { + alb_3000 = { + description = "Service port" + from_port = local.container_port + ip_protocol = "tcp" + referenced_security_group_id = "sg-12345678" } - egress_all = { - type = "egress" - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" } } } @@ -159,8 +154,8 @@ module "ecs" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66.1 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers @@ -181,23 +176,24 @@ 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 | `any` | `{}` | no | +| [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 | +| [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\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | 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 | `map(string)` | `{}` | no | -| [cluster\_settings](#input\_cluster\_settings) | List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `any` |
[
{
"name": "containerInsights",
"value": "enabled"
}
]
| no | +| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace |
object({
namespace = string
})
| `null` | no | +| [cluster\_setting](#input\_cluster\_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 | | [cluster\_tags](#input\_cluster\_tags) | A map of additional tags to add to the cluster | `map(string)` | `{}` | no | | [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | | [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module for the cluster logs. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | | [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | | [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | -| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | -| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | -| [services](#input\_services) | Map of service definitions to create | `any` | `{}` | no | +| [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 | +| [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, true)
create_service = optional(bool, true)
tags = optional(map(string), {})

# Service
ignore_task_definition_changes = optional(bool, false)
alarms = optional(object({
alarm_names = list(string)
enable = optional(bool, true)
rollback = optional(bool, true)
}))
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_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, true)
enable_execute_command = optional(bool, false)
force_delete = optional(bool)
force_new_deployment = optional(bool, true)
health_check_grace_period_seconds = optional(number)
launch_type = optional(string, "FARGATE")
load_balancer = optional(map(object({
container_name = string
container_port = number
elb_name = optional(string)
target_group_arn = optional(string)
})))
name = optional(string) # Will fall back to use map key if not set
assign_public_ip = optional(bool, false)
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, true)
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
}))
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
}))
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, "TASK_DEFINITION")
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, true)
iam_role_arn = optional(string)
iam_role_name = optional(string)
iam_role_use_name_prefix = optional(bool, true)
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, true)
task_definition_arn = optional(string)
container_definitions = optional(map(object({
operating_system_family = optional(string, "LINUX")
tags = optional(map(string), {})

# Container definition
command = optional(list(string))
cpu = optional(number)
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, false)
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, 30)
retries = optional(number, 3)
startPeriod = optional(number)
timeout = optional(number, 5)
}))
hostname = optional(string)
image = optional(string)
interactive = optional(bool, false)
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, false)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
}),
# Default
{
initProcessEnabled = false
}
)
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, false)
pseudoTerminal = optional(bool, false)
readonlyRootFilesystem = optional(bool, true)
repositoryCredentials = optional(object({
credentialsParameter = optional(string)
}))
resourceRequirements = optional(list(object({
type = string
value = string
})))
restartPolicy = optional(object({
enabled = optional(bool, true)
ignoredExitCodes = optional(list(number))
restartAttemptPeriod = optional(number)
}),
# Default
{
enabled = true
}
)
secrets = optional(list(object({
name = string
valueFrom = string
})))
startTimeout = optional(number, 30)
stopTimeout = optional(number, 120)
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, "disabled")
volumesFrom = optional(list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
})), [])
workingDirectory = optional(string)

# Cloudwatch Log Group
service = optional(string, "")
enable_cloudwatch_logging = optional(bool, true)
create_cloudwatch_log_group = optional(bool, true)
cloudwatch_log_group_name = optional(string)
cloudwatch_log_group_use_name_prefix = optional(bool, false)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_retention_in_days = optional(number, 14)
cloudwatch_log_group_kms_key_id = optional(string)
})),
# Default
{}
)
container_definition_defaults = optional(object({
operating_system_family = optional(string, "LINUX")
tags = optional(map(string), {})

# Container definition
command = optional(list(string))
cpu = optional(number)
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, false)
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, 30)
retries = optional(number, 3)
startPeriod = optional(number)
timeout = optional(number, 5)
}))
hostname = optional(string)
image = optional(string)
interactive = optional(bool, false)
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, false)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
}),
# Default
{
initProcessEnabled = false
}
)
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, false)
pseudoTerminal = optional(bool, false)
readonlyRootFilesystem = optional(bool, true)
repositoryCredentials = optional(object({
credentialsParameter = optional(string)
}))
resourceRequirements = optional(list(object({
type = string
value = string
})))
restartPolicy = optional(object({
enabled = optional(bool, true)
ignoredExitCodes = optional(list(number))
restartAttemptPeriod = optional(number)
}),
# Default
{
enabled = true
}
)
secrets = optional(list(object({
name = string
valueFrom = string
})))
startTimeout = optional(number, 30)
stopTimeout = optional(number, 120)
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, "disabled")
volumesFrom = optional(list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
})), [])
workingDirectory = optional(string)

# Cloudwatch Log Group
service = optional(string, "")
enable_cloudwatch_logging = optional(bool, true)
create_cloudwatch_log_group = optional(bool, true)
cloudwatch_log_group_name = optional(string)
cloudwatch_log_group_use_name_prefix = optional(bool, false)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_retention_in_days = optional(number, 14)
cloudwatch_log_group_kms_key_id = optional(string)
}),
# Default
{}
)
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, "awsvpc")
pid_mode = optional(string)
proxy_configuration = optional(object({
container_name = string
properties = optional(map(string))
type = optional(string)
}))
requires_compatibilities = optional(list(string), ["FARGATE"])
runtime_platform = optional(object({
cpu_architecture = optional(string, "X86_64")
operating_system_family = optional(string, "LINUX")
}),
# Default
{
cpu_architecture = "X86_64"
operating_system_family = "LINUX"
}
)
skip_destroy = optional(bool)
task_definition_placement_constraints = optional(map(object({
expression = optional(string)
type = string
})))
track_latest = optional(bool, true)
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, true)
task_exec_iam_role_arn = optional(string)
task_exec_iam_role_name = optional(string)
task_exec_iam_role_use_name_prefix = optional(bool, true)
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, true)
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, true)
tasks_iam_role_arn = optional(string)
tasks_iam_role_name = optional(string)
tasks_iam_role_use_name_prefix = optional(bool, true)
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, true)
autoscaling_min_capacity = optional(number, 1)
autoscaling_max_capacity = optional(number, 10)
autoscaling_policies = optional(map(object({
name = optional(string) # Will fall back to the key name if not provided
policy_type = optional(string, "TargetTrackingScaling")
step_scaling_policy_configuration = optional(object({
adjustment_type = optional(string)
cooldown = optional(number)
metric_aggregation_type = optional(string)
min_adjustment_magnitude = optional(number)
step_adjustments = 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, 300)
scale_out_cooldown = optional(number, 60)
target_value = optional(number, 75)
}))
})),
# Default
{
cpu = {
policy_type = "TargetTrackingScaling"

target_tracking_scaling_policy_configuration = {
predefined_metric_specification = {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
}
}
memory = {
policy_type = "TargetTrackingScaling"

target_tracking_scaling_policy_configuration = {
predefined_metric_specification = {
predefined_metric_type = "ECSServiceAverageMemoryUtilization"
}
}
}
}
)
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)
})))
# Security Group
create_security_group = optional(bool, true)
security_group_name = optional(string)
security_group_use_name_prefix = optional(bool, true)
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, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
})),
# Default
{}
)
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, "tcp")
prefix_list_id = optional(string)
referenced_security_group_id = optional(string)
tags = optional(map(string), {})
to_port = optional(string)
})),
# Default
{}
)
security_group_tags = optional(map(string), {})
# ECS Infrastructure IAM Role
create_infrastructure_iam_role = optional(bool, true)
infrastructure_iam_role_arn = optional(string)
infrastructure_iam_role_name = optional(string)
infrastructure_iam_role_use_name_prefix = optional(bool, true)
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,9 +202,9 @@ No resources. | [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | | [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | | [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | -| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | -| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | +| [task\_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(map(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 | ## Outputs diff --git a/docs/README.md b/docs/README.md index 80f0b9f9..c4924782 100644 --- a/docs/README.md +++ b/docs/README.md @@ -321,20 +321,20 @@ The default behavior of the container definition module is to create the CloudWa # FluentBit sidecar is required for Firelens fluent-bit = { image = data.aws_ssm_parameter.fluentbit.value - firelens_configuration = { + firelensConfiguration = { type = "fluentbit" } # ... } default = { - dependencies = [{ + dependsOn = [{ containerName = "fluent-bit" condition = "START" }] enable_cloudwatch_logging = false - log_configuration = { + logConfiguration = { logDriver = "awsfirelens" options = { # ... diff --git a/UPGRADE-4.0.md b/docs/UPGRADE-4.0.md similarity index 100% rename from UPGRADE-4.0.md rename to docs/UPGRADE-4.0.md diff --git a/docs/UPGRADE-6.0.md b/docs/UPGRADE-6.0.md new file mode 100644 index 00000000..de01ef98 --- /dev/null +++ b/docs/UPGRADE-6.0.md @@ -0,0 +1,425 @@ +# Upgrade from v5.x to v6.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 + +- Terraform `v1.5.7` is now minimum supported version +- AWS provider `v6.0.0` is now minimum supported version +- The attributes used to construct the container definition(s) have been changed from HCL's norm of `snake_case` to `camelCase` to match the AWS API. There currently isn't a [resource nor data source for the container definition](https://github.com/hashicorp/terraform-provider-aws/issues/17988), so one is constructed entirely from HCL in the `container-definition` sub-module. This definition is then rendered as JSON when presented to the task definition (or task set) APIs. Previously, the variable names used were `snake_case` and then internally converted to `camelCase`. However, this does not allow for [using the `container-definition` sub-module on its own](https://github.com/terraform-aws-modules/terraform-aws-ecs/issues/147) due to the mismatch between casing. Its probably going to trip a few folks up, but hopefully we'll remove this for a data source in the future. +- `security_group_rules` has been split into `security_group_ingress_rules` and `security_group_egress_rules` to better match the AWS API and allow for more flexibility in defining security group rules. +- Default permissive permissions for SSM parameter ARNs and Secrets Manager secret ARNs have been removed throughout. While this made it easier for users since it "just worked", it was not secure and could lead to unexpected access to resources. Users should now explicitly define the permissions they need in their IAM policies. +- The "hack" put in place to track the task definition version when updating outside of the module has been removed. Instead, users should rely on the `track_latest` variable to ensure that the latest task definition is used when updating the service. Any issues with tracking the task definition version should be reported to the *ECS service team* as it is a limitation of the AWS ECS service/API and not the module itself. +- The inline policy for the Tasks role of the `service` sub-module has been replaced with a standalone IAM policy. In some organizations, inline policies are not allowed. +- The default for the `container-definition` `user` has been changed from `0` to `null`. + +## Additional changes + +### Added + +- Support for `region` parameter to specify the AWS region for the resources created if different from the provider region. +- Support for ECS infrastructure IAM role creation in the `service` sub-module. This role is used to manage ECS infrastructure resources https://docs.aws.amazon.com/AmazonECS/latest/developerguide/infrastructure_IAM_role.html + +### Modified + +- Variable definitions now contain detailed `object` types in place of the previously used any type. + +### Variable and output changes + +1. Removed variables: + + - `default_capacity_provider_use_fargate` + - `fargate_capacity_providers` + + - `cluster` sub-module + - `fargate_capacity_providers`; part of `default_capacity_provider_strategy` now + - `default_capacity_provider_use_fargate` + + - `container-definition` sub-module + - None + + - `service` sub-module + - `inference_accelerator` + + +2. Renamed variables: + + - `cluster_settings` -> `cluster_setting` + + - `cluster` sub-module + - `cluster_configuration` - `configuration` + - `cluster_settings` - `setting` + - `cluster_service_connect_defaults` - `service_connect_defaults` + + - `container-definition` sub-module + - `dependencies` - `dependsOn` + - `disable_networking` - `disableNetworking` + - `dns_search_domains` - `dnsSearchDomains` + - `dns_servers` - `dnsServers` + - `docker_labels` - `dockerLabels` + - `docker_security_options` - `dockerSecurityOptions` + - `environment_files` - `environmentFiles` + - `extra_hosts` - `extraHosts` + - `firelens_configuration` - `firelensConfiguration` + - `health_check` - `healthCheck` + - `linux_parameters` - `linuxParameters` + - `log_configuration` - `logConfiguration` + - `memory_reservation` - `memoryReservation` + - `mount_points` - `mountPoints` + - `port_mappings` - `portMappings` + - `psuedo_terminal` - `pseudoTerminal` + - `readonly_root_filesystem` - `readonlyRootFilesystem` + - `repository_credentials` - `repositoryCredentials` + - `start_timeout` - `startTimeout` + - `system_controls` - `systemControls` + - `volumes_from` - `volumesFrom` + - `working_directory` - `workingDirectory` + + - `service` sub-module + - None + +3. Added variables: + + - `cloudwatch_log_group_class` + - `default_capacity_provider_strategy` + + - `cluster` sub-module + - `cloudwatch_log_group_class` + - `default_capacity_provider_strategy` - replaces `fargate_capacity_providers` and `default_capacity_provider_use_fargate` functionality + + - `container-definition` sub-module + - `log_group_class` + - `restartPolicy` - defaults to `enabled = true` + - `versionConsistency` - defaults to `"disabled"` https://github.com/aws/containers-roadmap/issues/2394 + + - `service` sub-module + - `availability_zone_rebalancing` + - `volume_configuration` + - `vpc_lattice_configurations` + - `enable_fault_injection` + - `track_latest` + - `create_infrastructure_iam_role` + - `infrastructure_iam_role_arn` + - `infrastructure_iam_role_name` + - `infrastructure_iam_role_use_name_prefix` + - `infrastructure_iam_role_path` + - `infrastructure_iam_role_description` + - `infrastructure_iam_role_permissions_boundary` + - `infrastructure_iam_role_tags` + +4. Removed outputs: + + - `cluster` sub-module + - None + - `container-definition` sub-module + - None + - `service` sub-module + - `task_definition_family_revision` + +5. Renamed outputs: + + - `cluster` sub-module + - None + - `container-definition` sub-module + - None + - `service` sub-module + - None + +6. Added outputs: + + - `cluster` sub-module + - None + - `container-definition` sub-module + - None + - `service` sub-module + - `infrastructure_iam_role_arn` + - `infrastructure_iam_role_name` + +## Upgrade Migrations + +### Before 5.x Example + +#### Cluster Sub-Module + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "~> 5.0" + + # Truncated for brevity ... + + # Capacity provider + fargate_capacity_providers = { + FARGATE = { + default_capacity_provider_strategy = { + weight = 50 + base = 20 + } + } + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 50 + } + } + } +} +``` + +#### Service Sub-Module + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "~> 5.0" + + # Truncated for brevity ... + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable" + firelens_configuration = { + type = "fluentbit" + } + memory_reservation = 50 + user = "0" + } + + ecsdemo-frontend = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + port_mappings = [ + { + name = ecsdemo-frontend + containerPort = 3000 + hostPort = 3000 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonly_root_filesystem = false + + dependencies = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + log_configuration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = local.region + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + + linux_parameters = { + capabilities = { + add = [] + drop = [ + "NET_RAW" + ] + } + } + + # Not required for fluent-bit, just an example + volumes_from = [{ + sourceContainer = "fluent-bit" + readOnly = false + }] + + memory_reservation = 100 + } + } + + service_connect_configuration = { + namespace = aws_service_discovery_http_namespace.this.arn + service = { + client_alias = { + port = 3000 + dns_name = "ecsdemo-frontend" + } + port_name = "ecsdemo-frontend" + discovery_name = "ecsdemo-frontend" + } + } + + security_group_rules = { + alb_ingress_3000 = { + type = "ingress" + from_port = 3000 + description = "Service port" + source_security_group_id = module.alb.security_group_id + } + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } +} +``` + +### After 6.x Example + +#### Cluster Sub-Module + +```hcl +module "ecs_cluster" { + source = "terraform-aws-modules/ecs/aws//modules/cluster" + version = "~> 6.0" + + # Truncated for brevity ... + + # Cluster capacity providers + default_capacity_provider_strategy = { + FARGATE = { + weight = 50 + base = 20 + } + FARGATE_SPOT = { + weight = 50 + } + } +} +``` + +#### Service Sub-Module + +```hcl +module "ecs_service" { + source = "terraform-aws-modules/ecs/aws//modules/service" + version = "~> 6.0" + + # Truncated for brevity ... + + # Container definition(s) + container_definitions = { + + fluent-bit = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-observability/aws-for-fluent-bit:stable" + firelensConfiguration = { + type = "fluentbit" + } + memoryReservation = 50 + user = "0" + } + + ecsdemo-frontend = { + cpu = 512 + memory = 1024 + essential = true + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + portMappings = [ + { + name = "ecsdemo-frontend" + containerPort = 3000 + hostPort = 3000 + protocol = "tcp" + } + ] + + # Example image used requires access to write to root filesystem + readonlyRootFilesystem = false + + dependsOn = [{ + containerName = "fluent-bit" + condition = "START" + }] + + enable_cloudwatch_logging = false + logConfiguration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = local.region + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + + linuxParameters = { + capabilities = { + add = [] + drop = [ + "NET_RAW" + ] + } + } + + restartPolicy = { + enabled = true + ignoredExitCodes = [1] + restartAttemptPeriod = 60 + } + + # Not required for fluent-bit, just an example + volumesFrom = [{ + sourceContainer = "fluent-bit" + readOnly = false + }] + + memoryReservation = 100 + } + } + + service_connect_configuration = { + namespace = aws_service_discovery_http_namespace.this.arn + service = [ + { + client_alias = { + port = 3000 + dns_name = "ecsdemo-frontend" + } + port_name = "ecsdemo-frontend" + discovery_name = "ecsdemo-frontend" + } + ] + } + + security_group_ingress_rules = { + alb_3000 = { + description = "Service port" + from_port = 3000 + referenced_security_group_id = module.alb.security_group_id + } + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" + } + } +} +``` + +### State Changes + +#### Service Sub-Module + +Due to the change from `aws_security_group_rule` to `aws_vpc_security_group_ingress_rule` and `aws_vpc_security_group_egress_rule`, the following reference state changes are required to maintain the current security group rules. (Note: these are different resources so they cannot be moved with `terraform mv ...`) + +```sh +terraform state rm 'module.ecs_service.aws_security_group_rule.this["alb_ingress_3000"]' +terraform state import 'module.ecs_service.aws_vpc_security_group_ingress_rule.this["alb_3000"]' 'sg-xxx' + +terraform state rm 'module.ecs_service.aws_security_group_rule.this["egress_all"]' +terraform state import 'module.ecs_service.aws_vpc_security_group_egress_rule.this["all"]' 'sg-xxx' +``` + +The inline tasks `aws_iam_role_policy` cannot be moved or imported into a standalone `aws_iam_policy`. It must be re-created. diff --git a/examples/complete/README.md b/examples/complete/README.md index 9d20e63d..090e1c34 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -26,25 +26,27 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66.1 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66.1 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 | +| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 9.0 | +| [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 5.0 | | [ecs](#module\_ecs) | ../../ | n/a | | [ecs\_cluster\_disabled](#module\_ecs\_cluster\_disabled) | ../../modules/cluster | n/a | | [ecs\_disabled](#module\_ecs\_disabled) | ../../ | n/a | | [service\_disabled](#module\_service\_disabled) | ../../modules/service | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | ## Resources @@ -52,6 +54,7 @@ Note that this example may create resources which will incur monetary charges on |------|------| | [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.ecs_optimized_ami](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | | [aws_ssm_parameter.fluentbit](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ssm_parameter) | data source | ## Inputs diff --git a/examples/complete/main.tf b/examples/complete/main.tf index b7353bbd..bdd6c820 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -30,17 +30,32 @@ module "ecs" { cluster_name = local.name - # Capacity provider - fargate_capacity_providers = { + # Cluster capacity providers + default_capacity_provider_strategy = { FARGATE = { - default_capacity_provider_strategy = { - weight = 50 - base = 20 - } + weight = 50 + base = 20 } FARGATE_SPOT = { - default_capacity_provider_strategy = { - weight = 50 + weight = 50 + } + ASG = { + weight = 50 + base = 20 + } + } + + 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 } } } @@ -58,10 +73,10 @@ module "ecs" { memory = 1024 essential = true image = nonsensitive(data.aws_ssm_parameter.fluentbit.value) - firelens_configuration = { + firelensConfiguration = { type = "fluentbit" } - memory_reservation = 50 + memoryReservation = 50 } (local.container_name) = { @@ -70,11 +85,11 @@ module "ecs" { essential = true image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - health_check = { + healthCheck = { command = ["CMD-SHELL", "curl -f http://localhost:${local.container_port}/health || exit 1"] } - port_mappings = [ + portMappings = [ { name = local.container_name containerPort = local.container_port @@ -84,15 +99,15 @@ module "ecs" { ] # Example image used requires access to write to root filesystem - readonly_root_filesystem = false + readonlyRootFilesystem = false - dependencies = [{ + dependsOn = [{ containerName = "fluent-bit" condition = "START" }] enable_cloudwatch_logging = false - log_configuration = { + logConfiguration = { logDriver = "awsfirelens" options = { Name = "firehose" @@ -101,20 +116,34 @@ module "ecs" { log-driver-buffer-limit = "2097152" } } - memory_reservation = 100 + memoryReservation = 100 + + restartPolicy = { + enabled = true + ignoredExitCodes = [1] + restartAttemptPeriod = 60 + } } } service_connect_configuration = { namespace = aws_service_discovery_http_namespace.this.arn - service = { - client_alias = { - port = local.container_port - dns_name = local.container_name + service = [ + { + client_alias = { + port = local.container_port + dns_name = local.container_name + } + + timeout = { + idle_timeout_seconds = 20 + per_request_timeout_seconds = 30 + } + + port_name = local.container_name + discovery_name = local.container_name } - port_name = local.container_name - discovery_name = local.container_name - } + ] } load_balancer = { @@ -137,22 +166,20 @@ module "ecs" { } ] - subnet_ids = module.vpc.private_subnets - security_group_rules = { - alb_ingress_3000 = { - type = "ingress" - from_port = local.container_port - to_port = local.container_port - protocol = "tcp" - description = "Service port" - source_security_group_id = module.alb.security_group_id + subnet_ids = module.vpc.private_subnets + availability_zone_rebalancing = "ENABLED" + security_group_ingress_rules = { + alb_3000 = { + from_port = local.container_port + description = "Service port" + referenced_security_group_id = module.alb.security_group_id } - egress_all = { - type = "egress" - from_port = 0 + } + security_group_egress_rules = { + all = { + cidr_ipv4 = "0.0.0.0/0" to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + ip_protocol = "-1" } } } @@ -183,6 +210,11 @@ module "service_disabled" { # Supporting Resources ################################################################################ +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux +data "aws_ssm_parameter" "ecs_optimized_ami" { + name = "/aws/service/ecs/optimized-ami/amazon-linux-2023/recommended" +} + data "aws_ssm_parameter" "fluentbit" { name = "/aws/service/aws-for-fluent-bit/stable" } @@ -263,9 +295,78 @@ module "alb" { tags = local.tags } +module "autoscaling" { + source = "terraform-aws-modules/autoscaling/aws" + version = "~> 9.0" + + name = local.name + + image_id = jsondecode(data.aws_ssm_parameter.ecs_optimized_ami.value)["image_id"] + instance_type = "t3.large" + + security_groups = [module.autoscaling_sg.security_group_id] + user_data = base64encode(<<-EOT + #!/bin/bash + + cat <<'EOF' >> /etc/ecs/ecs.config + ECS_CLUSTER=${local.name} + ECS_LOGLEVEL=debug + ECS_CONTAINER_INSTANCE_TAGS=${jsonencode(local.tags)} + ECS_ENABLE_TASK_IAM_ROLE=true + EOF + EOT + ) + ignore_desired_capacity_changes = true + + create_iam_instance_profile = true + iam_role_name = local.name + iam_role_description = "ECS role for ${local.name}" + iam_role_policies = { + AmazonEC2ContainerServiceforEC2Role = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role" + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + + vpc_zone_identifier = module.vpc.private_subnets + health_check_type = "EC2" + min_size = 1 + max_size = 5 + desired_capacity = 2 + + # https://github.com/hashicorp/terraform-provider-aws/issues/12582 + autoscaling_group_tags = { + AmazonECSManaged = true + } + + # Required for managed_termination_protection = "ENABLED" + protect_from_scale_in = true + + tags = local.tags +} + +module "autoscaling_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = local.name + description = "Autoscaling group security group" + vpc_id = module.vpc.vpc_id + + computed_ingress_with_source_security_group_id = [ + { + rule = "http-80-tcp" + source_security_group_id = module.alb.security_group_id + } + ] + number_of_computed_ingress_with_source_security_group_id = 1 + + egress_rules = ["all-all"] + + tags = local.tags +} + module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" + version = "~> 6.0" name = local.name cidr = local.vpc_cidr diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 682191e7..db13b0a8 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/examples/container-definition/README.md b/examples/container-definition/README.md new file mode 100644 index 00000000..46b7bd3c --- /dev/null +++ b/examples/container-definition/README.md @@ -0,0 +1,62 @@ +# ECS Container Definition + +Configuration in this directory creates: + +- ECS container definition + +## Usage + +To run this example you need to execute: + +```bash +$ 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. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | +| [local](#requirement\_local) | >= 2.5 | + +## Providers + +| Name | Version | +|------|---------| +| [local](#provider\_local) | >= 2.5 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [ecs\_container\_definition](#module\_ecs\_container\_definition) | ../../modules/container-definition | n/a | + +## Resources + +| Name | Type | +|------|------| +| [local_file.container_definition_json](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource | + +## Inputs + +No inputs. + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of CloudWatch log group created | +| [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of CloudWatch log group created | +| [container\_definition](#output\_container\_definition) | Container definition | +| [container\_definition\_json](#output\_container\_definition\_json) | Container definition | + + +## License + +Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE). diff --git a/examples/container-definition/definition.json b/examples/container-definition/definition.json new file mode 100755 index 00000000..b1c6aab0 --- /dev/null +++ b/examples/container-definition/definition.json @@ -0,0 +1,157 @@ +{ + "command": [ + "/usr/sbin/apache2", + "-D", + "FOREGROUND" + ], + "cpu": 512, + "dependsOn": [ + { + "condition": "START", + "containerName": "fluent-bit" + } + ], + "disableNetworking": false, + "dnsSearchDomains": [ + "mydns.on.my.network" + ], + "dnsServers": [ + "172.20.0.11" + ], + "dockerLabels": { + "com.example.label": "value" + }, + "dockerSecurityOptions": [ + "no-new-privileges" + ], + "entrypoint": [ + "/usr/sbin/apache2", + "-D", + "FOREGROUND" + ], + "environment": [ + { + "name": "ENV_VAR_1", + "value": "value1" + }, + { + "name": "ENV_VAR_2", + "value": "value2" + } + ], + "environmentFiles": [ + { + "type": "s3", + "value": "s3://my-bucket/my-env-file.env" + } + ], + "essential": true, + "firelensConfiguration": { + "type": "fluentbit" + }, + "healthCheck": { + "command": [ + "CMD-SHELL", + "curl -f http://localhost:3000/health || exit 1" + ], + "interval": 30, + "retries": 3, + "timeout": 5 + }, + "image": "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50", + "interactive": false, + "linuxParameters": { + "capabilities": { + "add": [], + "drop": [ + "NET_RAW" + ] + }, + "initProcessEnabled": false + }, + "logConfiguration": { + "logDriver": "awsfirelens", + "options": { + "Name": "firehose", + "delivery_stream": "my-stream", + "log-driver-buffer-limit": "2097152", + "region": "eu-west-1" + } + }, + "memory": 1024, + "memoryReservation": 100, + "mountPoints": [ + { + "containerPath": "/var/www/my-vol", + "readOnly": null, + "sourceVolume": "my-vol" + }, + { + "containerPath": "/ebs/data", + "readOnly": null, + "sourceVolume": "ebs-volume" + } + ], + "name": "ex-container-definition", + "portMappings": [ + { + "containerPort": 3000, + "hostPort": 3000, + "name": "ecsdemo-frontend", + "protocol": "tcp" + } + ], + "privileged": false, + "pseudoTerminal": false, + "readonlyRootFilesystem": true, + "repositoryCredentials": { + "credentialsParameter": "arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-repo-creds" + }, + "resourceRequirements": [ + { + "type": "GPU", + "value": "1" + } + ], + "restartPolicy": { + "enabled": true, + "ignoredExitCodes": [ + 1 + ], + "restartAttemptPeriod": 60 + }, + "secrets": [ + { + "name": "SECRET_ENV_VAR", + "valueFrom": "arn:aws:ssm:eu-west-1:123456789012:parameter/my-secret-env-var" + } + ], + "startTimeout": 30, + "stopTimeout": 120, + "systemControls": [ + { + "namespace": "network", + "value": "ipv6" + }, + { + "namespace": "net.core.somaxconn", + "value": "1024" + } + ], + "ulimits": [ + { + "hardLimit": 2048, + "name": "nofile", + "softLimit": 1024 + } + ], + "user": "65534", + "versionConsistency": "disabled", + "volumesFrom": [ + { + "readOnly": false, + "sourceContainer": "fluent-bit" + } + ], + "workingDirectory": "/var/www/html" +} diff --git a/examples/container-definition/main.tf b/examples/container-definition/main.tf new file mode 100644 index 00000000..cc1e8af4 --- /dev/null +++ b/examples/container-definition/main.tf @@ -0,0 +1,157 @@ +provider "aws" { + region = local.region +} + +locals { + region = "eu-west-1" + name = "ex-${basename(path.cwd)}" + + container_name = "ecsdemo-frontend" + container_port = 3000 + + tags = { + Name = local.name + Example = local.name + Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs" + } +} + +################################################################################ +# Service +################################################################################ + +module "ecs_container_definition" { + source = "../../modules/container-definition" + + name = local.name + + command = ["/usr/sbin/apache2", "-D", "FOREGROUND"] + cpu = 512 + + dependsOn = [{ + containerName = "fluent-bit" + condition = "START" + }] + disableNetworking = false + dnsSearchDomains = ["mydns.on.my.network"] + dnsServers = ["172.20.0.11"] + dockerLabels = { + "com.example.label" = "value" + } + dockerSecurityOptions = ["no-new-privileges"] + entrypoint = ["/usr/sbin/apache2", "-D", "FOREGROUND"] + environment = [ + { + name = "ENV_VAR_1" + value = "value1" + }, + { + name = "ENV_VAR_2" + value = "value2" + } + ] + environmentFiles = [ + { + type = "s3" + value = "s3://my-bucket/my-env-file.env" + } + ] + essential = true + firelensConfiguration = { + type = "fluentbit" + } + healthCheck = { + command = ["CMD-SHELL", "curl -f http://localhost:${local.container_port}/health || exit 1"] + } + image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" + interactive = false + linuxParameters = { + capabilities = { + add = [] + drop = [ + "NET_RAW" + ] + } + } + enable_cloudwatch_logging = false + logConfiguration = { + logDriver = "awsfirelens" + options = { + Name = "firehose" + region = local.region + delivery_stream = "my-stream" + log-driver-buffer-limit = "2097152" + } + } + memory = 1024 + memoryReservation = 100 + mountPoints = [ + { + sourceVolume = "my-vol", + containerPath = "/var/www/my-vol" + }, + { + sourceVolume = "ebs-volume" + containerPath = "/ebs/data" + } + ] + portMappings = [ + { + name = local.container_name + containerPort = local.container_port + hostPort = local.container_port + protocol = "tcp" + } + ] + privileged = false + pseudoTerminal = false + restartPolicy = { + enabled = true + ignoredExitCodes = [1] + restartAttemptPeriod = 60 + } + readonlyRootFilesystem = true + repositoryCredentials = { + credentialsParameter = "arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-repo-creds" + } + resourceRequirements = [ + { + type = "GPU" + value = "1" + }, + ] + secrets = [ + { + name = "SECRET_ENV_VAR" + valueFrom = "arn:aws:ssm:eu-west-1:123456789012:parameter/my-secret-env-var" + } + ] + startTimeout = 30 + stopTimeout = 120 + systemControls = [ + { + namespace = "network" + value = "ipv6" + }, + { + namespace = "net.core.somaxconn" + value = "1024" + } + ] + ulimits = [ + { + name = "nofile" + softLimit = 1024 + hardLimit = 2048 + } + ] + user = "65534" + versionConsistency = "disabled" + volumesFrom = [{ + sourceContainer = "fluent-bit" + readOnly = false + }] + workingDirectory = "/var/www/html" + + tags = local.tags +} diff --git a/examples/container-definition/outputs.tf b/examples/container-definition/outputs.tf new file mode 100644 index 00000000..dbf1f052 --- /dev/null +++ b/examples/container-definition/outputs.tf @@ -0,0 +1,32 @@ +################################################################################ +# Container Definition +################################################################################ + +output "container_definition" { + description = "Container definition" + value = module.ecs_container_definition.container_definition +} + +output "container_definition_json" { + description = "Container definition" + value = module.ecs_container_definition.container_definition_json +} + +resource "local_file" "container_definition_json" { + content = module.ecs_container_definition.container_definition_json + filename = "${path.module}/definition.json" +} + +################################################################################ +# CloudWatch Log Group +################################################################################ + +output "cloudwatch_log_group_name" { + description = "Name of CloudWatch log group created" + value = module.ecs_container_definition.cloudwatch_log_group_name +} + +output "cloudwatch_log_group_arn" { + description = "ARN of CloudWatch log group created" + value = module.ecs_container_definition.cloudwatch_log_group_arn +} diff --git a/examples/container-definition/variables.tf b/examples/container-definition/variables.tf new file mode 100644 index 00000000..e69de29b diff --git a/examples/container-definition/versions.tf b/examples/container-definition/versions.tf new file mode 100644 index 00000000..f5b245bc --- /dev/null +++ b/examples/container-definition/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + local = { + source = "hashicorp/local" + version = ">= 2.5" + } + } +} diff --git a/examples/ec2-autoscaling/README.md b/examples/ec2-autoscaling/README.md index ff7a998d..c08d6c19 100644 --- a/examples/ec2-autoscaling/README.md +++ b/examples/ec2-autoscaling/README.md @@ -26,25 +26,25 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66.1 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66.1 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [alb](#module\_alb) | terraform-aws-modules/alb/aws | ~> 9.0 | -| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 6.5 | +| [autoscaling](#module\_autoscaling) | terraform-aws-modules/autoscaling/aws | ~> 9.0 | | [autoscaling\_sg](#module\_autoscaling\_sg) | terraform-aws-modules/security-group/aws | ~> 5.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 | ~> 5.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | ## Resources @@ -74,6 +74,8 @@ No inputs. | [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\_infrastructure\_iam\_role\_arn](#output\_service\_infrastructure\_iam\_role\_arn) | Infrastructure IAM role ARN | +| [service\_infrastructure\_iam\_role\_name](#output\_service\_infrastructure\_iam\_role\_name) | Infrastructure IAM role name | | [service\_name](#output\_service\_name) | Name of the service | | [service\_task\_definition\_arn](#output\_service\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | | [service\_task\_definition\_revision](#output\_service\_task\_definition\_revision) | Revision of the task in a particular family | diff --git a/examples/ec2-autoscaling/main.tf b/examples/ec2-autoscaling/main.tf index cef24c97..bef66f6c 100644 --- a/examples/ec2-autoscaling/main.tf +++ b/examples/ec2-autoscaling/main.tf @@ -28,14 +28,24 @@ locals { module "ecs_cluster" { source = "../../modules/cluster" - cluster_name = local.name + name = local.name + + # Cluster capacity providers + default_capacity_provider_strategy = { + ex_1 = { + weight = 60 + base = 20 + } + ex_2 = { + weight = 40 + } + } - # Capacity provider - autoscaling groups - default_capacity_provider_use_fargate = false autoscaling_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 = { @@ -44,15 +54,11 @@ module "ecs_cluster" { status = "ENABLED" target_capacity = 60 } - - default_capacity_provider_strategy = { - weight = 60 - base = 20 - } } # Spot instances ex_2 = { auto_scaling_group_arn = module.autoscaling["ex_2"].autoscaling_group_arn + managed_draining = "ENABLED" managed_termination_protection = "ENABLED" managed_scaling = { @@ -61,10 +67,6 @@ module "ecs_cluster" { status = "ENABLED" target_capacity = 90 } - - default_capacity_provider_strategy = { - weight = 40 - } } } @@ -93,40 +95,59 @@ module "ecs_service" { } } + volume_configuration = { + name = "ebs-volume" + managed_ebs_volume = { + encrypted = true + file_system_type = "xfs" + size_in_gb = 5 + volume_type = "gp3" + } + } + volume = { - my-vol = {} + my-vol = {}, + ebs-volume = { + name = "ebs-volume" + configure_at_launch = true + } } # Container definition(s) container_definitions = { (local.container_name) = { image = "public.ecr.aws/ecs-sample-image/amazon-ecs-sample:latest" - port_mappings = [ + portMappings = [ { name = local.container_name containerPort = local.container_port + hostPort = local.container_port protocol = "tcp" } ] - mount_points = [ + mountPoints = [ { sourceVolume = "my-vol", containerPath = "/var/www/my-vol" + }, + { + sourceVolume = "ebs-volume" + containerPath = "/ebs/data" } ] - entry_point = ["/usr/sbin/apache2", "-D", "FOREGROUND"] + entrypoint = ["/usr/sbin/apache2", "-D", "FOREGROUND"] # Example image used requires access to write to root filesystem - readonly_root_filesystem = false + readonlyRootFilesystem = false enable_cloudwatch_logging = true create_cloudwatch_log_group = true cloudwatch_log_group_name = "/aws/ecs/${local.name}/${local.container_name}" cloudwatch_log_group_retention_in_days = 7 - log_configuration = { + logLonfiguration = { logDriver = "awslogs" } } @@ -141,14 +162,11 @@ module "ecs_service" { } subnet_ids = module.vpc.private_subnets - security_group_rules = { - alb_http_ingress = { - type = "ingress" - from_port = local.container_port - to_port = local.container_port - protocol = "tcp" - description = "Service port" - source_security_group_id = module.alb.security_group_id + security_group_ingress_rules = { + alb_http = { + from_port = local.container_port + description = "Service port" + referenced_security_group_id = module.alb.security_group_id } } @@ -161,7 +179,7 @@ module "ecs_service" { # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html#ecs-optimized-ami-linux data "aws_ssm_parameter" "ecs_optimized_ami" { - name = "/aws/service/ecs/optimized-ami/amazon-linux-2/recommended" + name = "/aws/service/ecs/optimized-ami/amazon-linux-2023/recommended" } module "alb" { @@ -236,14 +254,14 @@ module "alb" { module "autoscaling" { source = "terraform-aws-modules/autoscaling/aws" - version = "~> 6.5" + version = "~> 9.0" for_each = { # On-demand instances ex_1 = { instance_type = "t3.large" use_mixed_instances_policy = false - mixed_instances_policy = {} + mixed_instances_policy = null user_data = <<-EOT #!/bin/bash @@ -266,16 +284,18 @@ module "autoscaling" { spot_allocation_strategy = "price-capacity-optimized" } - override = [ - { - instance_type = "m4.large" - weighted_capacity = "2" - }, - { - instance_type = "t3.large" - weighted_capacity = "1" - }, - ] + launch_template = { + override = [ + { + instance_type = "m4.large" + weighted_capacity = "2" + }, + { + instance_type = "t3.large" + weighted_capacity = "1" + }, + ] + } } user_data = <<-EOT #!/bin/bash @@ -352,7 +372,7 @@ module "autoscaling_sg" { module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" + version = "~> 6.0" name = local.name cidr = local.vpc_cidr diff --git a/examples/ec2-autoscaling/outputs.tf b/examples/ec2-autoscaling/outputs.tf index 80903ad5..3ad62947 100644 --- a/examples/ec2-autoscaling/outputs.tf +++ b/examples/ec2-autoscaling/outputs.tf @@ -131,6 +131,16 @@ output "service_autoscaling_scheduled_actions" { value = module.ecs_service.autoscaling_scheduled_actions } +output "service_infrastructure_iam_role_arn" { + description = "Infrastructure IAM role ARN" + value = module.ecs_service.infrastructure_iam_role_arn +} + +output "service_infrastructure_iam_role_name" { + description = "Infrastructure IAM role name" + value = module.ecs_service.infrastructure_iam_role_name +} + ################################################################################ # Application Load Balancer ################################################################################ diff --git a/examples/ec2-autoscaling/versions.tf b/examples/ec2-autoscaling/versions.tf index 682191e7..db13b0a8 100644 --- a/examples/ec2-autoscaling/versions.tf +++ b/examples/ec2-autoscaling/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/examples/fargate/README.md b/examples/fargate/README.md index 49d4697d..440a45cf 100644 --- a/examples/fargate/README.md +++ b/examples/fargate/README.md @@ -26,14 +26,14 @@ Note that this example may create resources which will incur monetary charges on | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66.1 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66.1 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules @@ -43,7 +43,7 @@ Note that this example may create resources which will incur monetary charges on | [ecs\_cluster](#module\_ecs\_cluster) | ../../modules/cluster | n/a | | [ecs\_service](#module\_ecs\_service) | ../../modules/service | n/a | | [ecs\_task\_definition](#module\_ecs\_task\_definition) | ../../modules/service | n/a | -| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [vpc](#module\_vpc) | terraform-aws-modules/vpc/aws | ~> 6.0 | ## Resources @@ -78,7 +78,6 @@ No inputs. | [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\_family\_revision](#output\_service\_task\_definition\_family\_revision) | The family and revision (family:revision) 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 | diff --git a/examples/fargate/main.tf b/examples/fargate/main.tf index 7cae2e0c..b84c7387 100644 --- a/examples/fargate/main.tf +++ b/examples/fargate/main.tf @@ -28,20 +28,16 @@ locals { module "ecs_cluster" { source = "../../modules/cluster" - cluster_name = local.name + name = local.name # Capacity provider - fargate_capacity_providers = { + default_capacity_provider_strategy = { FARGATE = { - default_capacity_provider_strategy = { - weight = 50 - base = 20 - } + weight = 50 + base = 20 } FARGATE_SPOT = { - default_capacity_provider_strategy = { - weight = 50 - } + weight = 50 } } @@ -72,11 +68,11 @@ module "ecs_service" { memory = 1024 essential = true image = nonsensitive(data.aws_ssm_parameter.fluentbit.value) - firelens_configuration = { + firelensConfiguration = { type = "fluentbit" } - memory_reservation = 50 - user = "0" + memoryReservation = 50 + user = "0" } (local.container_name) = { @@ -84,7 +80,7 @@ module "ecs_service" { memory = 1024 essential = true image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ + portMappings = [ { name = local.container_name containerPort = local.container_port @@ -94,15 +90,15 @@ module "ecs_service" { ] # Example image used requires access to write to root filesystem - readonly_root_filesystem = false + readonlyRootFilesystem = false - dependencies = [{ + dependsOn = [{ containerName = "fluent-bit" condition = "START" }] enable_cloudwatch_logging = false - log_configuration = { + logConfiguration = { logDriver = "awsfirelens" options = { Name = "firehose" @@ -112,7 +108,7 @@ module "ecs_service" { } } - linux_parameters = { + linuxParameters = { capabilities = { add = [] drop = [ @@ -121,26 +117,34 @@ module "ecs_service" { } } + restartPolicy = { + enabled = true + ignoredExitCodes = [1] + restartAttemptPeriod = 60 + } + # Not required for fluent-bit, just an example - volumes_from = [{ + volumesFrom = [{ sourceContainer = "fluent-bit" readOnly = false }] - memory_reservation = 100 + memoryReservation = 100 } } service_connect_configuration = { namespace = aws_service_discovery_http_namespace.this.arn - service = { - client_alias = { - port = local.container_port - dns_name = local.container_name + service = [ + { + client_alias = { + port = local.container_port + dns_name = local.container_name + } + port_name = local.container_name + discovery_name = local.container_name } - port_name = local.container_name - discovery_name = local.container_name - } + ] } load_balancer = { @@ -152,21 +156,18 @@ module "ecs_service" { } subnet_ids = module.vpc.private_subnets - security_group_rules = { - alb_ingress_3000 = { - type = "ingress" - from_port = local.container_port - to_port = local.container_port - protocol = "tcp" - description = "Service port" - source_security_group_id = module.alb.security_group_id + security_group_ingress_rules = { + alb_3000 = { + description = "Service port" + from_port = local.container_port + ip_protocol = "tcp" + referenced_security_group_id = module.alb.security_group_id } - egress_all = { - type = "egress" - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" } } @@ -204,7 +205,7 @@ module "ecs_task_definition" { al2023 = { image = "public.ecr.aws/amazonlinux/amazonlinux:2023-minimal" - mount_points = [ + mountPoints = [ { sourceVolume = "ex-vol", containerPath = "/var/www/ex-vol" @@ -218,13 +219,10 @@ module "ecs_task_definition" { subnet_ids = module.vpc.private_subnets - security_group_rules = { - egress_all = { - type = "egress" - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" } } @@ -317,7 +315,7 @@ module "alb" { module "vpc" { source = "terraform-aws-modules/vpc/aws" - version = "~> 5.0" + version = "~> 6.0" name = local.name cidr = local.vpc_cidr diff --git a/examples/fargate/outputs.tf b/examples/fargate/outputs.tf index 6c77f7ba..6f33f2f1 100644 --- a/examples/fargate/outputs.tf +++ b/examples/fargate/outputs.tf @@ -76,11 +76,6 @@ output "service_task_definition_family" { value = module.ecs_service.task_definition_family } -output "service_task_definition_family_revision" { - description = "The family and revision (family:revision) of the task definition" - value = module.ecs_service.task_definition_family_revision -} - output "service_task_exec_iam_role_name" { description = "Task execution IAM role name" value = module.ecs_service.task_exec_iam_role_name @@ -159,7 +154,7 @@ output "task_definition_run_task_command" { description = "awscli command to run the standalone task" value = < v if var.create } + for_each = var.create && var.services != null ? var.services : {} - create = try(each.value.create, true) - create_service = try(each.value.create_service, true) + create = each.value.create + create_service = each.value.create_service + region = var.region # Service - ignore_task_definition_changes = try(each.value.ignore_task_definition_changes, false) - alarms = try(each.value.alarms, {}) - capacity_provider_strategy = try(each.value.capacity_provider_strategy, {}) + ignore_task_definition_changes = each.value.ignore_task_definition_changes + alarms = each.value.alarms + availability_zone_rebalancing = each.value.availability_zone_rebalancing + capacity_provider_strategy = each.value.capacity_provider_strategy cluster_arn = module.cluster.arn - deployment_circuit_breaker = try(each.value.deployment_circuit_breaker, {}) - deployment_controller = try(each.value.deployment_controller, {}) - deployment_maximum_percent = try(each.value.deployment_maximum_percent, 200) - deployment_minimum_healthy_percent = try(each.value.deployment_minimum_healthy_percent, 66) - desired_count = try(each.value.desired_count, 1) - enable_ecs_managed_tags = try(each.value.enable_ecs_managed_tags, true) - enable_execute_command = try(each.value.enable_execute_command, false) - force_new_deployment = try(each.value.force_new_deployment, true) - health_check_grace_period_seconds = try(each.value.health_check_grace_period_seconds, null) - launch_type = try(each.value.launch_type, "FARGATE") - load_balancer = lookup(each.value, "load_balancer", {}) - name = try(each.value.name, each.key) - assign_public_ip = try(each.value.assign_public_ip, false) - security_group_ids = lookup(each.value, "security_group_ids", []) - subnet_ids = lookup(each.value, "subnet_ids", []) - ordered_placement_strategy = try(each.value.ordered_placement_strategy, {}) - placement_constraints = try(each.value.placement_constraints, {}) - platform_version = try(each.value.platform_version, null) - propagate_tags = try(each.value.propagate_tags, null) - scheduling_strategy = try(each.value.scheduling_strategy, null) - service_connect_configuration = lookup(each.value, "service_connect_configuration", {}) - service_registries = lookup(each.value, "service_registries", {}) - timeouts = try(each.value.timeouts, {}) - triggers = try(each.value.triggers, {}) - wait_for_steady_state = try(each.value.wait_for_steady_state, null) + deployment_circuit_breaker = each.value.deployment_circuit_breaker + deployment_controller = each.value.deployment_controller + deployment_maximum_percent = each.value.deployment_maximum_percent + deployment_minimum_healthy_percent = each.value.deployment_minimum_healthy_percent + desired_count = each.value.desired_count + enable_ecs_managed_tags = each.value.enable_ecs_managed_tags + enable_execute_command = each.value.enable_execute_command + force_delete = each.value.force_delete + force_new_deployment = each.value.force_new_deployment + health_check_grace_period_seconds = each.value.health_check_grace_period_seconds + launch_type = each.value.launch_type + load_balancer = each.value.load_balancer + name = coalesce(each.value.name, each.key) + assign_public_ip = each.value.assign_public_ip + security_group_ids = each.value.security_group_ids + subnet_ids = each.value.subnet_ids + ordered_placement_strategy = each.value.ordered_placement_strategy + placement_constraints = each.value.placement_constraints + platform_version = each.value.platform_version + propagate_tags = each.value.propagate_tags + scheduling_strategy = each.value.scheduling_strategy + service_connect_configuration = each.value.service_connect_configuration + service_registries = each.value.service_registries + timeouts = each.value.timeouts + triggers = each.value.triggers + volume_configuration = each.value.volume_configuration + vpc_lattice_configurations = each.value.vpc_lattice_configurations + wait_for_steady_state = each.value.wait_for_steady_state + service_tags = each.value.service_tags # Service IAM role - create_iam_role = try(each.value.create_iam_role, true) - iam_role_arn = lookup(each.value, "iam_role_arn", null) - iam_role_name = try(each.value.iam_role_name, null) - iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, true) - iam_role_path = try(each.value.iam_role_path, null) - iam_role_description = try(each.value.iam_role_description, null) - iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, null) - iam_role_tags = try(each.value.iam_role_tags, {}) - iam_role_statements = lookup(each.value, "iam_role_statements", {}) + create_iam_role = each.value.create_iam_role + iam_role_arn = each.value.iam_role_arn + iam_role_name = each.value.iam_role_name + iam_role_use_name_prefix = each.value.iam_role_use_name_prefix + iam_role_path = each.value.iam_role_path + iam_role_description = each.value.iam_role_description + iam_role_permissions_boundary = each.value.iam_role_permissions_boundary + iam_role_tags = each.value.iam_role_tags + iam_role_statements = each.value.iam_role_statements # Task definition - create_task_definition = try(each.value.create_task_definition, true) - task_definition_arn = lookup(each.value, "task_definition_arn", null) - container_definitions = try(each.value.container_definitions, {}) - container_definition_defaults = try(each.value.container_definition_defaults, {}) - cpu = try(each.value.cpu, 1024) - ephemeral_storage = try(each.value.ephemeral_storage, {}) - family = try(each.value.family, null) - inference_accelerator = try(each.value.inference_accelerator, {}) - ipc_mode = try(each.value.ipc_mode, null) - memory = try(each.value.memory, 2048) - network_mode = try(each.value.network_mode, "awsvpc") - pid_mode = try(each.value.pid_mode, null) - proxy_configuration = try(each.value.proxy_configuration, {}) - requires_compatibilities = try(each.value.requires_compatibilities, ["FARGATE"]) - runtime_platform = try(each.value.runtime_platform, { - operating_system_family = "LINUX" - cpu_architecture = "X86_64" - }) - skip_destroy = try(each.value.skip_destroy, null) - volume = try(each.value.volume, {}) - task_tags = try(each.value.task_tags, {}) - - # Task execution IAM role - create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, true) - task_exec_iam_role_arn = lookup(each.value, "task_exec_iam_role_arn", null) - task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, null) - task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, true) - task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, null) - task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, null) - task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, null) - task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, {}) - task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, {}) - task_exec_iam_role_max_session_duration = try(each.value.task_exec_iam_role_max_session_duration, null) + create_task_definition = each.value.create_task_definition + task_definition_arn = each.value.task_definition_arn + container_definitions = each.value.container_definitions + container_definition_defaults = each.value.container_definition_defaults + cpu = each.value.cpu + enable_fault_injection = each.value.enable_fault_injection + ephemeral_storage = each.value.ephemeral_storage + family = each.value.family + ipc_mode = each.value.ipc_mode + memory = each.value.memory + network_mode = each.value.network_mode + pid_mode = each.value.pid_mode + proxy_configuration = each.value.proxy_configuration + requires_compatibilities = each.value.requires_compatibilities + runtime_platform = each.value.runtime_platform + skip_destroy = each.value.skip_destroy + task_definition_placement_constraints = each.value.task_definition_placement_constraints + track_latest = each.value.track_latest + volume = each.value.volume + task_tags = each.value.task_tags + + # Task Execution IAM role + create_task_exec_iam_role = each.value.create_task_exec_iam_role + task_exec_iam_role_arn = try(coalesce(each.value.task_exec_iam_role_arn, module.cluster.task_exec_iam_role_arn), null) + task_exec_iam_role_name = each.value.task_exec_iam_role_name + task_exec_iam_role_use_name_prefix = each.value.task_exec_iam_role_use_name_prefix + task_exec_iam_role_path = each.value.task_exec_iam_role_path + task_exec_iam_role_description = each.value.task_exec_iam_role_description + task_exec_iam_role_permissions_boundary = each.value.task_exec_iam_role_permissions_boundary + task_exec_iam_role_tags = each.value.task_exec_iam_role_tags + task_exec_iam_role_policies = each.value.task_exec_iam_role_policies + task_exec_iam_role_max_session_duration = each.value.task_exec_iam_role_max_session_duration # Task execution IAM role policy - create_task_exec_policy = try(each.value.create_task_exec_policy, true) - task_exec_ssm_param_arns = lookup(each.value, "task_exec_ssm_param_arns", ["arn:aws:ssm:*:*:parameter/*"]) - task_exec_secret_arns = lookup(each.value, "task_exec_secret_arns", ["arn:aws:secretsmanager:*:*:secret:*"]) - task_exec_iam_statements = lookup(each.value, "task_exec_iam_statements", {}) + create_task_exec_policy = each.value.create_task_exec_policy + task_exec_ssm_param_arns = each.value.task_exec_ssm_param_arns + task_exec_secret_arns = each.value.task_exec_secret_arns + task_exec_iam_statements = each.value.task_exec_iam_statements + task_exec_iam_policy_path = each.value.task_exec_iam_policy_path # Tasks - IAM role - create_tasks_iam_role = try(each.value.create_tasks_iam_role, true) - tasks_iam_role_arn = lookup(each.value, "tasks_iam_role_arn", null) - tasks_iam_role_name = try(each.value.tasks_iam_role_name, null) - tasks_iam_role_use_name_prefix = try(each.value.tasks_iam_role_use_name_prefix, true) - tasks_iam_role_path = try(each.value.tasks_iam_role_path, null) - tasks_iam_role_description = try(each.value.tasks_iam_role_description, null) - tasks_iam_role_permissions_boundary = try(each.value.tasks_iam_role_permissions_boundary, null) - tasks_iam_role_tags = try(each.value.tasks_iam_role_tags, {}) - tasks_iam_role_policies = lookup(each.value, "tasks_iam_role_policies", {}) - tasks_iam_role_statements = lookup(each.value, "tasks_iam_role_statements", {}) + create_tasks_iam_role = each.value.create_tasks_iam_role + tasks_iam_role_arn = each.value.tasks_iam_role_arn + tasks_iam_role_name = each.value.tasks_iam_role_name + tasks_iam_role_use_name_prefix = each.value.tasks_iam_role_use_name_prefix + tasks_iam_role_path = each.value.tasks_iam_role_path + tasks_iam_role_description = each.value.tasks_iam_role_description + tasks_iam_role_permissions_boundary = each.value.tasks_iam_role_permissions_boundary + tasks_iam_role_tags = each.value.tasks_iam_role_tags + tasks_iam_role_policies = each.value.tasks_iam_role_policies + tasks_iam_role_statements = each.value.tasks_iam_role_statements # Task set - external_id = try(each.value.external_id, null) - scale = try(each.value.scale, {}) - force_delete = try(each.value.force_delete, null) - wait_until_stable = try(each.value.wait_until_stable, null) - wait_until_stable_timeout = try(each.value.wait_until_stable_timeout, null) + external_id = each.value.external_id + scale = each.value.scale + wait_until_stable = each.value.wait_until_stable + wait_until_stable_timeout = each.value.wait_until_stable_timeout # Autoscaling - enable_autoscaling = try(each.value.enable_autoscaling, true) - autoscaling_min_capacity = try(each.value.autoscaling_min_capacity, 1) - autoscaling_max_capacity = try(each.value.autoscaling_max_capacity, 10) - autoscaling_policies = try(each.value.autoscaling_policies, { - cpu = { - policy_type = "TargetTrackingScaling" - - target_tracking_scaling_policy_configuration = { - predefined_metric_specification = { - predefined_metric_type = "ECSServiceAverageCPUUtilization" - } - } - } - memory = { - policy_type = "TargetTrackingScaling" - - target_tracking_scaling_policy_configuration = { - predefined_metric_specification = { - predefined_metric_type = "ECSServiceAverageMemoryUtilization" - } - } - } - }) - autoscaling_scheduled_actions = try(each.value.autoscaling_scheduled_actions, {}) + enable_autoscaling = each.value.enable_autoscaling + autoscaling_min_capacity = each.value.autoscaling_min_capacity + autoscaling_max_capacity = each.value.autoscaling_max_capacity + autoscaling_policies = each.value.autoscaling_policies + autoscaling_scheduled_actions = each.value.autoscaling_scheduled_actions # Security Group - create_security_group = try(each.value.create_security_group, true) - security_group_name = try(each.value.security_group_name, null) - security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, true) - security_group_description = try(each.value.security_group_description, null) - security_group_rules = lookup(each.value, "security_group_rules", {}) - security_group_tags = try(each.value.security_group_tags, {}) - - tags = merge(var.tags, try(each.value.tags, {})) + create_security_group = each.value.create_security_group + security_group_name = each.value.security_group_name + security_group_use_name_prefix = each.value.security_group_use_name_prefix + security_group_description = each.value.security_group_description + security_group_ingress_rules = each.value.security_group_ingress_rules + security_group_egress_rules = each.value.security_group_egress_rules + security_group_tags = each.value.security_group_tags + + # ECS infrastructure IAM role + create_infrastructure_iam_role = each.value.create_infrastructure_iam_role + infrastructure_iam_role_arn = each.value.infrastructure_iam_role_arn + infrastructure_iam_role_name = each.value.infrastructure_iam_role_name + infrastructure_iam_role_use_name_prefix = each.value.infrastructure_iam_role_use_name_prefix + infrastructure_iam_role_path = each.value.infrastructure_iam_role_path + infrastructure_iam_role_description = each.value.infrastructure_iam_role_description + infrastructure_iam_role_permissions_boundary = each.value.infrastructure_iam_role_permissions_boundary + infrastructure_iam_role_tags = each.value.infrastructure_iam_role_tags + + tags = merge(var.tags, each.value.tags) } diff --git a/modules/cluster/README.md b/modules/cluster/README.md index 282943b5..987dc30b 100644 --- a/modules/cluster/README.md +++ b/modules/cluster/README.md @@ -30,16 +30,13 @@ module "ecs_cluster" { } } - fargate_capacity_providers = { + default_capacity_provider_strategy = { FARGATE = { - default_capacity_provider_strategy = { - weight = 50 - } + weight = 50 + base = 20 } FARGATE_SPOT = { - default_capacity_provider_strategy = { - weight = 50 - } + weight = 50 } } @@ -67,9 +64,20 @@ module "ecs_cluster" { } } + default_capacity_provider_strategy = { + one = { + weight = 60 + base = 20 + } + two = { + weight = 40 + } + } + autoscaling_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 = { @@ -78,14 +86,10 @@ module "ecs_cluster" { status = "ENABLED" target_capacity = 60 } - - default_capacity_provider_strategy = { - weight = 60 - base = 20 - } } two = { auto_scaling_group_arn = "arn:aws:autoscaling:eu-west-1:012345678901:autoScalingGroup:08419a61:autoScalingGroupName/ecs-ec2-two-20220603194933774300000022" + managed_draining = "ENABLED" managed_termination_protection = "ENABLED" managed_scaling = { @@ -94,10 +98,6 @@ module "ecs_cluster" { status = "ENABLED" target_capacity = 90 } - - default_capacity_provider_strategy = { - weight = 40 - } } } @@ -134,14 +134,14 @@ module "ecs_cluster" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66.1 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66.1 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules @@ -166,21 +166,22 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [autoscaling\_capacity\_providers](#input\_autoscaling\_capacity\_providers) | Map of autoscaling capacity provider definitions to create for the cluster | `any` | `{}` | no | +| [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 | +| [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\_configuration](#input\_cluster\_configuration) | The execute command configuration for the cluster | `any` | `{}` | no | -| [cluster\_name](#input\_cluster\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | -| [cluster\_service\_connect\_defaults](#input\_cluster\_service\_connect\_defaults) | Configures a default Service Connect namespace | `map(string)` | `{}` | no | -| [cluster\_settings](#input\_cluster\_settings) | List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster | `any` |
[
{
"name": "containerInsights",
"value": "enabled"
}
]
| 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\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `false` | no | | [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | -| [default\_capacity\_provider\_use\_fargate](#input\_default\_capacity\_provider\_use\_fargate) | Determines whether to use Fargate or autoscaling for default capacity provider strategy | `bool` | `true` | no | -| [fargate\_capacity\_providers](#input\_fargate\_capacity\_providers) | Map of Fargate capacity provider definitions to use for the cluster | `any` | `{}` | no | +| [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 | +| [name](#input\_name) | Name of the cluster (up to 255 letters, numbers, hyphens, and underscores) | `string` | `""` | no | +| [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | 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 | | [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 | @@ -189,9 +190,9 @@ No modules. | [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | | [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | | [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | -| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | -| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | +| [task\_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(map(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 | ## Outputs diff --git a/modules/cluster/main.tf b/modules/cluster/main.tf index cecf7e44..eb750499 100644 --- a/modules/cluster/main.tf +++ b/modules/cluster/main.tf @@ -2,76 +2,52 @@ # Cluster ################################################################################ -locals { - execute_command_configuration = { - logging = "OVERRIDE" - log_configuration = { - cloud_watch_log_group_name = try(aws_cloudwatch_log_group.this[0].name, null) - } - } -} - resource "aws_ecs_cluster" "this" { count = var.create ? 1 : 0 - name = var.cluster_name + region = var.region dynamic "configuration" { - for_each = var.create_cloudwatch_log_group ? [var.cluster_configuration] : [] + for_each = var.configuration != null ? [var.configuration] : [] content { dynamic "execute_command_configuration" { - for_each = try([merge(local.execute_command_configuration, configuration.value.execute_command_configuration)], [{}]) + for_each = configuration.value.execute_command_configuration != null ? [configuration.value.execute_command_configuration] : [] content { - kms_key_id = try(execute_command_configuration.value.kms_key_id, null) - logging = try(execute_command_configuration.value.logging, "DEFAULT") + kms_key_id = execute_command_configuration.value.kms_key_id dynamic "log_configuration" { - for_each = try([execute_command_configuration.value.log_configuration], []) + for_each = execute_command_configuration.value.log_configuration != null ? [execute_command_configuration.value.log_configuration] : [] content { - cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) - cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) - s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) - s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) - s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) + cloud_watch_encryption_enabled = log_configuration.value.cloud_watch_encryption_enabled + cloud_watch_log_group_name = try(aws_cloudwatch_log_group.this[0].name, log_configuration.value.cloud_watch_log_group_name) + s3_bucket_encryption_enabled = log_configuration.value.s3_bucket_encryption_enabled + s3_bucket_name = log_configuration.value.s3_bucket_name + s3_key_prefix = log_configuration.value.s3_key_prefix } } + + logging = execute_command_configuration.value.logging } } - } - } - - dynamic "configuration" { - for_each = !var.create_cloudwatch_log_group && length(var.cluster_configuration) > 0 ? [var.cluster_configuration] : [] - content { - dynamic "execute_command_configuration" { - for_each = try([configuration.value.execute_command_configuration], [{}]) + dynamic "managed_storage_configuration" { + for_each = configuration.value.managed_storage_configuration != null ? [configuration.value.managed_storage_configuration] : [] content { - kms_key_id = try(execute_command_configuration.value.kms_key_id, null) - logging = try(execute_command_configuration.value.logging, "DEFAULT") - - dynamic "log_configuration" { - for_each = try([execute_command_configuration.value.log_configuration], []) - - content { - cloud_watch_encryption_enabled = try(log_configuration.value.cloud_watch_encryption_enabled, null) - cloud_watch_log_group_name = try(log_configuration.value.cloud_watch_log_group_name, null) - s3_bucket_name = try(log_configuration.value.s3_bucket_name, null) - s3_bucket_encryption_enabled = try(log_configuration.value.s3_bucket_encryption_enabled, null) - s3_key_prefix = try(log_configuration.value.s3_key_prefix, null) - } - } + fargate_ephemeral_storage_kms_key_id = managed_storage_configuration.value.fargate_ephemeral_storage_kms_key_id + kms_key_id = managed_storage_configuration.value.kms_key_id } } } } + name = var.name + dynamic "service_connect_defaults" { - for_each = length(var.cluster_service_connect_defaults) > 0 ? [var.cluster_service_connect_defaults] : [] + for_each = var.service_connect_defaults != null ? [var.service_connect_defaults] : [] content { namespace = service_connect_defaults.value.namespace @@ -79,7 +55,7 @@ resource "aws_ecs_cluster" "this" { } dynamic "setting" { - for_each = flatten([var.cluster_settings]) + for_each = var.setting != null ? var.setting : [] content { name = setting.value.name @@ -93,45 +69,51 @@ resource "aws_ecs_cluster" "this" { ################################################################################ # CloudWatch Log Group ################################################################################ + +locals { + log_group_name = try(coalesce(var.cloudwatch_log_group_name, "/aws/ecs/${var.name}"), "") +} + resource "aws_cloudwatch_log_group" "this" { count = var.create && var.create_cloudwatch_log_group ? 1 : 0 - name = try(coalesce(var.cloudwatch_log_group_name, "/aws/ecs/${var.cluster_name}"), "") + region = var.region + + name = local.log_group_name retention_in_days = var.cloudwatch_log_group_retention_in_days kms_key_id = var.cloudwatch_log_group_kms_key_id + log_group_class = var.cloudwatch_log_group_class - tags = merge(var.tags, var.cloudwatch_log_group_tags) + tags = merge( + var.tags, + var.cloudwatch_log_group_tags, + { Name = local.log_group_name } + ) } ################################################################################ # Cluster Capacity Providers ################################################################################ -locals { - default_capacity_providers = merge( - { for k, v in var.fargate_capacity_providers : k => v if var.default_capacity_provider_use_fargate }, - { for k, v in var.autoscaling_capacity_providers : k => v if !var.default_capacity_provider_use_fargate } - ) -} - resource "aws_ecs_cluster_capacity_providers" "this" { - count = var.create && length(merge(var.fargate_capacity_providers, var.autoscaling_capacity_providers)) > 0 ? 1 : 0 + 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.fargate_capacity_providers : try(v.name, k)], - [for k, v in var.autoscaling_capacity_providers : try(v.name, k)] + [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))] : [] )) # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/cluster-capacity-providers.html#capacity-providers-considerations dynamic "default_capacity_provider_strategy" { - for_each = local.default_capacity_providers - iterator = strategy + for_each = var.default_capacity_provider_strategy != null ? var.default_capacity_provider_strategy : {} content { - capacity_provider = try(strategy.value.name, strategy.key) - base = try(strategy.value.default_capacity_provider_strategy.base, null) - weight = try(strategy.value.default_capacity_provider_strategy.weight, null) + 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 } } @@ -145,29 +127,36 @@ resource "aws_ecs_cluster_capacity_providers" "this" { ################################################################################ resource "aws_ecs_capacity_provider" "this" { - for_each = { for k, v in var.autoscaling_capacity_providers : k => v if var.create } + for_each = var.create && var.autoscaling_capacity_providers != null ? var.autoscaling_capacity_providers : {} - name = try(each.value.name, each.key) + region = var.region auto_scaling_group_provider { auto_scaling_group_arn = each.value.auto_scaling_group_arn - # When you use managed termination protection, you must also use managed scaling otherwise managed termination protection won't work - managed_termination_protection = length(try([each.value.managed_scaling], [])) == 0 ? "DISABLED" : try(each.value.managed_termination_protection, null) + managed_draining = each.value.managed_draining dynamic "managed_scaling" { - for_each = try([each.value.managed_scaling], []) + for_each = each.value.managed_scaling != null ? [each.value.managed_scaling] : [] content { - instance_warmup_period = try(managed_scaling.value.instance_warmup_period, null) - maximum_scaling_step_size = try(managed_scaling.value.maximum_scaling_step_size, null) - minimum_scaling_step_size = try(managed_scaling.value.minimum_scaling_step_size, null) - status = try(managed_scaling.value.status, null) - target_capacity = try(managed_scaling.value.target_capacity, null) + 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 = each.value.managed_scaling != null ? each.value.managed_termination_protection : "DISABLED" } - tags = merge(var.tags, try(each.value.tags, {})) + name = try(coalesce(each.value.name, each.key), "") + + tags = merge( + var.tags, + each.value.tags, + ) } ################################################################################ @@ -176,7 +165,7 @@ resource "aws_ecs_capacity_provider" "this" { ################################################################################ locals { - task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.cluster_name), "") + task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.name), "") create_task_exec_iam_role = var.create && var.create_task_exec_iam_role create_task_exec_policy = local.create_task_exec_iam_role && var.create_task_exec_policy @@ -202,7 +191,7 @@ resource "aws_iam_role" "task_exec" { name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null path = var.task_exec_iam_role_path - description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${var.cluster_name}") + description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${var.name}") assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json permissions_boundary = var.task_exec_iam_role_permissions_boundary @@ -264,18 +253,18 @@ data "aws_iam_policy_document" "task_exec" { } dynamic "statement" { - for_each = var.task_exec_iam_statements + for_each = var.task_exec_iam_statements != null ? var.task_exec_iam_statements : {} content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) + 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 = try(statement.value.principals, []) + for_each = statement.value.principals != null ? statement.value.principals : [] content { type = principals.value.type @@ -284,7 +273,7 @@ data "aws_iam_policy_document" "task_exec" { } dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) + for_each = statement.value.not_principals != null ? statement.value.not_principals : [] content { type = not_principals.value.type @@ -293,7 +282,7 @@ data "aws_iam_policy_document" "task_exec" { } dynamic "condition" { - for_each = try(statement.value.conditions, []) + for_each = statement.value.conditions != null ? statement.value.conditions : [] content { test = condition.value.test diff --git a/modules/cluster/variables.tf b/modules/cluster/variables.tf index 6629743c..a01ddcae 100644 --- a/modules/cluster/variables.tf +++ b/modules/cluster/variables.tf @@ -4,6 +4,12 @@ variable "create" { default = true } +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + variable "tags" { description = "A map of tags to add to all resources" type = map(string) @@ -14,21 +20,55 @@ variable "tags" { # Cluster ################################################################################ -variable "cluster_name" { +variable "configuration" { + description = "The execute command configuration for the cluster" + type = 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) + })) + }) + default = { + execute_command_configuration = { + log_configuration = { + cloud_watch_log_group_name = "placeholder" # will use CloudWatch log group created by module + } + } + } +} + +variable "name" { description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" type = string default = "" } -variable "cluster_configuration" { - description = "The execute command configuration for the cluster" - type = any - default = {} +variable "service_connect_defaults" { + description = "Configures a default Service Connect namespace" + type = object({ + namespace = string + }) + default = null } -variable "cluster_settings" { +variable "setting" { description = "List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" - type = any + type = list(object({ + name = string + value = string + })) default = [ { name = "containerInsights" @@ -37,12 +77,6 @@ variable "cluster_settings" { ] } -variable "cluster_service_connect_defaults" { - description = "Configures a default Service Connect namespace" - type = map(string) - default = {} -} - ################################################################################ # CloudWatch Log Group ################################################################################ @@ -71,6 +105,12 @@ variable "cloudwatch_log_group_kms_key_id" { default = null } +variable "cloudwatch_log_group_class" { + description = "Specified the log class of the log group. Possible values are: `STANDARD` or `INFREQUENT_ACCESS`" + type = string + default = null +} + variable "cloudwatch_log_group_tags" { description = "A map of additional tags to add to the log group created" type = map(string) @@ -81,22 +121,33 @@ variable "cloudwatch_log_group_tags" { # Capacity Providers ################################################################################ -variable "default_capacity_provider_use_fargate" { - description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" - type = bool - default = true -} - -variable "fargate_capacity_providers" { - description = "Map of Fargate capacity provider definitions to use for the cluster" - type = any - default = {} -} - variable "autoscaling_capacity_providers" { description = "Map of autoscaling capacity provider definitions to create for the cluster" - type = any - default = {} + 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) + })) + managed_termination_protection = optional(string) + name = optional(string) # Will fall back to use map key if not set + tags = optional(map(string), {}) + })) + default = null +} + +variable "default_capacity_provider_strategy" { + description = "Map of default capacity provider strategy definitions to use for the cluster" + type = map(object({ + base = optional(number) + name = optional(string) # Will fall back to use map key if not set + weight = optional(number) + })) + default = null } ################################################################################ @@ -161,17 +212,37 @@ variable "create_task_exec_policy" { variable "task_exec_ssm_param_arns" { description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" type = list(string) - default = ["arn:aws:ssm:*:*:parameter/*"] + default = [] } variable "task_exec_secret_arns" { description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" type = list(string) - default = ["arn:aws:secretsmanager:*:*:secret:*"] + default = [] } variable "task_exec_iam_statements" { description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} + 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(map(object({ + test = string + variable = string + values = list(string) + }))) + })) + default = null } diff --git a/modules/cluster/versions.tf b/modules/cluster/versions.tf index 682191e7..db13b0a8 100644 --- a/modules/cluster/versions.tf +++ b/modules/cluster/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/modules/container-definition/README.md b/modules/container-definition/README.md index 6ed52ac7..2fc64486 100644 --- a/modules/container-definition/README.md +++ b/modules/container-definition/README.md @@ -19,7 +19,7 @@ module "ecs_container_definition" { memory = 1024 essential = true image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ + portMappings = [ { name = "ecs-sample" containerPort = 80 @@ -28,9 +28,9 @@ module "ecs_container_definition" { ] # Example image used requires access to write to root filesystem - readonly_root_filesystem = false + readonlyRootFilesystem = false - memory_reservation = 100 + memoryReservation = 100 tags = { Environment = "dev" @@ -50,10 +50,10 @@ module "fluentbit_ecs_container_definition" { memory = 1024 essential = true image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" - firelens_configuration = { + firelensConfiguration = { type = "fluentbit" } - memory_reservation = 50 + memoryReservation = 50 tags = { Environment = "dev" @@ -69,7 +69,7 @@ module "example_ecs_container_definition" { memory = 1024 essential = true image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ + portMappings = [ { name = "ecs-sample" containerPort = 80 @@ -78,15 +78,15 @@ module "example_ecs_container_definition" { ] # Example image used requires access to write to root filesystem - readonly_root_filesystem = false + readonlyRootFilesystem = false - dependencies = [{ + dependsOn = [{ containerName = "fluent-bit" condition = "START" }] enable_cloudwatch_logging = false - log_configuration = { + logConfiguration = { logDriver = "awsfirelens" options = { Name = "firehose" @@ -95,7 +95,7 @@ module "example_ecs_container_definition" { log-driver-buffer-limit = "2097152" } } - memory_reservation = 100 + memoryReservation = 100 tags = { Environment = "dev" @@ -115,14 +115,14 @@ module "example_ecs_container_definition" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66.1 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66.1 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules @@ -139,55 +139,59 @@ No modules. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [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 a service associated with the container definition | `string` | `null` | no | -| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events. Default is 30 days | `number` | `30` | no | +| [cloudwatch\_log\_group\_retention\_in\_days](#input\_cloudwatch\_log\_group\_retention\_in\_days) | Number of days to retain log events. Default is 30 days | `number` | `14` | no | | [cloudwatch\_log\_group\_use\_name\_prefix](#input\_cloudwatch\_log\_group\_use\_name\_prefix) | Determines whether the log group name should be used as a prefix | `bool` | `false` | no | -| [command](#input\_command) | The command that's passed to the container | `list(string)` | `[]` | no | +| [command](#input\_command) | The command that's passed to the container | `list(string)` | `null` | no | | [cpu](#input\_cpu) | The number of cpu units to reserve for the container. This is optional for tasks using Fargate launch type and the total amount of `cpu` of all containers in a task will need to be lower than the task-level cpu value | `number` | `null` | no | | [create\_cloudwatch\_log\_group](#input\_create\_cloudwatch\_log\_group) | Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled | `bool` | `true` | no | -| [dependencies](#input\_dependencies) | The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY |
list(object({
condition = string
containerName = string
}))
| `[]` | no | -| [disable\_networking](#input\_disable\_networking) | When this parameter is true, networking is disabled within the container | `bool` | `null` | no | -| [dns\_search\_domains](#input\_dns\_search\_domains) | Container DNS search domains. A list of DNS search domains that are presented to the container | `list(string)` | `[]` | no | -| [dns\_servers](#input\_dns\_servers) | Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers | `list(string)` | `[]` | no | -| [docker\_labels](#input\_docker\_labels) | A key/value map of labels to add to the container | `map(string)` | `{}` | no | -| [docker\_security\_options](#input\_docker\_security\_options) | A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type | `list(string)` | `[]` | no | +| [dependsOn](#input\_dependsOn) | The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY |
list(object({
condition = string
containerName = string
}))
| `null` | no | +| [disableNetworking](#input\_disableNetworking) | When this parameter is true, networking is disabled within the container | `bool` | `null` | no | +| [dnsSearchDomains](#input\_dnsSearchDomains) | Container DNS search domains. A list of DNS search domains that are presented to the container | `list(string)` | `null` | no | +| [dnsServers](#input\_dnsServers) | Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers | `list(string)` | `null` | no | +| [dockerLabels](#input\_dockerLabels) | A key/value map of labels to add to the container | `map(string)` | `null` | no | +| [dockerSecurityOptions](#input\_dockerSecurityOptions) | A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type | `list(string)` | `null` | no | | [enable\_cloudwatch\_logging](#input\_enable\_cloudwatch\_logging) | Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers | `bool` | `true` | no | | [enable\_execute\_command](#input\_enable\_execute\_command) | Specifies whether to enable Amazon ECS Exec for the tasks within the service | `bool` | `false` | no | | [entrypoint](#input\_entrypoint) | The entry point that is passed to the container | `list(string)` | `[]` | no | | [environment](#input\_environment) | The environment variables to pass to the container |
list(object({
name = string
value = string
}))
| `[]` | no | -| [environment\_files](#input\_environment\_files) | A list of files containing the environment variables to pass to a container |
list(object({
value = string
type = string
}))
| `[]` | no | +| [environmentFiles](#input\_environmentFiles) | A list of files containing the environment variables to pass to a container |
list(object({
value = string
type = string
}))
| `[]` | no | | [essential](#input\_essential) | If the `essential` parameter of a container is marked as `true`, and that container fails or stops for any reason, all other containers that are part of the task are stopped | `bool` | `null` | no | -| [extra\_hosts](#input\_extra\_hosts) | A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container |
list(object({
hostname = string
ipAddress = string
}))
| `[]` | no | -| [firelens\_configuration](#input\_firelens\_configuration) | The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide | `any` | `{}` | no | -| [health\_check](#input\_health\_check) | The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html) | `any` | `{}` | no | +| [extraHosts](#input\_extraHosts) | A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container |
list(object({
hostname = string
ipAddress = string
}))
| `null` | no | +| [firelensConfiguration](#input\_firelensConfiguration) | The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide |
object({
options = optional(map(string))
type = optional(string)
})
| `null` | no | +| [healthCheck](#input\_healthCheck) | The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html) |
object({
command = optional(list(string), [])
interval = optional(number, 30)
retries = optional(number, 3)
startPeriod = optional(number)
timeout = optional(number, 5)
})
| `null` | no | | [hostname](#input\_hostname) | The hostname to use for your container | `string` | `null` | no | | [image](#input\_image) | The image used to start a container. This string is passed directly to the Docker daemon. By default, images in the Docker Hub registry are available. Other repositories are specified with either `repository-url/image:tag` or `repository-url/image@digest` | `string` | `null` | no | | [interactive](#input\_interactive) | When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated | `bool` | `false` | no | -| [links](#input\_links) | The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge` | `list(string)` | `[]` | no | -| [linux\_parameters](#input\_linux\_parameters) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) | `any` | `{}` | no | -| [log\_configuration](#input\_log\_configuration) | The log configuration for the container. For more information see [LogConfiguration](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html) | `any` | `{}` | no | +| [links](#input\_links) | The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge` | `list(string)` | `null` | no | +| [linuxParameters](#input\_linuxParameters) | Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html) |
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, false)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
})
|
{
"initProcessEnabled": false
}
| no | +| [logConfiguration](#input\_logConfiguration) | The log configuration for the container. For more information see [LogConfiguration](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html) |
object({
logDriver = optional(string)
options = optional(map(string))
secretOptions = optional(list(object({
name = string
valueFrom = string
})))
})
| `{}` | no | | [memory](#input\_memory) | The amount (in MiB) of memory to present to the container. If your container attempts to exceed the memory specified here, the container is killed. The total amount of memory reserved for all containers within a task must be lower than the task `memory` value, if one is specified | `number` | `null` | no | -| [memory\_reservation](#input\_memory\_reservation) | The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance | `number` | `null` | no | -| [mount\_points](#input\_mount\_points) | The mount points for data volumes in your container | `list(any)` | `[]` | no | +| [memoryReservation](#input\_memoryReservation) | The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance | `number` | `null` | no | +| [mountPoints](#input\_mountPoints) | The mount points for data volumes in your container |
list(object({
containerPath = optional(string)
readOnly = optional(bool)
sourceVolume = optional(string)
}))
| `[]` | no | | [name](#input\_name) | The name of a container. If you're linking multiple containers together in a task definition, the name of one container can be entered in the links of another container to connect the containers. Up to 255 letters (uppercase and lowercase), numbers, underscores, and hyphens are allowed | `string` | `null` | no | | [operating\_system\_family](#input\_operating\_system\_family) | The OS family for task | `string` | `"LINUX"` | no | -| [port\_mappings](#input\_port\_mappings) | The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort | `list(any)` | `[]` | no | +| [portMappings](#input\_portMappings) | The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort |
list(object({
appProtocol = optional(string)
containerPort = optional(number)
containerPortRange = optional(string)
hostPort = optional(number)
name = optional(string)
protocol = optional(string)
}))
| `null` | no | | [privileged](#input\_privileged) | When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user) | `bool` | `false` | no | -| [pseudo\_terminal](#input\_pseudo\_terminal) | When this parameter is true, a `TTY` is allocated | `bool` | `false` | no | -| [readonly\_root\_filesystem](#input\_readonly\_root\_filesystem) | When this parameter is true, the container is given read-only access to its root file system | `bool` | `true` | no | -| [repository\_credentials](#input\_repository\_credentials) | Container repository credentials; required when using a private repo. This map currently supports a single key; "credentialsParameter", which should be the ARN of a Secrets Manager's secret holding the credentials | `map(string)` | `{}` | no | -| [resource\_requirements](#input\_resource\_requirements) | The type and amount of a resource to assign to a container. The only supported resource is a GPU |
list(object({
type = string
value = string
}))
| `[]` | no | -| [secrets](#input\_secrets) | The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide |
list(object({
name = string
valueFrom = string
}))
| `[]` | no | -| [service](#input\_service) | The name of the service that the container definition is associated with | `string` | `""` | no | -| [start\_timeout](#input\_start\_timeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no | -| [stop\_timeout](#input\_stop\_timeout) | Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own | `number` | `120` | no | -| [system\_controls](#input\_system\_controls) | A list of namespaced kernel parameters to set in the container | `list(map(string))` | `[]` | no | +| [pseudoTerminal](#input\_pseudoTerminal) | When this parameter is true, a `TTY` is allocated | `bool` | `false` | no | +| [readonlyRootFilesystem](#input\_readonlyRootFilesystem) | When this parameter is true, the container is given read-only access to its root file system | `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 | +| [repositoryCredentials](#input\_repositoryCredentials) | Container repository credentials; required when using a private repo. This map currently supports a single key; "credentialsParameter", which should be the ARN of a Secrets Manager's secret holding the credentials |
object({
credentialsParameter = optional(string)
})
| `null` | no | +| [resourceRequirements](#input\_resourceRequirements) | The type and amount of a resource to assign to a container. The only supported resource is a GPU |
list(object({
type = string
value = string
}))
| `null` | no | +| [restartPolicy](#input\_restartPolicy) | Container restart policy; helps overcome transient failures faster and maintain task availability |
object({
enabled = optional(bool, true)
ignoredExitCodes = optional(list(number), [])
restartAttemptPeriod = optional(number)
})
|
{
"enabled": true
}
| no | +| [secrets](#input\_secrets) | The secrets to pass to the container. For more information, see [Specifying Sensitive Data](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the Amazon Elastic Container Service Developer Guide |
list(object({
name = string
valueFrom = string
}))
| `null` | no | +| [service](#input\_service) | The name of the service that the container definition is associated with. Used in CloudWatch log group default name (if one is not provided) | `string` | `null` | no | +| [startTimeout](#input\_startTimeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no | +| [stopTimeout](#input\_stopTimeout) | Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own | `number` | `120` | no | +| [systemControls](#input\_systemControls) | A list of namespaced kernel parameters to set in the container |
list(object({
namespace = optional(string)
value = optional(string)
}))
| `[]` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | -| [ulimits](#input\_ulimits) | A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker |
list(object({
hardLimit = number
name = string
softLimit = number
}))
| `[]` | no | +| [ulimits](#input\_ulimits) | A list of ulimits to set in the container. If a ulimit value is specified in a task definition, it overrides the default values set by Docker |
list(object({
hardLimit = number
name = string
softLimit = number
}))
| `null` | no | | [user](#input\_user) | The user to run as inside the container. Can be any of these formats: user, user:group, uid, uid:gid, user:gid, uid:group. The default (null) will use the container's configured `USER` directive or root if not set | `string` | `null` | no | -| [volumes\_from](#input\_volumes\_from) | Data volumes to mount from another container | `list(any)` | `[]` | no | -| [working\_directory](#input\_working\_directory) | The working directory to run commands inside the container | `string` | `null` | no | +| [versionConsistency](#input\_versionConsistency) | Specifies whether Amazon ECS will resolve the container image tag provided in the container definition to an image digest | `string` | `"disabled"` | no | +| [volumesFrom](#input\_volumesFrom) | Data volumes to mount from another container |
list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
}))
| `[]` | no | +| [workingDirectory](#input\_workingDirectory) | The working directory to run commands inside the container | `string` | `null` | no | ## Outputs @@ -196,6 +200,7 @@ No modules. | [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of CloudWatch log group created | | [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of CloudWatch log group created | | [container\_definition](#output\_container\_definition) | Container definition | +| [container\_definition\_json](#output\_container\_definition\_json) | Container definition | ## License diff --git a/modules/container-definition/main.tf b/modules/container-definition/main.tf index 682fc94c..288f7c5e 100644 --- a/modules/container-definition/main.tf +++ b/modules/container-definition/main.tf @@ -1,70 +1,73 @@ -data "aws_region" "current" {} +data "aws_region" "current" { + region = var.region +} + locals { is_not_windows = contains(["LINUX"], var.operating_system_family) - log_group_name = try(coalesce(var.cloudwatch_log_group_name, "/aws/ecs/${var.service}/${var.name}"), "") + service = var.service != null ? "/${var.service}" : "" + name = var.name != null ? "/${var.name}" : "" + log_group_name = try(coalesce(var.cloudwatch_log_group_name, "/aws/ecs${local.service}${local.name}"), "") - log_configuration = merge( + # tflint-ignore: terraform_naming_convention + logConfiguration = merge( { for k, v in { logDriver = "awslogs", options = { - awslogs-region = data.aws_region.current.name, + awslogs-region = data.aws_region.current.region, awslogs-group = try(aws_cloudwatch_log_group.this[0].name, ""), awslogs-stream-prefix = "ecs" }, - } : k => v if var.enable_cloudwatch_logging }, - var.log_configuration + } : k => v if var.create_cloudwatch_log_group }, + { for k, v in var.logConfiguration : k => v if v != null } ) - linux_parameters = var.enable_execute_command ? merge({ "initProcessEnabled" : true }, var.linux_parameters) : merge({ "initProcessEnabled" : false }, var.linux_parameters) - - health_check = length(var.health_check) > 0 ? merge({ - interval = 30, - retries = 3, - timeout = 5 - }, var.health_check) : null + # tflint-ignore: terraform_naming_convention + linuxParameters = var.enable_execute_command ? merge(var.linuxParameters, { "initProcessEnabled" : true }) : var.linuxParameters definition = { - command = length(var.command) > 0 ? var.command : null + command = var.command cpu = var.cpu - dependsOn = length(var.dependencies) > 0 ? var.dependencies : null # depends_on is a reserved word - disableNetworking = local.is_not_windows ? var.disable_networking : null - dnsSearchDomains = local.is_not_windows && length(var.dns_search_domains) > 0 ? var.dns_search_domains : null - dnsServers = local.is_not_windows && length(var.dns_servers) > 0 ? var.dns_servers : null - dockerLabels = length(var.docker_labels) > 0 ? var.docker_labels : null - dockerSecurityOptions = length(var.docker_security_options) > 0 ? var.docker_security_options : null - entrypoint = length(var.entrypoint) > 0 ? var.entrypoint : null + dependsOn = var.dependsOn + disableNetworking = local.is_not_windows ? var.disableNetworking : null + dnsSearchDomains = local.is_not_windows ? var.dnsSearchDomains : null + dnsServers = local.is_not_windows ? var.dnsServers : null + dockerLabels = var.dockerLabels + dockerSecurityOptions = var.dockerSecurityOptions + entrypoint = var.entrypoint environment = var.environment - environmentFiles = length(var.environment_files) > 0 ? var.environment_files : null + environmentFiles = var.environmentFiles essential = var.essential - extraHosts = local.is_not_windows && length(var.extra_hosts) > 0 ? var.extra_hosts : null - firelensConfiguration = length(var.firelens_configuration) > 0 ? var.firelens_configuration : null - healthCheck = local.health_check + extraHosts = local.is_not_windows ? var.extraHosts : null + firelensConfiguration = var.firelensConfiguration != null ? { for k, v in var.firelensConfiguration : k => v if v != null } : null + healthCheck = var.healthCheck != null ? { for k, v in var.healthCheck : k => v if v != null } : null hostname = var.hostname image = var.image interactive = var.interactive - links = local.is_not_windows && length(var.links) > 0 ? var.links : null - linuxParameters = local.is_not_windows && length(local.linux_parameters) > 0 ? local.linux_parameters : null - logConfiguration = length(local.log_configuration) > 0 ? local.log_configuration : null + links = local.is_not_windows ? var.links : null + linuxParameters = local.is_not_windows ? { for k, v in local.linuxParameters : k => v if v != null } : null + logConfiguration = local.logConfiguration memory = var.memory - memoryReservation = var.memory_reservation - mountPoints = var.mount_points + memoryReservation = var.memoryReservation + mountPoints = var.mountPoints name = var.name - portMappings = var.port_mappings + portMappings = var.portMappings != null ? [for p in var.portMappings : { for k, v in p : k => v if v != null }] : null privileged = local.is_not_windows ? var.privileged : null - pseudoTerminal = var.pseudo_terminal - readonlyRootFilesystem = local.is_not_windows ? var.readonly_root_filesystem : null - repositoryCredentials = length(var.repository_credentials) > 0 ? var.repository_credentials : null - resourceRequirements = length(var.resource_requirements) > 0 ? var.resource_requirements : null - secrets = length(var.secrets) > 0 ? var.secrets : null - startTimeout = var.start_timeout - stopTimeout = var.stop_timeout - systemControls = length(var.system_controls) > 0 ? var.system_controls : [] - ulimits = local.is_not_windows && length(var.ulimits) > 0 ? var.ulimits : null + pseudoTerminal = var.pseudoTerminal + restartPolicy = { for k, v in var.restartPolicy : k => v if v != null } + readonlyRootFilesystem = local.is_not_windows ? var.readonlyRootFilesystem : null + repositoryCredentials = var.repositoryCredentials + resourceRequirements = var.resourceRequirements + secrets = var.secrets + startTimeout = var.startTimeout + stopTimeout = var.stopTimeout + systemControls = var.systemControls + ulimits = local.is_not_windows ? var.ulimits : null user = local.is_not_windows ? var.user : null - volumesFrom = var.volumes_from - workingDirectory = var.working_directory + versionConsistency = var.versionConsistency + volumesFrom = var.volumesFrom + workingDirectory = var.workingDirectory } # Strip out all null values, ECS API will provide defaults in place of null/empty values @@ -74,8 +77,11 @@ locals { resource "aws_cloudwatch_log_group" "this" { count = var.create_cloudwatch_log_group && var.enable_cloudwatch_logging ? 1 : 0 + region = var.region + name = var.cloudwatch_log_group_use_name_prefix ? null : local.log_group_name name_prefix = var.cloudwatch_log_group_use_name_prefix ? "${local.log_group_name}-" : null + log_group_class = var.cloudwatch_log_group_class retention_in_days = var.cloudwatch_log_group_retention_in_days kms_key_id = var.cloudwatch_log_group_kms_key_id diff --git a/modules/container-definition/outputs.tf b/modules/container-definition/outputs.tf index 2f26967a..ea938109 100644 --- a/modules/container-definition/outputs.tf +++ b/modules/container-definition/outputs.tf @@ -7,6 +7,11 @@ output "container_definition" { value = local.container_definition } +output "container_definition_json" { + description = "Container definition" + value = jsonencode(local.container_definition) +} + ################################################################################ # CloudWatch Log Group ################################################################################ diff --git a/modules/container-definition/variables.tf b/modules/container-definition/variables.tf index 0f88b9de..c32ec336 100644 --- a/modules/container-definition/variables.tf +++ b/modules/container-definition/variables.tf @@ -1,9 +1,21 @@ +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + variable "operating_system_family" { description = "The OS family for task" type = string default = "LINUX" } +variable "tags" { + description = "A map of tags to add to all resources" + type = map(string) + default = {} +} + ################################################################################ # Container Definition ################################################################################ @@ -11,7 +23,7 @@ variable "operating_system_family" { variable "command" { description = "The command that's passed to the container" type = list(string) - default = [] + default = null } variable "cpu" { @@ -20,43 +32,49 @@ variable "cpu" { default = null } -variable "dependencies" { +# tflint-ignore: terraform_naming_convention +variable "dependsOn" { description = "The dependencies defined for container startup and shutdown. A container can contain multiple dependencies. When a dependency is defined for container startup, for container shutdown it is reversed. The condition can be one of START, COMPLETE, SUCCESS or HEALTHY" type = list(object({ condition = string containerName = string })) - default = [] + default = null } -variable "disable_networking" { +# tflint-ignore: terraform_naming_convention +variable "disableNetworking" { description = "When this parameter is true, networking is disabled within the container" type = bool default = null } -variable "dns_search_domains" { +# tflint-ignore: terraform_naming_convention +variable "dnsSearchDomains" { description = "Container DNS search domains. A list of DNS search domains that are presented to the container" type = list(string) - default = [] + default = null } -variable "dns_servers" { +# tflint-ignore: terraform_naming_convention +variable "dnsServers" { description = "Container DNS servers. This is a list of strings specifying the IP addresses of the DNS servers" type = list(string) - default = [] + default = null } -variable "docker_labels" { +# tflint-ignore: terraform_naming_convention +variable "dockerLabels" { description = "A key/value map of labels to add to the container" type = map(string) - default = {} + default = null } -variable "docker_security_options" { +# tflint-ignore: terraform_naming_convention +variable "dockerSecurityOptions" { description = "A list of strings to provide custom labels for SELinux and AppArmor multi-level security systems. This field isn't valid for containers in tasks using the Fargate launch type" type = list(string) - default = [] + default = null } variable "enable_execute_command" { @@ -80,7 +98,8 @@ variable "environment" { default = [] } -variable "environment_files" { +# tflint-ignore: terraform_naming_convention +variable "environmentFiles" { description = "A list of files containing the environment variables to pass to a container" type = list(object({ value = string @@ -95,25 +114,37 @@ variable "essential" { default = null } -variable "extra_hosts" { +# tflint-ignore: terraform_naming_convention +variable "extraHosts" { description = "A list of hostnames and IP address mappings to append to the `/etc/hosts` file on the container" type = list(object({ hostname = string ipAddress = string })) - default = [] + default = null } -variable "firelens_configuration" { +# tflint-ignore: terraform_naming_convention +variable "firelensConfiguration" { description = "The FireLens configuration for the container. This is used to specify and configure a log router for container logs. For more information, see [Custom Log Routing](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/using_firelens.html) in the Amazon Elastic Container Service Developer Guide" - type = any - default = {} + type = object({ + options = optional(map(string)) + type = optional(string) + }) + default = null } -variable "health_check" { +# tflint-ignore: terraform_naming_convention +variable "healthCheck" { description = "The container health check command and associated configuration parameters for the container. See [HealthCheck](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_HealthCheck.html)" - type = any - default = {} + type = object({ + command = optional(list(string), []) + interval = optional(number, 30) + retries = optional(number, 3) + startPeriod = optional(number) + timeout = optional(number, 5) + }) + default = null } variable "hostname" { @@ -137,19 +168,49 @@ variable "interactive" { variable "links" { description = "The links parameter allows containers to communicate with each other without the need for port mappings. This parameter is only supported if the network mode of a task definition is `bridge`" type = list(string) - default = [] + default = null } -variable "linux_parameters" { +# tflint-ignore: terraform_naming_convention +variable "linuxParameters" { description = "Linux-specific modifications that are applied to the container, such as Linux kernel capabilities. For more information see [KernelCapabilities](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_KernelCapabilities.html)" - type = any - default = {} -} - -variable "log_configuration" { + type = 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, false) + maxSwap = optional(number) + sharedMemorySize = optional(number) + swappiness = optional(number) + tmpfs = optional(list(object({ + containerPath = string + mountOptions = optional(list(string)) + size = number + }))) + }) + default = { + initProcessEnabled = false + } +} + +# tflint-ignore: terraform_naming_convention +variable "logConfiguration" { description = "The log configuration for the container. For more information see [LogConfiguration](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_LogConfiguration.html)" - type = any - default = {} + type = object({ + logDriver = optional(string) + options = optional(map(string)) + secretOptions = optional(list(object({ + name = string + valueFrom = string + }))) + }) + default = {} } variable "memory" { @@ -158,16 +219,22 @@ variable "memory" { default = null } -variable "memory_reservation" { +# tflint-ignore: terraform_naming_convention +variable "memoryReservation" { description = "The soft limit (in MiB) of memory to reserve for the container. When system memory is under heavy contention, Docker attempts to keep the container memory to this soft limit. However, your container can consume more memory when it needs to, up to either the hard limit specified with the `memory` parameter (if applicable), or all of the available memory on the container instance" type = number default = null } -variable "mount_points" { +# tflint-ignore: terraform_naming_convention +variable "mountPoints" { description = "The mount points for data volumes in your container" - type = list(any) - default = [] + type = list(object({ + containerPath = optional(string) + readOnly = optional(bool) + sourceVolume = optional(string) + })) + default = [] } variable "name" { @@ -176,10 +243,18 @@ variable "name" { default = null } -variable "port_mappings" { +# tflint-ignore: terraform_naming_convention +variable "portMappings" { description = "The list of port mappings for the container. Port mappings allow containers to access ports on the host container instance to send or receive traffic. For task definitions that use the awsvpc network mode, only specify the containerPort. The hostPort can be left blank or it must be the same value as the containerPort" - type = list(any) - default = [] + type = list(object({ + appProtocol = optional(string) + containerPort = optional(number) + containerPortRange = optional(string) + hostPort = optional(number) + name = optional(string) + protocol = optional(string) + })) + default = null } variable "privileged" { @@ -188,31 +263,50 @@ variable "privileged" { default = false } -variable "pseudo_terminal" { +# tflint-ignore: terraform_naming_convention +variable "pseudoTerminal" { description = "When this parameter is true, a `TTY` is allocated" type = bool default = false } -variable "readonly_root_filesystem" { +# tflint-ignore: terraform_naming_convention +variable "readonlyRootFilesystem" { description = "When this parameter is true, the container is given read-only access to its root file system" type = bool default = true } -variable "repository_credentials" { +# tflint-ignore: terraform_naming_convention +variable "repositoryCredentials" { description = "Container repository credentials; required when using a private repo. This map currently supports a single key; \"credentialsParameter\", which should be the ARN of a Secrets Manager's secret holding the credentials" - type = map(string) - default = {} + type = object({ + credentialsParameter = optional(string) + }) + default = null } -variable "resource_requirements" { +# tflint-ignore: terraform_naming_convention +variable "resourceRequirements" { description = "The type and amount of a resource to assign to a container. The only supported resource is a GPU" type = list(object({ type = string value = string })) - default = [] + default = null +} + +# tflint-ignore: terraform_naming_convention +variable "restartPolicy" { + description = "Container restart policy; helps overcome transient failures faster and maintain task availability" + type = object({ + enabled = optional(bool, true) + ignoredExitCodes = optional(list(number), []) + restartAttemptPeriod = optional(number) + }) + default = { + enabled = true + } } variable "secrets" { @@ -221,25 +315,31 @@ variable "secrets" { name = string valueFrom = string })) - default = [] + default = null } -variable "start_timeout" { +# tflint-ignore: terraform_naming_convention +variable "startTimeout" { description = "Time duration (in seconds) to wait before giving up on resolving dependencies for a container" type = number default = 30 } -variable "stop_timeout" { +# tflint-ignore: terraform_naming_convention +variable "stopTimeout" { description = "Time duration (in seconds) to wait before the container is forcefully killed if it doesn't exit normally on its own" type = number default = 120 } -variable "system_controls" { +# tflint-ignore: terraform_naming_convention +variable "systemControls" { description = "A list of namespaced kernel parameters to set in the container" - type = list(map(string)) - default = [] + type = list(object({ + namespace = optional(string) + value = optional(string) + })) + default = [] } variable "ulimits" { @@ -249,7 +349,7 @@ variable "ulimits" { name = string softLimit = number })) - default = [] + default = null } variable "user" { @@ -258,13 +358,25 @@ variable "user" { default = null } -variable "volumes_from" { +# tflint-ignore: terraform_naming_convention +variable "versionConsistency" { + description = "Specifies whether Amazon ECS will resolve the container image tag provided in the container definition to an image digest" + type = string + default = "disabled" +} + +# tflint-ignore: terraform_naming_convention +variable "volumesFrom" { description = "Data volumes to mount from another container" - type = list(any) - default = [] + type = list(object({ + readOnly = optional(bool) + sourceContainer = optional(string) + })) + default = [] } -variable "working_directory" { +# tflint-ignore: terraform_naming_convention +variable "workingDirectory" { description = "The working directory to run commands inside the container" type = string default = null @@ -275,9 +387,9 @@ variable "working_directory" { ################################################################################ variable "service" { - description = "The name of the service that the container definition is associated with" + description = "The name of the service that the container definition is associated with. Used in CloudWatch log group default name (if one is not provided)" type = string - default = "" + default = null } variable "enable_cloudwatch_logging" { @@ -304,10 +416,16 @@ variable "cloudwatch_log_group_use_name_prefix" { default = false } +variable "cloudwatch_log_group_class" { + description = "Specified the log class of the log group. Possible values are: `STANDARD` or `INFREQUENT_ACCESS`" + type = string + default = null +} + variable "cloudwatch_log_group_retention_in_days" { description = "Number of days to retain log events. Default is 30 days" type = number - default = 30 + default = 14 } variable "cloudwatch_log_group_kms_key_id" { @@ -315,9 +433,3 @@ variable "cloudwatch_log_group_kms_key_id" { type = string default = null } - -variable "tags" { - description = "A map of tags to add to all resources" - type = map(string) - default = {} -} diff --git a/modules/container-definition/versions.tf b/modules/container-definition/versions.tf index 682191e7..db13b0a8 100644 --- a/modules/container-definition/versions.tf +++ b/modules/container-definition/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/modules/service/README.md b/modules/service/README.md index 9296ad7f..f62647e8 100644 --- a/modules/service/README.md +++ b/modules/service/README.md @@ -32,10 +32,10 @@ module "ecs_service" { memory = 1024 essential = true image = "906394416424.dkr.ecr.us-west-2.amazonaws.com/aws-for-fluent-bit:stable" - firelens_configuration = { + firelensConfiguration = { type = "fluentbit" } - memory_reservation = 50 + memoryReservation = 50 } ecs-sample = { @@ -43,7 +43,7 @@ module "ecs_service" { memory = 1024 essential = true image = "public.ecr.aws/aws-containers/ecsdemo-frontend:776fd50" - port_mappings = [ + portMappings = [ { name = "ecs-sample" containerPort = 80 @@ -52,15 +52,15 @@ module "ecs_service" { ] # Example image used requires access to write to root filesystem - readonly_root_filesystem = false + readonlyRootFilesystem = false - dependencies = [{ + dependsOn = [{ containerName = "fluent-bit" condition = "START" }] enable_cloudwatch_logging = false - log_configuration = { + logConfiguration = { logDriver = "awsfirelens" options = { Name = "firehose" @@ -69,7 +69,13 @@ module "ecs_service" { log-driver-buffer-limit = "2097152" } } - memory_reservation = 100 + memoryReservation = 100 + + restartPolicy = { + enabled = true + ignoredExitCodes = [1] + restartAttemptPeriod = 60 + } } } @@ -94,21 +100,18 @@ module "ecs_service" { } subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] - security_group_rules = { - alb_ingress_3000 = { - type = "ingress" - from_port = 80 - to_port = 80 - protocol = "tcp" - description = "Service port" - source_security_group_id = "sg-12345678" + security_group_ingress_rules = { + alb_3000 = { + description = "Service port" + from_port = local.container_port + ip_protocol = "tcp" + referenced_security_group_id = "sg-12345678" } - egress_all = { - type = "egress" - from_port = 0 - to_port = 0 - protocol = "-1" - cidr_blocks = ["0.0.0.0/0"] + } + security_group_egress_rules = { + all = { + ip_protocol = "-1" + cidr_ipv4 = "0.0.0.0/0" } } @@ -166,14 +169,14 @@ module "ecs_service" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.66.1 | +| [terraform](#requirement\_terraform) | >= 1.5.7 | +| [aws](#requirement\_aws) | >= 6.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.66.1 | +| [aws](#provider\_aws) | >= 6.0 | ## Modules @@ -195,18 +198,23 @@ module "ecs_service" { | [aws_ecs_task_set.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_set) | resource | | [aws_iam_policy.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | | [aws_iam_policy.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_policy.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role.infrastructure_iam_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | -| [aws_iam_role_policy.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy) | resource | +| [aws_iam_role_policy_attachment.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_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 | | [aws_iam_role_policy_attachment.task_exec_additional](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_iam_role_policy_attachment.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_role_policy_attachment.tasks_internal](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_security_group.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | -| [aws_security_group_rule.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule) | resource | +| [aws_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 | | [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | -| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ecs_task_definition) | data source | +| [aws_iam_policy_document.infrastructure_iam_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.service](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.service_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -221,37 +229,40 @@ module "ecs_service" { | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [alarms](#input\_alarms) | Information about the CloudWatch alarms | `any` | `{}` | no | +| [alarms](#input\_alarms) | Information about the CloudWatch alarms |
object({
alarm_names = list(string)
enable = optional(bool, true)
rollback = optional(bool, true)
})
| `null` | no | | [assign\_public\_ip](#input\_assign\_public\_ip) | Assign a public IP address to the ENI (Fargate launch type only) | `bool` | `false` | no | | [autoscaling\_max\_capacity](#input\_autoscaling\_max\_capacity) | Maximum number of tasks to run in your service | `number` | `10` | no | | [autoscaling\_min\_capacity](#input\_autoscaling\_min\_capacity) | Minimum number of tasks to run in your service | `number` | `1` | no | -| [autoscaling\_policies](#input\_autoscaling\_policies) | Map of autoscaling policies to create for the service | `any` |
{
"cpu": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageCPUUtilization"
}
}
},
"memory": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageMemoryUtilization"
}
}
}
}
| no | -| [autoscaling\_scheduled\_actions](#input\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions to create for the service | `any` | `{}` | no | -| [capacity\_provider\_strategy](#input\_capacity\_provider\_strategy) | Capacity provider strategies to use for the service. Can be one or more | `any` | `{}` | no | +| [autoscaling\_policies](#input\_autoscaling\_policies) | Map of autoscaling policies to create for the service |
map(object({
name = optional(string) # Will fall back to the key name if not provided
policy_type = optional(string, "TargetTrackingScaling")
step_scaling_policy_configuration = optional(object({
adjustment_type = optional(string)
cooldown = optional(number)
metric_aggregation_type = optional(string)
min_adjustment_magnitude = optional(number)
step_adjustments = 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, 300)
scale_out_cooldown = optional(number, 60)
target_value = optional(number, 75)
}))
}))
|
{
"cpu": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageCPUUtilization"
}
}
},
"memory": {
"policy_type": "TargetTrackingScaling",
"target_tracking_scaling_policy_configuration": {
"predefined_metric_specification": {
"predefined_metric_type": "ECSServiceAverageMemoryUtilization"
}
}
}
}
| no | +| [autoscaling\_scheduled\_actions](#input\_autoscaling\_scheduled\_actions) | Map of autoscaling scheduled actions to create for the service |
map(object({
name = optional(string)
min_capacity = number
max_capacity = number
schedule = string
start_time = optional(string)
end_time = optional(string)
timezone = optional(string)
}))
| `null` | no | +| [availability\_zone\_rebalancing](#input\_availability\_zone\_rebalancing) | ECS automatically redistributes tasks within a service across Availability Zones (AZs) to mitigate the risk of impaired application availability due to underlying infrastructure failures and task lifecycle activities. The valid values are `ENABLED` and `DISABLED`. Defaults to `DISABLED` | `string` | `null` | no | +| [capacity\_provider\_strategy](#input\_capacity\_provider\_strategy) | Capacity provider strategies to use for the service. Can be one or more |
map(object({
base = optional(number)
capacity_provider = string
weight = optional(number)
}))
| `null` | no | | [cluster\_arn](#input\_cluster\_arn) | ARN of the ECS cluster where the resources will be provisioned | `string` | `""` | no | -| [container\_definition\_defaults](#input\_container\_definition\_defaults) | A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions` | `any` | `{}` | no | -| [container\_definitions](#input\_container\_definitions) | A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document | `any` | `{}` | no | +| [container\_definition\_defaults](#input\_container\_definition\_defaults) | A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions` |
object({
create = optional(bool)
operating_system_family = optional(string)
tags = optional(map(string))

# Container definition
command = optional(list(string))
cpu = optional(number)
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, false) Set in standalone variable
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)
})
| `{}` | no | +| [container\_definitions](#input\_container\_definitions) | A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document |
map(object({
create = optional(bool, true)
operating_system_family = optional(string, "LINUX")
tags = optional(map(string), {})

# Container definition
command = optional(list(string))
cpu = optional(number)
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, false) Set in standalone variable
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, 30)
retries = optional(number, 3)
startPeriod = optional(number)
timeout = optional(number, 5)
}))
hostname = optional(string)
image = optional(string)
interactive = optional(bool, false)
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, false)
maxSwap = optional(number)
sharedMemorySize = optional(number)
swappiness = optional(number)
tmpfs = optional(list(object({
containerPath = string
mountOptions = optional(list(string))
size = number
})))
}),
# Default
{
initProcessEnabled = false
}
)
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, false)
pseudoTerminal = optional(bool, false)
readonlyRootFilesystem = optional(bool, true)
repositoryCredentials = optional(object({
credentialsParameter = optional(string)
}))
resourceRequirements = optional(list(object({
type = string
value = string
})))
restartPolicy = optional(object({
enabled = optional(bool, true)
ignoredExitCodes = optional(list(number))
restartAttemptPeriod = optional(number)
}),
# Default
{
enabled = true
}
)
secrets = optional(list(object({
name = string
valueFrom = string
})))
startTimeout = optional(number, 30)
stopTimeout = optional(number, 120)
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, "disabled")
volumesFrom = optional(list(object({
readOnly = optional(bool)
sourceContainer = optional(string)
})), [])
workingDirectory = optional(string)

# Cloudwatch Log Group
service = optional(string, "")
enable_cloudwatch_logging = optional(bool, true)
create_cloudwatch_log_group = optional(bool, true)
cloudwatch_log_group_name = optional(string)
cloudwatch_log_group_use_name_prefix = optional(bool, false)
cloudwatch_log_group_class = optional(string)
cloudwatch_log_group_retention_in_days = optional(number, 14)
cloudwatch_log_group_kms_key_id = optional(string)
}))
| `{}` | no | | [cpu](#input\_cpu) | Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required | `number` | `1024` | no | | [create](#input\_create) | Determines whether resources will be created (affects all resources) | `bool` | `true` | no | | [create\_iam\_role](#input\_create\_iam\_role) | Determines whether the ECS service IAM role should be created | `bool` | `true` | no | +| [create\_infrastructure\_iam\_role](#input\_create\_infrastructure\_iam\_role) | Determines whether the ECS infrastructure IAM role should be created | `bool` | `true` | no | | [create\_security\_group](#input\_create\_security\_group) | Determines if a security group is created | `bool` | `true` | no | | [create\_service](#input\_create\_service) | Determines whether service resource will be created (set to `false` in case you want to create task definition only) | `bool` | `true` | no | | [create\_task\_definition](#input\_create\_task\_definition) | Determines whether to create a task definition or use existing/provided | `bool` | `true` | no | | [create\_task\_exec\_iam\_role](#input\_create\_task\_exec\_iam\_role) | Determines whether the ECS task definition IAM role should be created | `bool` | `true` | no | | [create\_task\_exec\_policy](#input\_create\_task\_exec\_policy) | Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters | `bool` | `true` | no | | [create\_tasks\_iam\_role](#input\_create\_tasks\_iam\_role) | Determines whether the ECS tasks IAM role should be created | `bool` | `true` | no | -| [deployment\_circuit\_breaker](#input\_deployment\_circuit\_breaker) | Configuration block for deployment circuit breaker | `any` | `{}` | no | -| [deployment\_controller](#input\_deployment\_controller) | Configuration block for deployment controller configuration | `any` | `{}` | no | +| [deployment\_circuit\_breaker](#input\_deployment\_circuit\_breaker) | Configuration block for deployment circuit breaker |
object({
enable = bool
rollback = bool
})
| `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 | | [enable\_autoscaling](#input\_enable\_autoscaling) | Determines whether to enable autoscaling for the service | `bool` | `true` | no | | [enable\_ecs\_managed\_tags](#input\_enable\_ecs\_managed\_tags) | Specifies whether to enable Amazon ECS managed tags for the tasks within the service | `bool` | `true` | no | | [enable\_execute\_command](#input\_enable\_execute\_command) | Specifies whether to enable Amazon ECS Exec for the tasks within the service | `bool` | `false` | no | -| [ephemeral\_storage](#input\_ephemeral\_storage) | The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate | `any` | `{}` | no | +| [enable\_fault\_injection](#input\_enable\_fault\_injection) | Enables fault injection and allows for fault injection requests to be accepted from the task's containers. Default is `false` | `bool` | `null` | no | +| [ephemeral\_storage](#input\_ephemeral\_storage) | The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate |
object({
size_in_gib = number
})
| `null` | no | | [external\_id](#input\_external\_id) | The external ID associated with the task set | `string` | `null` | no | | [family](#input\_family) | A unique name for your task definition | `string` | `null` | no | -| [force\_delete](#input\_force\_delete) | Whether to allow deleting the task set without waiting for scaling down to 0 | `bool` | `null` | no | +| [force\_delete](#input\_force\_delete) | Enable to delete a service even if it wasn't scaled down to zero tasks. It's only necessary to use this if the service uses the `REPLICA` scheduling strategy | `bool` | `null` | no | | [force\_new\_deployment](#input\_force\_new\_deployment) | Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates | `bool` | `true` | no | | [health\_check\_grace\_period\_seconds](#input\_health\_check\_grace\_period\_seconds) | Seconds to ignore failing load balancer health checks on newly instantiated tasks to prevent premature shutdown, up to 2147483647. Only valid for services configured to use load balancers | `number` | `null` | no | | [iam\_role\_arn](#input\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | @@ -259,41 +270,49 @@ module "ecs_service" { | [iam\_role\_name](#input\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | | [iam\_role\_path](#input\_iam\_role\_path) | IAM role path | `string` | `null` | no | | [iam\_role\_permissions\_boundary](#input\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | -| [iam\_role\_statements](#input\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [iam\_role\_statements](#input\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage |
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
})))
}))
| `null` | no | | [iam\_role\_tags](#input\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | | [iam\_role\_use\_name\_prefix](#input\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`iam_role_name`) is used as a prefix | `bool` | `true` | no | | [ignore\_task\_definition\_changes](#input\_ignore\_task\_definition\_changes) | Whether changes to service `task_definition` changes should be ignored | `bool` | `false` | no | -| [inference\_accelerator](#input\_inference\_accelerator) | Configuration block(s) with Inference Accelerators settings | `any` | `{}` | no | +| [infrastructure\_iam\_role\_arn](#input\_infrastructure\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | 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\_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\_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 | | [ipc\_mode](#input\_ipc\_mode) | IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none` | `string` | `null` | no | | [launch\_type](#input\_launch\_type) | Launch type on which to run your service. The valid values are `EC2`, `FARGATE`, and `EXTERNAL`. Defaults to `FARGATE` | `string` | `"FARGATE"` | no | -| [load\_balancer](#input\_load\_balancer) | Configuration block for load balancers | `any` | `{}` | no | +| [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)
}))
| `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 | | [network\_mode](#input\_network\_mode) | Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host` | `string` | `"awsvpc"` | no | -| [ordered\_placement\_strategy](#input\_ordered\_placement\_strategy) | Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence | `any` | `{}` | no | +| [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 | | [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 | `any` | `{}` | 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 | `any` | `{}` | 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 | -| [runtime\_platform](#input\_runtime\_platform) | Configuration block for `runtime_platform` that containers in your task may use | `any` |
{
"cpu_architecture": "X86_64",
"operating_system_family": "LINUX"
}
| no | -| [scale](#input\_scale) | A floating-point percentage of the desired number of tasks to place and keep running in the task set | `any` | `{}` | no | +| [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 | | [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)
}))
| `{}` | no | | [security\_group\_ids](#input\_security\_group\_ids) | List of security groups to associate with the task or service | `list(string)` | `[]` | 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\_rules](#input\_security\_group\_rules) | Security group rules to add to the security group created | `any` | `{}` | no | | [security\_group\_tags](#input\_security\_group\_tags) | A map of additional tags to add to the security group created | `map(string)` | `{}` | no | | [security\_group\_use\_name\_prefix](#input\_security\_group\_use\_name\_prefix) | Determines whether the security group name (`security_group_name`) is used as a prefix | `bool` | `true` | no | -| [service\_connect\_configuration](#input\_service\_connect\_configuration) | The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace | `any` | `{}` | no | -| [service\_registries](#input\_service\_registries) | Service discovery registries for the service | `any` | `{}` | no | +| [service\_connect\_configuration](#input\_service\_connect\_configuration) | The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace |
object({
enabled = optional(bool, true)
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
}))
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)
}))
})))
})
| `null` | no | +| [service\_registries](#input\_service\_registries) | Service discovery registries for the service |
object({
container_name = optional(string)
container_port = optional(number)
port = optional(number)
registry_arn = string
})
| `null` | no | | [service\_tags](#input\_service\_tags) | A map of additional tags to add to the service | `map(string)` | `{}` | no | | [skip\_destroy](#input\_skip\_destroy) | If true, the task is not deleted when the service is deleted | `bool` | `null` | no | | [subnet\_ids](#input\_subnet\_ids) | List of subnets to associate with the task or service | `list(string)` | `[]` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | | [task\_definition\_arn](#input\_task\_definition\_arn) | Existing task definition ARN. Required when `create_task_definition` is `false` | `string` | `null` | no | -| [task\_definition\_placement\_constraints](#input\_task\_definition\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service | `any` | `{}` | no | +| [task\_definition\_placement\_constraints](#input\_task\_definition\_placement\_constraints) | Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service |
map(object({
expression = optional(string)
type = string
}))
| `null` | no | | [task\_exec\_iam\_policy\_path](#input\_task\_exec\_iam\_policy\_path) | Path for the iam role | `string` | `null` | no | | [task\_exec\_iam\_role\_arn](#input\_task\_exec\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | | [task\_exec\_iam\_role\_description](#input\_task\_exec\_iam\_role\_description) | Description of the role | `string` | `null` | no | @@ -304,22 +323,25 @@ module "ecs_service" { | [task\_exec\_iam\_role\_policies](#input\_task\_exec\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | | [task\_exec\_iam\_role\_tags](#input\_task\_exec\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | | [task\_exec\_iam\_role\_use\_name\_prefix](#input\_task\_exec\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [task\_exec\_iam\_statements](#input\_task\_exec\_iam\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | -| [task\_exec\_secret\_arns](#input\_task\_exec\_secret\_arns) | List of SecretsManager secret ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:secretsmanager:*:*:secret:*"
]
| no | -| [task\_exec\_ssm\_param\_arns](#input\_task\_exec\_ssm\_param\_arns) | List of SSM parameter ARNs the task execution role will be permitted to get/read | `list(string)` |
[
"arn:aws:ssm:*:*:parameter/*"
]
| no | +| [task\_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 |
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
})))
}))
| `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 | | [task\_tags](#input\_task\_tags) | A map of additional tags to add to the task definition/set created | `map(string)` | `{}` | no | | [tasks\_iam\_role\_arn](#input\_tasks\_iam\_role\_arn) | Existing IAM role ARN | `string` | `null` | no | | [tasks\_iam\_role\_description](#input\_tasks\_iam\_role\_description) | Description of the role | `string` | `null` | no | | [tasks\_iam\_role\_name](#input\_tasks\_iam\_role\_name) | Name to use on IAM role created | `string` | `null` | no | | [tasks\_iam\_role\_path](#input\_tasks\_iam\_role\_path) | IAM role path | `string` | `null` | no | | [tasks\_iam\_role\_permissions\_boundary](#input\_tasks\_iam\_role\_permissions\_boundary) | ARN of the policy that is used to set the permissions boundary for the IAM role | `string` | `null` | no | -| [tasks\_iam\_role\_policies](#input\_tasks\_iam\_role\_policies) | Map of IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | -| [tasks\_iam\_role\_statements](#input\_tasks\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage | `any` | `{}` | no | +| [tasks\_iam\_role\_policies](#input\_tasks\_iam\_role\_policies) | Map of additioanl IAM role policy ARNs to attach to the IAM role | `map(string)` | `{}` | no | +| [tasks\_iam\_role\_statements](#input\_tasks\_iam\_role\_statements) | A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage |
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
})))
}))
| `null` | no | | [tasks\_iam\_role\_tags](#input\_tasks\_iam\_role\_tags) | A map of additional tags to add to the IAM role created | `map(string)` | `{}` | no | | [tasks\_iam\_role\_use\_name\_prefix](#input\_tasks\_iam\_role\_use\_name\_prefix) | Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix | `bool` | `true` | no | -| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the service | `map(string)` | `{}` | no | -| [triggers](#input\_triggers) | Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `plantimestamp()` | `any` | `{}` | no | -| [volume](#input\_volume) | Configuration block for volumes that containers in your task may use | `any` | `{}` | no | +| [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the service |
object({
create = optional(string)
update = optional(string)
delete = optional(string)
})
| `null` | no | +| [track\_latest](#input\_track\_latest) | Whether should track latest `ACTIVE` task definition on AWS or the one created with the resource stored in state. Default is `false`. Useful in the event the task definition is modified outside of this resource | `bool` | `true` | no | +| [triggers](#input\_triggers) | Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()` | `map(string)` | `null` | no | +| [volume](#input\_volume) | Configuration block for volumes that containers in your task may use |
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)
}))
| `null` | no | +| [volume\_configuration](#input\_volume\_configuration) | Configuration for a volume specified in the task definition as a volume that is configured at launch time |
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, "TASK_DEFINITION")
resource_type = string
tags = optional(map(string))
})))
throughput = optional(number)
volume_type = optional(string)
})
})
| `null` | no | +| [vpc\_lattice\_configurations](#input\_vpc\_lattice\_configurations) | The VPC Lattice configuration for your service that allows Lattice to connect, secure, and monitor your service across multiple accounts and VPCs |
object({
role_arn = string
target_group_arn = string
port_name = string
})
| `null` | no | | [wait\_for\_steady\_state](#input\_wait\_for\_steady\_state) | If true, Terraform will wait for the service to reach a steady state before continuing. Default is `false` | `bool` | `null` | no | | [wait\_until\_stable](#input\_wait\_until\_stable) | Whether terraform should wait until the task set has reached `STEADY_STATE` | `bool` | `null` | no | | [wait\_until\_stable\_timeout](#input\_wait\_until\_stable\_timeout) | Wait timeout for task set to reach `STEADY_STATE`. Valid time units include `ns`, `us` (or µs), `ms`, `s`, `m`, and `h`. Default `10m` | `string` | `null` | no | @@ -335,12 +357,13 @@ module "ecs_service" { | [iam\_role\_name](#output\_iam\_role\_name) | Service IAM role name | | [iam\_role\_unique\_id](#output\_iam\_role\_unique\_id) | Stable and unique string identifying the service IAM role | | [id](#output\_id) | ARN that identifies the service | +| [infrastructure\_iam\_role\_arn](#output\_infrastructure\_iam\_role\_arn) | Infrastructure IAM role ARN | +| [infrastructure\_iam\_role\_name](#output\_infrastructure\_iam\_role\_name) | Infrastructure IAM role name | | [name](#output\_name) | Name of the service | | [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | | [security\_group\_id](#output\_security\_group\_id) | ID of the security group | | [task\_definition\_arn](#output\_task\_definition\_arn) | Full ARN of the Task Definition (including both `family` and `revision`) | | [task\_definition\_family](#output\_task\_definition\_family) | The unique name of the task definition | -| [task\_definition\_family\_revision](#output\_task\_definition\_family\_revision) | The family and revision (family:revision) of the task definition | | [task\_definition\_revision](#output\_task\_definition\_revision) | Revision of the task in a particular family | | [task\_exec\_iam\_role\_arn](#output\_task\_exec\_iam\_role\_arn) | Task execution IAM role ARN | | [task\_exec\_iam\_role\_name](#output\_task\_exec\_iam\_role\_name) | Task execution IAM role name | diff --git a/modules/service/main.tf b/modules/service/main.tf index c69c12da..00d6fa25 100644 --- a/modules/service/main.tf +++ b/modules/service/main.tf @@ -1,11 +1,17 @@ -data "aws_region" "current" {} -data "aws_partition" "current" {} -data "aws_caller_identity" "current" {} +data "aws_region" "current" { + 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 = data.aws_caller_identity.current.account_id - partition = data.aws_partition.current.partition - region = data.aws_region.current.name + 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, "") } ################################################################################ @@ -31,31 +37,35 @@ locals { resource "aws_ecs_service" "this" { count = local.create_service && !var.ignore_task_definition_changes ? 1 : 0 + region = var.region + dynamic "alarms" { - for_each = length(var.alarms) > 0 ? [var.alarms] : [] + for_each = var.alarms != null ? [var.alarms] : [] content { alarm_names = alarms.value.alarm_names - enable = try(alarms.value.enable, true) - rollback = try(alarms.value.rollback, true) + enable = alarms.value.enable + rollback = alarms.value.rollback } } + availability_zone_rebalancing = var.availability_zone_rebalancing + dynamic "capacity_provider_strategy" { # Set by task set if deployment controller is external - for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + for_each = !local.is_external_deployment && var.capacity_provider_strategy != null ? var.capacity_provider_strategy : {} content { - base = try(capacity_provider_strategy.value.base, null) + base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) + weight = capacity_provider_strategy.value.weight } } cluster = var.cluster_arn dynamic "deployment_circuit_breaker" { - for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + for_each = var.deployment_circuit_breaker != null ? [var.deployment_circuit_breaker] : [] content { enable = deployment_circuit_breaker.value.enable @@ -64,10 +74,10 @@ resource "aws_ecs_service" "this" { } dynamic "deployment_controller" { - for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + for_each = var.deployment_controller != null ? [var.deployment_controller] : [] content { - type = try(deployment_controller.value.type, null) + type = deployment_controller.value.type } } @@ -76,20 +86,21 @@ resource "aws_ecs_service" "this" { desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count enable_ecs_managed_tags = var.enable_ecs_managed_tags enable_execute_command = var.enable_execute_command + force_delete = var.force_delete force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment health_check_grace_period_seconds = var.health_check_grace_period_seconds iam_role = local.iam_role_arn - launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + launch_type = local.is_external_deployment || var.capacity_provider_strategy != null ? null : var.launch_type dynamic "load_balancer" { # Set by task set if deployment controller is external - for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + for_each = var.load_balancer != null ? var.load_balancer : {} content { container_name = load_balancer.value.container_name container_port = load_balancer.value.container_port - elb_name = try(load_balancer.value.elb_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) + elb_name = load_balancer.value.elb_name + target_group_arn = load_balancer.value.target_group_arn } } @@ -107,42 +118,43 @@ resource "aws_ecs_service" "this" { } dynamic "ordered_placement_strategy" { - for_each = var.ordered_placement_strategy + for_each = var.ordered_placement_strategy != null ? var.ordered_placement_strategy : {} content { - field = try(ordered_placement_strategy.value.field, null) + field = ordered_placement_strategy.value.field type = ordered_placement_strategy.value.type } } dynamic "placement_constraints" { - for_each = var.placement_constraints + for_each = var.placement_constraints != null ? var.placement_constraints : {} content { - expression = try(placement_constraints.value.expression, null) + expression = placement_constraints.value.expression type = placement_constraints.value.type } } # Set by task set if deployment controller is external platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + propagate_tags = var.propagate_tags scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy dynamic "service_connect_configuration" { - for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + for_each = var.service_connect_configuration != null ? [var.service_connect_configuration] : [] content { - enabled = try(service_connect_configuration.value.enabled, true) + enabled = service_connect_configuration.value.enabled dynamic "log_configuration" { - for_each = try([service_connect_configuration.value.log_configuration], []) + for_each = service_connect_configuration.value.log_configuration != null ? [service_connect_configuration.value.log_configuration] : [] content { - log_driver = try(log_configuration.value.log_driver, null) - options = try(log_configuration.value.options, null) + log_driver = log_configuration.value.log_driver + options = log_configuration.value.options dynamic "secret_option" { - for_each = try(log_configuration.value.secret_option, []) + for_each = log_configuration.value.secret_option != null ? log_configuration.value.secret_option : [] content { name = secret_option.value.name @@ -152,25 +164,50 @@ resource "aws_ecs_service" "this" { } } - namespace = lookup(service_connect_configuration.value, "namespace", null) + namespace = service_connect_configuration.value.namespace dynamic "service" { - for_each = try([service_connect_configuration.value.service], []) + for_each = service_connect_configuration.value.service != null ? service_connect_configuration.value.service : [] content { - dynamic "client_alias" { - for_each = try([service.value.client_alias], []) + for_each = service.value.client_alias != null ? [service.value.client_alias] : [] content { - dns_name = try(client_alias.value.dns_name, null) + dns_name = client_alias.value.dns_name port = client_alias.value.port } } - discovery_name = try(service.value.discovery_name, null) - ingress_port_override = try(service.value.ingress_port_override, null) + discovery_name = service.value.discovery_name + ingress_port_override = service.value.ingress_port_override port_name = service.value.port_name + + dynamic "timeout" { + for_each = service.value.timeout != null ? [service.value.timeout] : [] + + content { + idle_timeout_seconds = timeout.value.idle_timeout_seconds + per_request_timeout_seconds = timeout.value.per_request_timeout_seconds + } + } + + dynamic "tls" { + for_each = service.value.tls != null ? [service.value.tls] : [] + + content { + dynamic "issuer_cert_authority" { + for_each = tls.value.issuer_cert_authority + + content { + aws_pca_authority_arn = issuer_cert_authority.value.aws_pca_authority_arn + } + } + + kms_key = tls.value.kms_key + role_arn = tls.value.role_arn + } + } } } } @@ -178,31 +215,80 @@ resource "aws_ecs_service" "this" { dynamic "service_registries" { # Set by task set if deployment controller is external - for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + for_each = var.service_registries != null && !local.is_external_deployment ? [var.service_registries] : [] content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) + container_name = service_registries.value.container_name + container_port = service_registries.value.container_port + port = service_registries.value.port registry_arn = service_registries.value.registry_arn } } - task_definition = local.task_definition - triggers = var.triggers + tags = merge(var.tags, var.service_tags) + task_definition = local.task_definition + triggers = var.triggers + + dynamic "volume_configuration" { + for_each = var.volume_configuration != null ? [var.volume_configuration] : [] + + content { + name = try(volume_configuration.value.name, volume_configuration.key) + + dynamic "managed_ebs_volume" { + for_each = [volume_configuration.value.managed_ebs_volume] + + content { + encrypted = managed_ebs_volume.value.encrypted + file_system_type = managed_ebs_volume.value.file_system_type + iops = managed_ebs_volume.value.iops + kms_key_id = managed_ebs_volume.value.kms_key_id + role_arn = local.infrastructure_iam_role_arn + size_in_gb = managed_ebs_volume.value.size_in_gb + snapshot_id = managed_ebs_volume.value.snapshot_id + + dynamic "tag_specifications" { + for_each = managed_ebs_volume.value.tag_specifications != null ? managed_ebs_volume.value.tag_specifications : [] + + content { + resource_type = tag_specifications.value.resource_type + propagate_tags = tag_specifications.value.propagate_tags + tags = tag_specifications.value.tags + } + } + + throughput = managed_ebs_volume.value.throughput + volume_type = managed_ebs_volume.value.volume_type + } + } + } + } + + dynamic "vpc_lattice_configurations" { + for_each = var.vpc_lattice_configurations != null ? [var.vpc_lattice_configurations] : [] + + content { + role_arn = local.infrastructure_iam_role_arn + target_group_arn = vpc_lattice_configurations.value.target_group_arn + port_name = vpc_lattice_configurations.value.port_name + } + } + wait_for_steady_state = var.wait_for_steady_state - propagate_tags = var.propagate_tags - tags = merge(var.tags, var.service_tags) + dynamic "timeouts" { + for_each = var.timeouts != null ? [var.timeouts] : [] - timeouts { - create = try(var.timeouts.create, null) - update = try(var.timeouts.update, null) - delete = try(var.timeouts.delete, null) + content { + create = timeouts.value.create + update = timeouts.value.update + delete = timeouts.value.delete + } } depends_on = [ - aws_iam_role_policy_attachment.service + aws_iam_role_policy_attachment.service, + aws_iam_role_policy_attachment.infrastructure_iam_role_ebs_policy, ] lifecycle { @@ -219,31 +305,35 @@ resource "aws_ecs_service" "this" { resource "aws_ecs_service" "ignore_task_definition" { count = local.create_service && var.ignore_task_definition_changes ? 1 : 0 + region = var.region + dynamic "alarms" { - for_each = length(var.alarms) > 0 ? [var.alarms] : [] + for_each = var.alarms != null ? [var.alarms] : [] content { alarm_names = alarms.value.alarm_names - enable = try(alarms.value.enable, true) - rollback = try(alarms.value.rollback, true) + enable = alarms.value.enable + rollback = alarms.value.rollback } } + availability_zone_rebalancing = var.availability_zone_rebalancing + dynamic "capacity_provider_strategy" { # Set by task set if deployment controller is external - for_each = { for k, v in var.capacity_provider_strategy : k => v if !local.is_external_deployment } + for_each = !local.is_external_deployment && var.capacity_provider_strategy != null ? var.capacity_provider_strategy : {} content { - base = try(capacity_provider_strategy.value.base, null) + base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) + weight = capacity_provider_strategy.value.weight } } cluster = var.cluster_arn dynamic "deployment_circuit_breaker" { - for_each = length(var.deployment_circuit_breaker) > 0 ? [var.deployment_circuit_breaker] : [] + for_each = var.deployment_circuit_breaker != null ? [var.deployment_circuit_breaker] : [] content { enable = deployment_circuit_breaker.value.enable @@ -252,10 +342,10 @@ resource "aws_ecs_service" "ignore_task_definition" { } dynamic "deployment_controller" { - for_each = length(var.deployment_controller) > 0 ? [var.deployment_controller] : [] + for_each = var.deployment_controller != null ? [var.deployment_controller] : [] content { - type = try(deployment_controller.value.type, null) + type = deployment_controller.value.type } } @@ -264,20 +354,21 @@ resource "aws_ecs_service" "ignore_task_definition" { desired_count = local.is_daemon || local.is_external_deployment ? null : var.desired_count enable_ecs_managed_tags = var.enable_ecs_managed_tags enable_execute_command = var.enable_execute_command + force_delete = var.force_delete force_new_deployment = local.is_external_deployment ? null : var.force_new_deployment health_check_grace_period_seconds = var.health_check_grace_period_seconds iam_role = local.iam_role_arn - launch_type = local.is_external_deployment || length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + launch_type = local.is_external_deployment || var.capacity_provider_strategy != null ? null : var.launch_type dynamic "load_balancer" { # Set by task set if deployment controller is external - for_each = { for k, v in var.load_balancer : k => v if !local.is_external_deployment } + for_each = var.load_balancer != null ? var.load_balancer : {} content { container_name = load_balancer.value.container_name container_port = load_balancer.value.container_port - elb_name = try(load_balancer.value.elb_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) + elb_name = load_balancer.value.elb_name + target_group_arn = load_balancer.value.target_group_arn } } @@ -295,42 +386,43 @@ resource "aws_ecs_service" "ignore_task_definition" { } dynamic "ordered_placement_strategy" { - for_each = var.ordered_placement_strategy + for_each = var.ordered_placement_strategy != null ? var.ordered_placement_strategy : {} content { - field = try(ordered_placement_strategy.value.field, null) + field = ordered_placement_strategy.value.field type = ordered_placement_strategy.value.type } } dynamic "placement_constraints" { - for_each = var.placement_constraints + for_each = var.placement_constraints != null ? var.placement_constraints : {} content { - expression = try(placement_constraints.value.expression, null) + expression = placement_constraints.value.expression type = placement_constraints.value.type } } # Set by task set if deployment controller is external platform_version = local.is_fargate && !local.is_external_deployment ? var.platform_version : null + propagate_tags = var.propagate_tags scheduling_strategy = local.is_fargate ? "REPLICA" : var.scheduling_strategy dynamic "service_connect_configuration" { - for_each = length(var.service_connect_configuration) > 0 ? [var.service_connect_configuration] : [] + for_each = var.service_connect_configuration != null ? [var.service_connect_configuration] : [] content { - enabled = try(service_connect_configuration.value.enabled, true) + enabled = service_connect_configuration.value.enabled dynamic "log_configuration" { - for_each = try([service_connect_configuration.value.log_configuration], []) + for_each = service_connect_configuration.value.log_configuration != null ? [service_connect_configuration.value.log_configuration] : [] content { - log_driver = try(log_configuration.value.log_driver, null) - options = try(log_configuration.value.options, null) + log_driver = log_configuration.value.log_driver + options = log_configuration.value.options dynamic "secret_option" { - for_each = try(log_configuration.value.secret_option, []) + for_each = log_configuration.value.secret_option != null ? log_configuration.value.secret_option : [] content { name = secret_option.value.name @@ -340,25 +432,50 @@ resource "aws_ecs_service" "ignore_task_definition" { } } - namespace = lookup(service_connect_configuration.value, "namespace", null) + namespace = service_connect_configuration.value.namespace dynamic "service" { - for_each = try([service_connect_configuration.value.service], []) + for_each = service_connect_configuration.value.service != null ? service_connect_configuration.value.service : [] content { - dynamic "client_alias" { - for_each = try([service.value.client_alias], []) + for_each = service.value.client_alias != null ? [service.value.client_alias] : [] content { - dns_name = try(client_alias.value.dns_name, null) + dns_name = client_alias.value.dns_name port = client_alias.value.port } } - discovery_name = try(service.value.discovery_name, null) - ingress_port_override = try(service.value.ingress_port_override, null) + discovery_name = service.value.discovery_name + ingress_port_override = service.value.ingress_port_override port_name = service.value.port_name + + dynamic "timeout" { + for_each = service.value.timeout != null ? [service.value.timeout] : [] + + content { + idle_timeout_seconds = timeout.value.idle_timeout_seconds + per_request_timeout_seconds = timeout.value.per_request_timeout_seconds + } + } + + dynamic "tls" { + for_each = service.value.tls != null ? [service.value.tls] : [] + + content { + dynamic "issuer_cert_authority" { + for_each = tls.value.issuer_cert_authority + + content { + aws_pca_authority_arn = issuer_cert_authority.value.aws_pca_authority_arn + } + } + + kms_key = tls.value.kms_key + role_arn = tls.value.role_arn + } + } } } } @@ -366,31 +483,80 @@ resource "aws_ecs_service" "ignore_task_definition" { dynamic "service_registries" { # Set by task set if deployment controller is external - for_each = length(var.service_registries) > 0 ? [{ for k, v in var.service_registries : k => v if !local.is_external_deployment }] : [] + for_each = var.service_registries != null && !local.is_external_deployment ? [var.service_registries] : [] content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) + container_name = service_registries.value.container_name + container_port = service_registries.value.container_port + port = service_registries.value.port registry_arn = service_registries.value.registry_arn } } - task_definition = local.task_definition - triggers = var.triggers + tags = merge(var.tags, var.service_tags) + task_definition = local.task_definition + triggers = var.triggers + + dynamic "volume_configuration" { + for_each = var.volume_configuration != null ? [var.volume_configuration] : [] + + content { + name = volume_configuration.value.name + + dynamic "managed_ebs_volume" { + for_each = [volume_configuration.value.managed_ebs_volume] + + content { + encrypted = managed_ebs_volume.value.encrypted + file_system_type = managed_ebs_volume.value.file_system_type + iops = managed_ebs_volume.value.iops + kms_key_id = managed_ebs_volume.value.kms_key_id + role_arn = local.infrastructure_iam_role_arn + size_in_gb = managed_ebs_volume.value.size_in_gb + snapshot_id = managed_ebs_volume.value.snapshot_id + + dynamic "tag_specifications" { + for_each = managed_ebs_volume.value.tag_specifications != null ? managed_ebs_volume.value.tag_specifications : [] + + content { + resource_type = tag_specifications.value.resource_type + propagate_tags = tag_specifications.value.propagate_tags + tags = tag_specifications.value.tags + } + } + + throughput = managed_ebs_volume.value.throughput + volume_type = managed_ebs_volume.value.volume_type + } + } + } + } + + dynamic "vpc_lattice_configurations" { + for_each = var.vpc_lattice_configurations != null ? [var.vpc_lattice_configurations] : [] + + content { + role_arn = local.infrastructure_iam_role_arn + target_group_arn = vpc_lattice_configurations.value.target_group_arn + port_name = vpc_lattice_configurations.value.port_name + } + } + wait_for_steady_state = var.wait_for_steady_state - propagate_tags = var.propagate_tags - tags = merge(var.tags, var.service_tags) + dynamic "timeouts" { + for_each = var.timeouts != null ? [var.timeouts] : [] - timeouts { - create = try(var.timeouts.create, null) - update = try(var.timeouts.update, null) - delete = try(var.timeouts.delete, null) + content { + create = timeouts.value.create + update = timeouts.value.update + delete = timeouts.value.delete + } } depends_on = [ - aws_iam_role_policy_attachment.service + aws_iam_role_policy_attachment.service, + aws_iam_role_policy_attachment.infrastructure_iam_role_ebs_policy, ] lifecycle { @@ -408,7 +574,7 @@ resource "aws_ecs_service" "ignore_task_definition" { locals { # Role is not required if task definition uses `awsvpc` network mode or if a load balancer is not used - needs_iam_role = var.network_mode != "awsvpc" && length(var.load_balancer) > 0 + needs_iam_role = var.network_mode != "awsvpc" && var.load_balancer != null create_iam_role = var.create && var.create_iam_role && local.needs_iam_role iam_role_arn = local.needs_iam_role ? try(aws_iam_role.service[0].arn, var.iam_role_arn) : null @@ -462,18 +628,18 @@ data "aws_iam_policy_document" "service" { } dynamic "statement" { - for_each = var.iam_role_statements + for_each = var.iam_role_statements != null ? var.iam_role_statements : [] content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) + sid = statement.value.sid + 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 = try(statement.value.principals, []) + for_each = statement.value.principals != null ? statement.value.principals : [] content { type = principals.value.type @@ -482,7 +648,7 @@ data "aws_iam_policy_document" "service" { } dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) + for_each = statement.value.not_principals != null ? statement.value.not_principals : [] content { type = not_principals.value.type @@ -491,7 +657,7 @@ data "aws_iam_policy_document" "service" { } dynamic "condition" { - for_each = try(statement.value.conditions, []) + for_each = statement.value.conditions != null ? statement.value.conditions : [] content { test = condition.value.test @@ -528,60 +694,64 @@ resource "aws_iam_role_policy_attachment" "service" { module "container_definition" { source = "../container-definition" - for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition && try(v.create, true) } + region = var.region + + for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition && v.create } - operating_system_family = try(var.runtime_platform.operating_system_family, "LINUX") + enable_execute_command = var.enable_execute_command + operating_system_family = var.runtime_platform.operating_system_family # Container Definition - command = try(each.value.command, var.container_definition_defaults.command, []) - cpu = try(each.value.cpu, var.container_definition_defaults.cpu, null) - dependencies = try(each.value.dependencies, var.container_definition_defaults.dependencies, []) # depends_on is a reserved word - disable_networking = try(each.value.disable_networking, var.container_definition_defaults.disable_networking, null) - dns_search_domains = try(each.value.dns_search_domains, var.container_definition_defaults.dns_search_domains, []) - dns_servers = try(each.value.dns_servers, var.container_definition_defaults.dns_servers, []) - docker_labels = try(each.value.docker_labels, var.container_definition_defaults.docker_labels, {}) - docker_security_options = try(each.value.docker_security_options, var.container_definition_defaults.docker_security_options, []) - enable_execute_command = try(each.value.enable_execute_command, var.container_definition_defaults.enable_execute_command, var.enable_execute_command) - entrypoint = try(each.value.entrypoint, var.container_definition_defaults.entrypoint, []) - environment = try(each.value.environment, var.container_definition_defaults.environment, []) - environment_files = try(each.value.environment_files, var.container_definition_defaults.environment_files, []) - essential = try(each.value.essential, var.container_definition_defaults.essential, null) - extra_hosts = try(each.value.extra_hosts, var.container_definition_defaults.extra_hosts, []) - firelens_configuration = try(each.value.firelens_configuration, var.container_definition_defaults.firelens_configuration, {}) - health_check = try(each.value.health_check, var.container_definition_defaults.health_check, {}) - hostname = try(each.value.hostname, var.container_definition_defaults.hostname, null) - image = try(each.value.image, var.container_definition_defaults.image, null) - interactive = try(each.value.interactive, var.container_definition_defaults.interactive, false) - links = try(each.value.links, var.container_definition_defaults.links, []) - linux_parameters = try(each.value.linux_parameters, var.container_definition_defaults.linux_parameters, {}) - log_configuration = try(each.value.log_configuration, var.container_definition_defaults.log_configuration, {}) - memory = try(each.value.memory, var.container_definition_defaults.memory, null) - memory_reservation = try(each.value.memory_reservation, var.container_definition_defaults.memory_reservation, null) - mount_points = try(each.value.mount_points, var.container_definition_defaults.mount_points, []) - name = try(each.value.name, each.key) - port_mappings = try(each.value.port_mappings, var.container_definition_defaults.port_mappings, []) - privileged = try(each.value.privileged, var.container_definition_defaults.privileged, false) - pseudo_terminal = try(each.value.pseudo_terminal, var.container_definition_defaults.pseudo_terminal, false) - readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.container_definition_defaults.readonly_root_filesystem, true) - repository_credentials = try(each.value.repository_credentials, var.container_definition_defaults.repository_credentials, {}) - resource_requirements = try(each.value.resource_requirements, var.container_definition_defaults.resource_requirements, []) - secrets = try(each.value.secrets, var.container_definition_defaults.secrets, []) - start_timeout = try(each.value.start_timeout, var.container_definition_defaults.start_timeout, 30) - stop_timeout = try(each.value.stop_timeout, var.container_definition_defaults.stop_timeout, 120) - system_controls = try(each.value.system_controls, var.container_definition_defaults.system_controls, []) - ulimits = try(each.value.ulimits, var.container_definition_defaults.ulimits, []) - user = try(each.value.user, var.container_definition_defaults.user, 0) - volumes_from = try(each.value.volumes_from, var.container_definition_defaults.volumes_from, []) - working_directory = try(each.value.working_directory, var.container_definition_defaults.working_directory, null) + command = try(coalesce(each.value.command, var.container_definition_defaults.command), null) + cpu = try(coalesce(each.value.cpu, var.container_definition_defaults.cpu), null) + dependsOn = try(coalesce(each.value.dependsOn, var.container_definition_defaults.dependsOn), null) + disableNetworking = try(coalesce(each.value.disableNetworking, var.container_definition_defaults.disableNetworking), null) + dnsSearchDomains = try(coalesce(each.value.dnsSearchDomains, var.container_definition_defaults.dnsSearchDomains), null) + dnsServers = try(coalesce(each.value.dnsServers, var.container_definition_defaults.dnsServers), null) + dockerLabels = try(coalesce(each.value.dockerLabels, var.container_definition_defaults.dockerLabels), null) + dockerSecurityOptions = try(coalesce(each.value.dockerSecurityOptions, var.container_definition_defaults.dockerSecurityOptions), null) + entrypoint = try(coalesce(each.value.entrypoint, var.container_definition_defaults.entrypoint), null) + environment = try(coalesce(each.value.environment, var.container_definition_defaults.environment), null) + environmentFiles = try(coalesce(each.value.environmentFiles, var.container_definition_defaults.environmentFiles), null) + essential = try(coalesce(each.value.essential, var.container_definition_defaults.essential), null) + extraHosts = try(coalesce(each.value.extraHosts, var.container_definition_defaults.extraHosts), null) + firelensConfiguration = try(coalesce(each.value.firelensConfiguration, var.container_definition_defaults.firelensConfiguration), null) + healthCheck = try(coalesce(each.value.healthCheck, var.container_definition_defaults.healthCheck), null) + hostname = try(coalesce(each.value.hostname, var.container_definition_defaults.hostname), null) + image = try(coalesce(each.value.image, var.container_definition_defaults.image), null) + interactive = try(coalesce(each.value.interactive, var.container_definition_defaults.interactive), null) + links = try(coalesce(each.value.links, var.container_definition_defaults.links), null) + linuxParameters = try(coalesce(each.value.linuxParameters, var.container_definition_defaults.linuxParameters), null) + logConfiguration = try(coalesce(each.value.logConfiguration, var.container_definition_defaults.logConfiguration), null) + memory = try(coalesce(each.value.memory, var.container_definition_defaults.memory), null) + memoryReservation = try(coalesce(each.value.memoryReservation, var.container_definition_defaults.memoryReservation), null) + mountPoints = try(coalesce(each.value.mountPoints, var.container_definition_defaults.mountPoints), null) + name = coalesce(each.value.name, each.key) + portMappings = try(coalesce(each.value.portMappings, var.container_definition_defaults.portMappings), null) + privileged = try(coalesce(each.value.privileged, var.container_definition_defaults.privileged), null) + pseudoTerminal = try(coalesce(each.value.pseudoTerminal, var.container_definition_defaults.pseudoTerminal), null) + readonlyRootFilesystem = try(coalesce(each.value.readonlyRootFilesystem, var.container_definition_defaults.readonlyRootFilesystem), null) + repositoryCredentials = try(coalesce(each.value.repositoryCredentials, var.container_definition_defaults.repositoryCredentials), null) + resourceRequirements = try(coalesce(each.value.resourceRequirements, var.container_definition_defaults.resourceRequirements), null) + restartPolicy = try(coalesce(each.value.restartPolicy, var.container_definition_defaults.restartPolicy), null) + secrets = try(coalesce(each.value.secrets, var.container_definition_defaults.secrets), null) + startTimeout = try(coalesce(each.value.startTimeout, var.container_definition_defaults.startTimeout), null) + stopTimeout = try(coalesce(each.value.stopTimeout, var.container_definition_defaults.stopTimeout), null) + systemControls = try(coalesce(each.value.systemControls, var.container_definition_defaults.systemControls), null) + ulimits = try(coalesce(each.value.ulimits, var.container_definition_defaults.ulimits), null) + user = try(coalesce(each.value.user, var.container_definition_defaults.user), null) + volumesFrom = try(coalesce(each.value.volumesFrom, var.container_definition_defaults.volumesFrom), null) + workingDirectory = try(coalesce(each.value.workingDirectory, var.container_definition_defaults.workingDirectory), null) # CloudWatch Log Group service = var.name - enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.container_definition_defaults.enable_cloudwatch_logging, true) - create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.container_definition_defaults.create_cloudwatch_log_group, true) - cloudwatch_log_group_name = try(each.value.cloudwatch_log_group_name, var.container_definition_defaults.cloudwatch_log_group_name, null) - cloudwatch_log_group_use_name_prefix = try(each.value.cloudwatch_log_group_use_name_prefix, var.container_definition_defaults.cloudwatch_log_group_use_name_prefix, false) - cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.container_definition_defaults.cloudwatch_log_group_retention_in_days, 14) - cloudwatch_log_group_kms_key_id = try(each.value.cloudwatch_log_group_kms_key_id, var.container_definition_defaults.cloudwatch_log_group_kms_key_id, null) + enable_cloudwatch_logging = try(coalesce(each.value.enable_cloudwatch_logging, var.container_definition_defaults.enable_cloudwatch_logging), null) + create_cloudwatch_log_group = try(coalesce(each.value.create_cloudwatch_log_group, var.container_definition_defaults.create_cloudwatch_log_group), null) + cloudwatch_log_group_name = try(coalesce(each.value.cloudwatch_log_group_name, var.container_definition_defaults.cloudwatch_log_group_name), null) + cloudwatch_log_group_use_name_prefix = try(coalesce(each.value.cloudwatch_log_group_use_name_prefix, var.container_definition_defaults.cloudwatch_log_group_use_name_prefix), null) + cloudwatch_log_group_class = try(coalesce(each.value.cloudwatch_log_group_class, var.container_definition_defaults.cloudwatch_log_group_class), null) + cloudwatch_log_group_retention_in_days = try(coalesce(each.value.cloudwatch_log_group_retention_in_days, var.container_definition_defaults.cloudwatch_log_group_retention_in_days), null) + cloudwatch_log_group_kms_key_id = try(coalesce(each.value.cloudwatch_log_group_kms_key_id, var.container_definition_defaults.cloudwatch_log_group_kms_key_id), null) tags = var.tags } @@ -592,37 +762,21 @@ module "container_definition" { locals { create_task_definition = var.create && var.create_task_definition - - # This allows us to query both the existing as well as Terraform's state and get - # and get the max version of either source, useful for when external resources - # update the container definition - max_task_def_revision = local.create_task_definition ? max(aws_ecs_task_definition.this[0].revision, data.aws_ecs_task_definition.this[0].revision) : 0 - task_definition = local.create_task_definition ? "${aws_ecs_task_definition.this[0].family}:${local.max_task_def_revision}" : var.task_definition_arn -} - -# This allows us to query both the existing as well as Terraform's state and get -# and get the max version of either source, useful for when external resources -# update the container definition -data "aws_ecs_task_definition" "this" { - count = local.create_task_definition ? 1 : 0 - - task_definition = aws_ecs_task_definition.this[0].family - - depends_on = [ - # Needs to exist first on first deployment - aws_ecs_task_definition.this - ] + task_definition = local.create_task_definition ? aws_ecs_task_definition.this[0].arn : var.task_definition_arn } resource "aws_ecs_task_definition" "this" { count = local.create_task_definition ? 1 : 0 + region = var.region + # Convert map of maps to array of maps before JSON encoding - container_definitions = jsonencode([for k, v in module.container_definition : v.container_definition]) - cpu = var.cpu + container_definitions = jsonencode([for k, v in module.container_definition : v.container_definition]) + cpu = var.cpu + enable_fault_injection = var.enable_fault_injection dynamic "ephemeral_storage" { - for_each = length(var.ephemeral_storage) > 0 ? [var.ephemeral_storage] : [] + for_each = var.ephemeral_storage != null ? [var.ephemeral_storage] : [] content { size_in_gib = ephemeral_storage.value.size_in_gib @@ -632,95 +786,89 @@ resource "aws_ecs_task_definition" "this" { execution_role_arn = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn) family = coalesce(var.family, var.name) - dynamic "inference_accelerator" { - for_each = var.inference_accelerator - - content { - device_name = inference_accelerator.value.device_name - device_type = inference_accelerator.value.device_type - } - } - ipc_mode = var.ipc_mode memory = var.memory network_mode = var.network_mode pid_mode = var.pid_mode dynamic "placement_constraints" { - for_each = var.task_definition_placement_constraints + for_each = var.task_definition_placement_constraints != null ? var.task_definition_placement_constraints : {} content { - expression = try(placement_constraints.value.expression, null) + expression = placement_constraints.value.expression type = placement_constraints.value.type } } dynamic "proxy_configuration" { - for_each = length(var.proxy_configuration) > 0 ? [var.proxy_configuration] : [] + for_each = var.proxy_configuration != null ? [var.proxy_configuration] : [] content { container_name = proxy_configuration.value.container_name - properties = try(proxy_configuration.value.properties, null) - type = try(proxy_configuration.value.type, null) + properties = proxy_configuration.value.properties + type = proxy_configuration.value.type } } requires_compatibilities = var.requires_compatibilities dynamic "runtime_platform" { - for_each = length(var.runtime_platform) > 0 ? [var.runtime_platform] : [] + for_each = var.runtime_platform != null ? [var.runtime_platform] : [] content { - cpu_architecture = try(runtime_platform.value.cpu_architecture, null) - operating_system_family = try(runtime_platform.value.operating_system_family, null) + cpu_architecture = runtime_platform.value.cpu_architecture + operating_system_family = runtime_platform.value.operating_system_family } } skip_destroy = var.skip_destroy task_role_arn = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn) + track_latest = var.track_latest dynamic "volume" { - for_each = var.volume + for_each = var.volume != null ? var.volume : {} content { + configure_at_launch = volume.value.configure_at_launch + dynamic "docker_volume_configuration" { - for_each = try([volume.value.docker_volume_configuration], []) + for_each = volume.value.docker_volume_configuration != null ? [volume.value.docker_volume_configuration] : [] content { - autoprovision = try(docker_volume_configuration.value.autoprovision, null) - driver = try(docker_volume_configuration.value.driver, null) - driver_opts = try(docker_volume_configuration.value.driver_opts, null) - labels = try(docker_volume_configuration.value.labels, null) - scope = try(docker_volume_configuration.value.scope, null) + autoprovision = docker_volume_configuration.value.autoprovision + driver = docker_volume_configuration.value.driver + driver_opts = docker_volume_configuration.value.driver_opts + labels = docker_volume_configuration.value.labels + scope = docker_volume_configuration.value.scope } } dynamic "efs_volume_configuration" { - for_each = try([volume.value.efs_volume_configuration], []) + for_each = volume.value.efs_volume_configuration != null ? [volume.value.efs_volume_configuration] : [] content { dynamic "authorization_config" { - for_each = try([efs_volume_configuration.value.authorization_config], []) + for_each = efs_volume_configuration.value.authorization_config != null ? [efs_volume_configuration.value.authorization_config] : [] content { - access_point_id = try(authorization_config.value.access_point_id, null) - iam = try(authorization_config.value.iam, null) + access_point_id = authorization_config.value.access_point_id + iam = authorization_config.value.iam } } file_system_id = efs_volume_configuration.value.file_system_id - root_directory = try(efs_volume_configuration.value.root_directory, null) - transit_encryption = try(efs_volume_configuration.value.transit_encryption, null) - transit_encryption_port = try(efs_volume_configuration.value.transit_encryption_port, null) + root_directory = efs_volume_configuration.value.root_directory + transit_encryption = efs_volume_configuration.value.transit_encryption + transit_encryption_port = efs_volume_configuration.value.transit_encryption_port } } dynamic "fsx_windows_file_server_volume_configuration" { - for_each = try([volume.value.fsx_windows_file_server_volume_configuration], []) + for_each = volume.value.fsx_windows_file_server_volume_configuration != null ? [volume.value.fsx_windows_file_server_volume_configuration] : [] content { dynamic "authorization_config" { - for_each = try([fsx_windows_file_server_volume_configuration.value.authorization_config], []) + for_each = fsx_windows_file_server_volume_configuration.value.authorization_config != null ? [fsx_windows_file_server_volume_configuration.value.authorization_config] : [] content { credentials_parameter = authorization_config.value.credentials_parameter @@ -733,8 +881,8 @@ resource "aws_ecs_task_definition" "this" { } } - host_path = try(volume.value.host_path, null) - name = try(volume.value.name, volume.key) + host_path = volume.value.host_path + name = coalesce(volume.value.name, volume.key) } } @@ -757,7 +905,7 @@ resource "aws_ecs_task_definition" "this" { ################################################################################ locals { - task_exec_iam_role_name = try(coalesce(var.task_exec_iam_role_name, var.name), "") + task_exec_iam_role_name = coalesce(var.task_exec_iam_role_name, var.name, "NotProvided") 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 @@ -846,18 +994,18 @@ data "aws_iam_policy_document" "task_exec" { } dynamic "statement" { - for_each = var.task_exec_iam_statements + for_each = var.task_exec_iam_statements != null ? var.task_exec_iam_statements : [] content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) + sid = statement.value.sid + 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 = try(statement.value.principals, []) + for_each = statement.value.principals != null ? statement.value.principals : [] content { type = principals.value.type @@ -866,7 +1014,7 @@ data "aws_iam_policy_document" "task_exec" { } dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) + for_each = statement.value.not_principals != null ? statement.value.not_principals : [] content { type = not_principals.value.type @@ -875,7 +1023,7 @@ data "aws_iam_policy_document" "task_exec" { } dynamic "condition" { - for_each = try(statement.value.conditions, []) + for_each = statement.value.conditions != null ? statement.value.conditions : [] content { test = condition.value.test @@ -911,7 +1059,7 @@ resource "aws_iam_role_policy_attachment" "task_exec" { ################################################################################ locals { - tasks_iam_role_name = try(coalesce(var.tasks_iam_role_name, var.name), "") + tasks_iam_role_name = coalesce(var.tasks_iam_role_name, var.name, "NotProvided") create_tasks_iam_role = local.create_task_definition && var.create_tasks_iam_role } @@ -957,15 +1105,8 @@ resource "aws_iam_role" "tasks" { tags = merge(var.tags, var.tasks_iam_role_tags) } -resource "aws_iam_role_policy_attachment" "tasks" { - for_each = { for k, v in var.tasks_iam_role_policies : k => v if local.create_tasks_iam_role } - - role = aws_iam_role.tasks[0].name - policy_arn = each.value -} - data "aws_iam_policy_document" "tasks" { - count = local.create_tasks_iam_role && (length(var.tasks_iam_role_statements) > 0 || var.enable_execute_command) ? 1 : 0 + count = local.create_tasks_iam_role && (var.tasks_iam_role_statements != null || var.enable_execute_command) ? 1 : 0 dynamic "statement" { for_each = var.enable_execute_command ? [1] : [] @@ -983,18 +1124,18 @@ data "aws_iam_policy_document" "tasks" { } dynamic "statement" { - for_each = var.tasks_iam_role_statements + for_each = var.tasks_iam_role_statements != null ? var.tasks_iam_role_statements : [] content { - sid = try(statement.value.sid, null) - actions = try(statement.value.actions, null) - not_actions = try(statement.value.not_actions, null) - effect = try(statement.value.effect, null) - resources = try(statement.value.resources, null) - not_resources = try(statement.value.not_resources, null) + sid = statement.value.sid + 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 = try(statement.value.principals, []) + for_each = statement.value.principals != null ? statement.value.principals : [] content { type = principals.value.type @@ -1003,7 +1144,7 @@ data "aws_iam_policy_document" "tasks" { } dynamic "not_principals" { - for_each = try(statement.value.not_principals, []) + for_each = statement.value.not_principals != null ? statement.value.not_principals : [] content { type = not_principals.value.type @@ -1012,7 +1153,7 @@ data "aws_iam_policy_document" "tasks" { } dynamic "condition" { - for_each = try(statement.value.conditions, []) + for_each = statement.value.condition != null ? statement.value.condition : [] content { test = condition.value.test @@ -1024,13 +1165,29 @@ data "aws_iam_policy_document" "tasks" { } } -resource "aws_iam_role_policy" "tasks" { - count = local.create_tasks_iam_role && (length(var.tasks_iam_role_statements) > 0 || var.enable_execute_command) ? 1 : 0 +resource "aws_iam_policy" "tasks" { + count = local.create_tasks_iam_role && (var.tasks_iam_role_statements != null || var.enable_execute_command) ? 1 : 0 name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null + description = coalesce(var.tasks_iam_role_description, "Task role IAM policy") policy = data.aws_iam_policy_document.tasks[0].json - role = aws_iam_role.tasks[0].id + path = var.tasks_iam_role_path + tags = merge(var.tags, var.tasks_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "tasks_internal" { + count = local.create_tasks_iam_role && (var.tasks_iam_role_statements != null || var.enable_execute_command) ? 1 : 0 + + role = aws_iam_role.tasks[0].name + policy_arn = aws_iam_policy.tasks[0].arn +} + +resource "aws_iam_role_policy_attachment" "tasks" { + for_each = { for k, v in var.tasks_iam_role_policies : k => v if local.create_tasks_iam_role } + + role = aws_iam_role.tasks[0].name + policy_arn = each.value } ################################################################################ @@ -1041,6 +1198,8 @@ resource "aws_ecs_task_set" "this" { # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html count = local.create_task_definition && local.is_external_deployment && !var.ignore_task_definition_changes ? 1 : 0 + region = var.region + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) cluster = var.cluster_arn external_id = var.external_id @@ -1057,47 +1216,47 @@ resource "aws_ecs_task_set" "this" { } dynamic "load_balancer" { - for_each = var.load_balancer + for_each = var.load_balancer != null ? var.load_balancer : {} content { - load_balancer_name = try(load_balancer.value.load_balancer_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) + load_balancer_name = load_balancer.value.load_balancer_name + target_group_arn = load_balancer.value.target_group_arn container_name = load_balancer.value.container_name - container_port = try(load_balancer.value.container_port, null) + container_port = load_balancer.value.container_port } } dynamic "service_registries" { - for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + for_each = var.service_registries != null ? [var.service_registries] : [] content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) + container_name = service_registries.value.container_name + container_port = service_registries.value.container_port + port = service_registries.value.port registry_arn = service_registries.value.registry_arn } } - launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + launch_type = var.capacity_provider_strategy != null ? null : var.launch_type dynamic "capacity_provider_strategy" { - for_each = var.capacity_provider_strategy + for_each = var.capacity_provider_strategy != null ? var.capacity_provider_strategy : {} content { - base = try(capacity_provider_strategy.value.base, null) + base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) + weight = capacity_provider_strategy.value.weight } } platform_version = local.is_fargate ? var.platform_version : null dynamic "scale" { - for_each = length(var.scale) > 0 ? [var.scale] : [] + for_each = var.scale != null ? [var.scale] : [] content { - unit = try(scale.value.unit, null) - value = try(scale.value.value, null) + unit = scale.value.unit + value = scale.value.value } } @@ -1122,6 +1281,8 @@ resource "aws_ecs_task_set" "ignore_task_definition" { # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskset.html count = local.create_task_definition && local.is_external_deployment && var.ignore_task_definition_changes ? 1 : 0 + region = var.region + service = try(aws_ecs_service.this[0].id, aws_ecs_service.ignore_task_definition[0].id) cluster = var.cluster_arn external_id = var.external_id @@ -1138,47 +1299,47 @@ resource "aws_ecs_task_set" "ignore_task_definition" { } dynamic "load_balancer" { - for_each = var.load_balancer + for_each = var.load_balancer != null ? var.load_balancer : {} content { - load_balancer_name = try(load_balancer.value.load_balancer_name, null) - target_group_arn = try(load_balancer.value.target_group_arn, null) + load_balancer_name = load_balancer.value.load_balancer_name + target_group_arn = load_balancer.value.target_group_arn container_name = load_balancer.value.container_name - container_port = try(load_balancer.value.container_port, null) + container_port = load_balancer.value.container_port } } dynamic "service_registries" { - for_each = length(var.service_registries) > 0 ? [var.service_registries] : [] + for_each = var.service_registries != null ? [var.service_registries] : [] content { - container_name = try(service_registries.value.container_name, null) - container_port = try(service_registries.value.container_port, null) - port = try(service_registries.value.port, null) + container_name = service_registries.value.container_name + container_port = service_registries.value.container_port + port = service_registries.value.port registry_arn = service_registries.value.registry_arn } } - launch_type = length(var.capacity_provider_strategy) > 0 ? null : var.launch_type + launch_type = var.capacity_provider_strategy != null ? null : var.launch_type dynamic "capacity_provider_strategy" { - for_each = var.capacity_provider_strategy + for_each = var.capacity_provider_strategy != null ? var.capacity_provider_strategy : {} content { - base = try(capacity_provider_strategy.value.base, null) + base = capacity_provider_strategy.value.base capacity_provider = capacity_provider_strategy.value.capacity_provider - weight = try(capacity_provider_strategy.value.weight, null) + weight = capacity_provider_strategy.value.weight } } platform_version = local.is_fargate ? var.platform_version : null dynamic "scale" { - for_each = length(var.scale) > 0 ? [var.scale] : [] + for_each = var.scale != null ? [var.scale] : [] content { - unit = try(scale.value.unit, null) - value = try(scale.value.value, null) + unit = scale.value.unit + value = scale.value.value } } @@ -1209,6 +1370,8 @@ locals { resource "aws_appautoscaling_target" "this" { count = local.enable_autoscaling ? 1 : 0 + region = var.region + # Desired needs to be between or equal to min/max min_capacity = min(var.autoscaling_min_capacity, var.desired_count) max_capacity = max(var.autoscaling_max_capacity, var.desired_count) @@ -1222,43 +1385,45 @@ resource "aws_appautoscaling_target" "this" { resource "aws_appautoscaling_policy" "this" { for_each = { for k, v in var.autoscaling_policies : k => v if local.enable_autoscaling } - name = try(each.value.name, each.key) - policy_type = try(each.value.policy_type, "TargetTrackingScaling") + region = var.region + + name = coalesce(each.value.name, each.key) + policy_type = each.value.policy_type resource_id = aws_appautoscaling_target.this[0].resource_id scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension service_namespace = aws_appautoscaling_target.this[0].service_namespace dynamic "step_scaling_policy_configuration" { - for_each = try([each.value.step_scaling_policy_configuration], []) + for_each = each.value.step_scaling_policy_configuration != null ? [each.value.step_scaling_policy_configuration] : [] content { - adjustment_type = try(step_scaling_policy_configuration.value.adjustment_type, null) - cooldown = try(step_scaling_policy_configuration.value.cooldown, null) - metric_aggregation_type = try(step_scaling_policy_configuration.value.metric_aggregation_type, null) - min_adjustment_magnitude = try(step_scaling_policy_configuration.value.min_adjustment_magnitude, null) + adjustment_type = step_scaling_policy_configuration.value.adjustment_type + cooldown = step_scaling_policy_configuration.value.cooldown + metric_aggregation_type = step_scaling_policy_configuration.value.metric_aggregation_type + min_adjustment_magnitude = step_scaling_policy_configuration.value.min_adjustment_magnitude dynamic "step_adjustment" { - for_each = try(step_scaling_policy_configuration.value.step_adjustment, []) + for_each = step_scaling_policy_configuration.value.step_adjustment != null ? step_scaling_policy_configuration.value.step_adjustment : [] content { - metric_interval_lower_bound = try(step_adjustment.value.metric_interval_lower_bound, null) - metric_interval_upper_bound = try(step_adjustment.value.metric_interval_upper_bound, null) - scaling_adjustment = try(step_adjustment.value.scaling_adjustment, null) + metric_interval_lower_bound = step_adjustment.value.metric_interval_lower_bound + metric_interval_upper_bound = step_adjustment.value.metric_interval_upper_bound + scaling_adjustment = step_adjustment.value.scaling_adjustment } } } } dynamic "target_tracking_scaling_policy_configuration" { - for_each = try(each.value.policy_type, null) == "TargetTrackingScaling" ? try([each.value.target_tracking_scaling_policy_configuration], []) : [] + for_each = each.value.policy_type == "TargetTrackingScaling" && each.value.target_tracking_scaling_policy_configuration != null ? [each.value.target_tracking_scaling_policy_configuration] : [] content { dynamic "customized_metric_specification" { - for_each = try([target_tracking_scaling_policy_configuration.value.customized_metric_specification], []) + for_each = target_tracking_scaling_policy_configuration.value.customized_metric_specification != null ? [target_tracking_scaling_policy_configuration.value.customized_metric_specification] : [] content { dynamic "dimensions" { - for_each = try(customized_metric_specification.value.dimensions, []) + for_each = customized_metric_specification.value.dimensions != null ? customized_metric_specification.value.dimensions : [] content { name = dimensions.value.name @@ -1267,34 +1432,76 @@ resource "aws_appautoscaling_policy" "this" { } metric_name = customized_metric_specification.value.metric_name - namespace = customized_metric_specification.value.namespace - statistic = customized_metric_specification.value.statistic - unit = try(customized_metric_specification.value.unit, null) + + dynamic "metrics" { + for_each = customized_metric_specification.value.metrics != null ? customized_metric_specification.value.metrics : [] + + content { + expression = metrics.value.expression + id = metrics.value.id + label = metrics.value.label + + dynamic "metric_stat" { + for_each = metrics.value.metric_stat != null ? [metrics.value.metric_stat] : [] + + content { + dynamic "metric" { + for_each = [metric_stat.value.metric] + + content { + dynamic "dimensions" { + for_each = metric.value.dimensions != null ? metric.value.dimensions : [] + + content { + name = dimensions.value.name + value = dimensions.value.value + } + } + + metric_name = metric.value.metric_name + namespace = metric.value.namespace + } + } + + stat = metric_stat.value.stat + unit = metric_stat.value.unit + } + } + + return_data = metrics.value.return_data + } + } + + namespace = customized_metric_specification.value.namespace + statistic = customized_metric_specification.value.statistic + unit = customized_metric_specification.value.unit } } - disable_scale_in = try(target_tracking_scaling_policy_configuration.value.disable_scale_in, null) + disable_scale_in = target_tracking_scaling_policy_configuration.value.disable_scale_in dynamic "predefined_metric_specification" { - for_each = try([target_tracking_scaling_policy_configuration.value.predefined_metric_specification], []) + for_each = target_tracking_scaling_policy_configuration.value.predefined_metric_specification != null ? [target_tracking_scaling_policy_configuration.value.predefined_metric_specification] : [] content { predefined_metric_type = predefined_metric_specification.value.predefined_metric_type - resource_label = try(predefined_metric_specification.value.resource_label, null) + resource_label = predefined_metric_specification.value.resource_label } } - scale_in_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_in_cooldown, 300) - scale_out_cooldown = try(target_tracking_scaling_policy_configuration.value.scale_out_cooldown, 60) - target_value = try(target_tracking_scaling_policy_configuration.value.target_value, 75) + scale_in_cooldown = target_tracking_scaling_policy_configuration.value.scale_in_cooldown + scale_out_cooldown = target_tracking_scaling_policy_configuration.value.scale_out_cooldown + target_value = target_tracking_scaling_policy_configuration.value.target_value } } } resource "aws_appautoscaling_scheduled_action" "this" { - for_each = { for k, v in var.autoscaling_scheduled_actions : k => v if local.enable_autoscaling } + for_each = local.enable_autoscaling && var.autoscaling_scheduled_actions != null ? var.autoscaling_scheduled_actions : {} + + region = var.region - name = try(each.value.name, each.key) + name = coalesce(each.value.name, each.key) service_namespace = aws_appautoscaling_target.this[0].service_namespace resource_id = aws_appautoscaling_target.this[0].resource_id scalable_dimension = aws_appautoscaling_target.this[0].scalable_dimension @@ -1305,9 +1512,9 @@ resource "aws_appautoscaling_scheduled_action" "this" { } schedule = each.value.schedule - start_time = try(each.value.start_time, null) - end_time = try(each.value.end_time, null) - timezone = try(each.value.timezone, null) + start_time = each.value.start_time + end_time = each.value.end_time + timezone = each.value.timezone } ################################################################################ @@ -1316,18 +1523,22 @@ resource "aws_appautoscaling_scheduled_action" "this" { locals { create_security_group = var.create && var.create_security_group && var.network_mode == "awsvpc" - security_group_name = try(coalesce(var.security_group_name, var.name), "") + security_group_name = coalesce(var.security_group_name, var.name, "NotProvided") } data "aws_subnet" "this" { count = local.create_security_group ? 1 : 0 + region = var.region + id = element(var.subnet_ids, 0) } 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 = var.security_group_description @@ -1344,21 +1555,101 @@ resource "aws_security_group" "this" { } } -resource "aws_security_group_rule" "this" { - for_each = { for k, v in var.security_group_rules : k => v if local.create_security_group } - - # Required - security_group_id = aws_security_group.this[0].id - protocol = each.value.protocol - from_port = each.value.from_port - to_port = each.value.to_port - type = each.value.type - - # Optional - description = lookup(each.value, "description", null) - cidr_blocks = lookup(each.value, "cidr_blocks", null) - ipv6_cidr_blocks = lookup(each.value, "ipv6_cidr_blocks", null) - prefix_list_ids = lookup(each.value, "prefix_list_ids", null) - self = lookup(each.value, "self", null) - source_security_group_id = lookup(each.value, "source_security_group_id", null) +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 + 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 + 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 +} + +############################################################################################ +# ECS infrastructure IAM role +# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/infrastructure_IAM_role.html +############################################################################################ + +locals { + needs_infrastructure_iam_role = var.volume_configuration != null || var.vpc_lattice_configurations != 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") +} + +data "aws_iam_policy_document" "infrastructure_iam_role" { + count = local.create_infrastructure_iam_role ? 1 : 0 + + statement { + sid = "ECSServiceAssumeRole" + actions = ["sts:AssumeRole"] + + principals { + type = "Service" + identifiers = ["ecs.amazonaws.com"] + } + } +} + +resource "aws_iam_role" "infrastructure_iam_role" { + 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") + + assume_role_policy = data.aws_iam_policy_document.infrastructure_iam_role[0].json + permissions_boundary = var.infrastructure_iam_role_permissions_boundary + force_detach_policies = true + + tags = merge(var.tags, var.infrastructure_iam_role_tags) +} + +resource "aws_iam_role_policy_attachment" "infrastructure_iam_role_ebs_policy" { + count = local.create_infrastructure_iam_role && var.volume_configuration != null ? 1 : 0 + + role = aws_iam_role.infrastructure_iam_role[0].name + policy_arn = "arn:${local.partition}:iam::aws:policy/service-role/AmazonECSInfrastructureRolePolicyForVolumes" +} + +resource "aws_iam_role_policy_attachment" "infrastructure_iam_role_vpc_lattice_policy" { + count = local.create_infrastructure_iam_role && var.vpc_lattice_configurations != null ? 1 : 0 + + role = aws_iam_role.infrastructure_iam_role[0].name + policy_arn = "arn:${local.partition}:iam::aws:policy/service-role/AmazonECSInfrastructureRolePolicyForVpcLattice" } diff --git a/modules/service/outputs.tf b/modules/service/outputs.tf index 1eaa8510..0d81c8f9 100644 --- a/modules/service/outputs.tf +++ b/modules/service/outputs.tf @@ -59,11 +59,6 @@ output "task_definition_family" { value = try(aws_ecs_task_definition.this[0].family, null) } -output "task_definition_family_revision" { - description = "The family and revision (family:revision) of the task definition" - value = "${try(aws_ecs_task_definition.this[0].family, "")}:${local.max_task_def_revision}" -} - ################################################################################ # Task Execution - IAM Role # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html @@ -155,3 +150,17 @@ output "security_group_id" { description = "ID of the security group" value = try(aws_security_group.this[0].id, null) } + +############################################################################################ +# ECS infrastructure IAM role +############################################################################################ + +output "infrastructure_iam_role_arn" { + description = "Infrastructure IAM role ARN" + value = try(aws_iam_role.infrastructure_iam_role[0].arn, null) +} + +output "infrastructure_iam_role_name" { + description = "Infrastructure IAM role name" + value = try(aws_iam_role.infrastructure_iam_role[0].name, null) +} diff --git a/modules/service/variables.tf b/modules/service/variables.tf index e457f7d6..7992eb96 100644 --- a/modules/service/variables.tf +++ b/modules/service/variables.tf @@ -10,6 +10,12 @@ variable "create_service" { default = true } +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + variable "tags" { description = "A map of tags to add to all resources" type = map(string) @@ -28,14 +34,28 @@ variable "ignore_task_definition_changes" { variable "alarms" { description = "Information about the CloudWatch alarms" - type = any - default = {} + type = object({ + alarm_names = list(string) + enable = optional(bool, true) + rollback = optional(bool, true) + }) + default = null +} + +variable "availability_zone_rebalancing" { + description = "ECS automatically redistributes tasks within a service across Availability Zones (AZs) to mitigate the risk of impaired application availability due to underlying infrastructure failures and task lifecycle activities. The valid values are `ENABLED` and `DISABLED`. Defaults to `DISABLED`" + type = string + default = null } variable "capacity_provider_strategy" { description = "Capacity provider strategies to use for the service. Can be one or more" - type = any - default = {} + type = map(object({ + base = optional(number) + capacity_provider = string + weight = optional(number) + })) + default = null } variable "cluster_arn" { @@ -46,14 +66,19 @@ variable "cluster_arn" { variable "deployment_circuit_breaker" { description = "Configuration block for deployment circuit breaker" - type = any - default = {} + type = object({ + enable = bool + rollback = bool + }) + default = null } variable "deployment_controller" { description = "Configuration block for deployment controller configuration" - type = any - default = {} + type = object({ + type = optional(string) + }) + default = null } variable "deployment_maximum_percent" { @@ -86,6 +111,12 @@ variable "enable_execute_command" { default = false } +variable "force_delete" { + description = "Enable to delete a service even if it wasn't scaled down to zero tasks. It's only necessary to use this if the service uses the `REPLICA` scheduling strategy" + type = bool + default = null +} + variable "force_new_deployment" { description = "Enable to force a new task deployment of the service. This can be used to update tasks to use a newer Docker image with same image/tag combination, roll Fargate tasks onto a newer platform version, or immediately deploy `ordered_placement_strategy` and `placement_constraints` updates" type = bool @@ -106,8 +137,13 @@ variable "launch_type" { variable "load_balancer" { description = "Configuration block for load balancers" - type = any - default = {} + type = map(object({ + container_name = string + container_port = number + elb_name = optional(string) + target_group_arn = optional(string) + })) + default = null } variable "name" { @@ -136,14 +172,20 @@ variable "subnet_ids" { variable "ordered_placement_strategy" { description = "Service level strategy rules that are taken into consideration during task placement. List from top to bottom in order of precedence" - type = any - default = {} + type = map(object({ + field = optional(string) + type = string + })) + default = null } variable "placement_constraints" { description = "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" - type = any - default = {} + type = map(object({ + expression = optional(string) + type = string + })) + default = null } variable "platform_version" { @@ -166,26 +208,99 @@ variable "scheduling_strategy" { variable "service_connect_configuration" { description = "The ECS Service Connect configuration for this service to discover and connect to services, and be discovered by, and connected from, other services within a namespace" - type = any - default = {} + type = object({ + enabled = optional(bool, true) + 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 + })) + 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) + })) + }))) + }) + default = null } variable "service_registries" { description = "Service discovery registries for the service" - type = any - default = {} + type = object({ + container_name = optional(string) + container_port = optional(number) + port = optional(number) + registry_arn = string + }) + default = null } variable "timeouts" { description = "Create, update, and delete timeout configurations for the service" - type = map(string) - default = {} + type = object({ + create = optional(string) + update = optional(string) + delete = optional(string) + }) + default = null } variable "triggers" { - description = "Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `plantimestamp()`" - type = any - default = {} + description = "Map of arbitrary keys and values that, when changed, will trigger an in-place update (redeployment). Useful with `timestamp()`" + type = map(string) + default = null +} + +variable "volume_configuration" { + description = "Configuration for a volume specified in the task definition as a volume that is configured at launch time" + type = 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, "TASK_DEFINITION") + resource_type = string + tags = optional(map(string)) + }))) + throughput = optional(number) + volume_type = optional(string) + }) + }) + default = null +} + +variable "vpc_lattice_configurations" { + description = "The VPC Lattice configuration for your service that allows Lattice to connect, secure, and monitor your service across multiple accounts and VPCs" + type = object({ + role_arn = string + target_group_arn = string + port_name = string + }) + default = null } variable "wait_for_steady_state" { @@ -254,8 +369,28 @@ variable "iam_role_tags" { variable "iam_role_statements" { description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} + type = 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 + }))) + })) + default = null } ################################################################################ @@ -276,14 +411,301 @@ variable "task_definition_arn" { variable "container_definitions" { description = "A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document" - type = any - default = {} + type = map(object({ + create = optional(bool, true) + operating_system_family = optional(string, "LINUX") + tags = optional(map(string), {}) + + # Container definition + command = optional(list(string)) + cpu = optional(number) + 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, false) Set in standalone variable + 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, 30) + retries = optional(number, 3) + startPeriod = optional(number) + timeout = optional(number, 5) + })) + hostname = optional(string) + image = optional(string) + interactive = optional(bool, false) + 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, false) + maxSwap = optional(number) + sharedMemorySize = optional(number) + swappiness = optional(number) + tmpfs = optional(list(object({ + containerPath = string + mountOptions = optional(list(string)) + size = number + }))) + }), + # Default + { + initProcessEnabled = false + } + ) + 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, false) + pseudoTerminal = optional(bool, false) + readonlyRootFilesystem = optional(bool, true) + repositoryCredentials = optional(object({ + credentialsParameter = optional(string) + })) + resourceRequirements = optional(list(object({ + type = string + value = string + }))) + restartPolicy = optional(object({ + enabled = optional(bool, true) + ignoredExitCodes = optional(list(number)) + restartAttemptPeriod = optional(number) + }), + # Default + { + enabled = true + } + ) + secrets = optional(list(object({ + name = string + valueFrom = string + }))) + startTimeout = optional(number, 30) + stopTimeout = optional(number, 120) + 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, "disabled") + volumesFrom = optional(list(object({ + readOnly = optional(bool) + sourceContainer = optional(string) + })), []) + workingDirectory = optional(string) + + # Cloudwatch Log Group + service = optional(string, "") + enable_cloudwatch_logging = optional(bool, true) + create_cloudwatch_log_group = optional(bool, true) + cloudwatch_log_group_name = optional(string) + cloudwatch_log_group_use_name_prefix = optional(bool, false) + cloudwatch_log_group_class = optional(string) + cloudwatch_log_group_retention_in_days = optional(number, 14) + cloudwatch_log_group_kms_key_id = optional(string) + })) + default = {} } variable "container_definition_defaults" { description = "A map of default values for [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html) created by `container_definitions`" - type = any - default = {} + type = object({ + create = optional(bool) + operating_system_family = optional(string) + tags = optional(map(string)) + + # Container definition + command = optional(list(string)) + cpu = optional(number) + 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, false) Set in standalone variable + 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) + }) + default = {} } variable "cpu" { @@ -292,10 +714,18 @@ variable "cpu" { default = 1024 } +variable "enable_fault_injection" { + description = "Enables fault injection and allows for fault injection requests to be accepted from the task's containers. Default is `false`" + type = bool + default = null +} + variable "ephemeral_storage" { description = "The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate" - type = any - default = {} + type = object({ + size_in_gib = number + }) + default = null } variable "family" { @@ -304,12 +734,6 @@ variable "family" { default = null } -variable "inference_accelerator" { - description = "Configuration block(s) with Inference Accelerators settings" - type = any - default = {} -} - variable "ipc_mode" { description = "IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`" type = string @@ -334,16 +758,14 @@ variable "pid_mode" { default = null } -variable "task_definition_placement_constraints" { - description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service" - type = any - default = {} -} - variable "proxy_configuration" { description = "Configuration block for the App Mesh proxy" - type = any - default = {} + type = object({ + container_name = string + properties = optional(map(string)) + type = optional(string) + }) + default = null } variable "requires_compatibilities" { @@ -354,7 +776,10 @@ variable "requires_compatibilities" { variable "runtime_platform" { description = "Configuration block for `runtime_platform` that containers in your task may use" - type = any + type = object({ + cpu_architecture = optional(string, "X86_64") + operating_system_family = optional(string, "LINUX") + }) default = { operating_system_family = "LINUX" cpu_architecture = "X86_64" @@ -367,10 +792,54 @@ variable "skip_destroy" { default = null } +variable "task_definition_placement_constraints" { + description = "Configuration block for rules that are taken into consideration during task placement (up to max of 10). This is set at the task definition, see `placement_constraints` for setting at the service" + type = map(object({ + expression = optional(string) + type = string + })) + default = null +} + +variable "track_latest" { + description = "Whether should track latest `ACTIVE` task definition on AWS or the one created with the resource stored in state. Default is `false`. Useful in the event the task definition is modified outside of this resource" + type = bool + default = true +} + variable "volume" { description = "Configuration block for volumes that containers in your task may use" - type = any - default = {} + type = 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) + })) + default = null } variable "task_tags" { @@ -453,19 +922,39 @@ variable "create_task_exec_policy" { variable "task_exec_ssm_param_arns" { description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" type = list(string) - default = ["arn:aws:ssm:*:*:parameter/*"] + default = [] } variable "task_exec_secret_arns" { description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" type = list(string) - default = ["arn:aws:secretsmanager:*:*:secret:*"] + default = [] } variable "task_exec_iam_statements" { description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} + type = 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 + }))) + })) + default = null } variable "task_exec_iam_policy_path" { @@ -528,15 +1017,35 @@ variable "tasks_iam_role_tags" { } variable "tasks_iam_role_policies" { - description = "Map of IAM role policy ARNs to attach to the IAM role" + description = "Map of additioanl IAM role policy ARNs to attach to the IAM role" type = map(string) default = {} } variable "tasks_iam_role_statements" { description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} + type = 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 + }))) + })) + default = null } ################################################################################ @@ -551,14 +1060,11 @@ variable "external_id" { variable "scale" { description = "A floating-point percentage of the desired number of tasks to place and keep running in the task set" - type = any - default = {} -} - -variable "force_delete" { - description = "Whether to allow deleting the task set without waiting for scaling down to 0" - type = bool - default = null + type = object({ + unit = optional(string) + value = optional(number) + }) + default = null } variable "wait_until_stable" { @@ -597,7 +1103,59 @@ variable "autoscaling_max_capacity" { variable "autoscaling_policies" { description = "Map of autoscaling policies to create for the service" - type = any + type = map(object({ + name = optional(string) # Will fall back to the key name if not provided + policy_type = optional(string, "TargetTrackingScaling") + step_scaling_policy_configuration = optional(object({ + adjustment_type = optional(string) + cooldown = optional(number) + metric_aggregation_type = optional(string) + min_adjustment_magnitude = optional(number) + step_adjustments = 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, 300) + scale_out_cooldown = optional(number, 60) + target_value = optional(number, 75) + })) + })) default = { cpu = { policy_type = "TargetTrackingScaling" @@ -622,8 +1180,16 @@ variable "autoscaling_policies" { variable "autoscaling_scheduled_actions" { description = "Map of autoscaling scheduled actions to create for the service" - type = any - default = {} + type = map(object({ + name = optional(string) + min_capacity = number + max_capacity = number + schedule = string + start_time = optional(string) + end_time = optional(string) + timezone = optional(string) + })) + default = null } ################################################################################ @@ -654,10 +1220,40 @@ variable "security_group_description" { default = null } -variable "security_group_rules" { - description = "Security group rules to add to the security group created" - type = any - default = {} +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 = {} +} + +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 = {} } variable "security_group_tags" { @@ -665,3 +1261,55 @@ variable "security_group_tags" { type = map(string) default = {} } + +############################################################################################ +# ECS Infrastructure IAM role +############################################################################################ + +variable "create_infrastructure_iam_role" { + description = "Determines whether the ECS infrastructure IAM role should be created" + type = bool + default = true +} + +variable "infrastructure_iam_role_arn" { + description = "Existing IAM role ARN" + type = string + default = null +} + +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 +} + +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 = {} +} diff --git a/modules/service/versions.tf b/modules/service/versions.tf index 682191e7..db13b0a8 100644 --- a/modules/service/versions.tf +++ b/modules/service/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/variables.tf b/variables.tf index b624a302..92e071a1 100644 --- a/variables.tf +++ b/variables.tf @@ -4,6 +4,12 @@ variable "create" { default = true } +variable "region" { + description = "Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration" + type = string + default = null +} + variable "tags" { description = "A map of tags to add to all resources" type = map(string) @@ -14,21 +20,55 @@ variable "tags" { # Cluster ################################################################################ +variable "cluster_configuration" { + description = "The execute command configuration for the cluster" + type = 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) + })) + }) + default = { + execute_command_configuration = { + log_configuration = { + cloud_watch_log_group_name = "placeholder" # will use CloudWatch log group created by module + } + } + } +} + variable "cluster_name" { description = "Name of the cluster (up to 255 letters, numbers, hyphens, and underscores)" type = string default = "" } -variable "cluster_configuration" { - description = "The execute command configuration for the cluster" - type = any - default = {} +variable "cluster_service_connect_defaults" { + description = "Configures a default Service Connect namespace" + type = object({ + namespace = string + }) + default = null } -variable "cluster_settings" { +variable "cluster_setting" { description = "List of configuration block(s) with cluster settings. For example, this can be used to enable CloudWatch Container Insights for a cluster" - type = any + type = list(object({ + name = string + value = string + })) default = [ { name = "containerInsights" @@ -36,13 +76,6 @@ variable "cluster_settings" { } ] } - -variable "cluster_service_connect_defaults" { - description = "Configures a default Service Connect namespace" - type = map(string) - default = {} -} - variable "cluster_tags" { description = "A map of additional tags to add to the cluster" type = map(string) @@ -77,6 +110,12 @@ variable "cloudwatch_log_group_kms_key_id" { default = null } +variable "cloudwatch_log_group_class" { + description = "Specified the log class of the log group. Possible values are: `STANDARD` or `INFREQUENT_ACCESS`" + type = string + default = null +} + variable "cloudwatch_log_group_tags" { description = "A map of additional tags to add to the log group created" type = map(string) @@ -87,22 +126,33 @@ variable "cloudwatch_log_group_tags" { # Capacity Providers ################################################################################ -variable "default_capacity_provider_use_fargate" { - description = "Determines whether to use Fargate or autoscaling for default capacity provider strategy" - type = bool - default = true -} - -variable "fargate_capacity_providers" { - description = "Map of Fargate capacity provider definitions to use for the cluster" - type = any - default = {} -} - variable "autoscaling_capacity_providers" { description = "Map of autoscaling capacity provider definitions to create for the cluster" - type = any - default = {} + 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) + })) + managed_termination_protection = optional(string) + name = optional(string) # Will fall back to use map key if not set + tags = optional(map(string), {}) + })) + default = null +} + +variable "default_capacity_provider_strategy" { + description = "Map of default capacity provider strategy definitions to use for the cluster" + type = map(object({ + base = optional(number) + name = optional(string) # Will fall back to use map key if not set + weight = optional(number) + })) + default = null } ################################################################################ @@ -167,19 +217,39 @@ variable "create_task_exec_policy" { variable "task_exec_ssm_param_arns" { description = "List of SSM parameter ARNs the task execution role will be permitted to get/read" type = list(string) - default = ["arn:aws:ssm:*:*:parameter/*"] + default = [] } variable "task_exec_secret_arns" { description = "List of SecretsManager secret ARNs the task execution role will be permitted to get/read" type = list(string) - default = ["arn:aws:secretsmanager:*:*:secret:*"] + default = [] } variable "task_exec_iam_statements" { description = "A map of IAM policy [statements](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document#statement) for custom permission usage" - type = any - default = {} + 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(map(object({ + test = string + variable = string + values = list(string) + }))) + })) + default = null } ################################################################################ @@ -188,6 +258,735 @@ variable "task_exec_iam_statements" { variable "services" { description = "Map of service definitions to create" - type = any - default = {} + type = map(object({ + create = optional(bool, true) + create_service = optional(bool, true) + tags = optional(map(string), {}) + + # Service + ignore_task_definition_changes = optional(bool, false) + alarms = optional(object({ + alarm_names = list(string) + enable = optional(bool, true) + rollback = optional(bool, true) + })) + 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_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, true) + enable_execute_command = optional(bool, false) + force_delete = optional(bool) + force_new_deployment = optional(bool, true) + health_check_grace_period_seconds = optional(number) + launch_type = optional(string, "FARGATE") + load_balancer = optional(map(object({ + container_name = string + container_port = number + elb_name = optional(string) + target_group_arn = optional(string) + }))) + name = optional(string) # Will fall back to use map key if not set + assign_public_ip = optional(bool, false) + 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, true) + 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 + })) + 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 + })) + 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, "TASK_DEFINITION") + 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, true) + iam_role_arn = optional(string) + iam_role_name = optional(string) + iam_role_use_name_prefix = optional(bool, true) + 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, true) + task_definition_arn = optional(string) + container_definitions = optional(map(object({ + operating_system_family = optional(string, "LINUX") + tags = optional(map(string), {}) + + # Container definition + command = optional(list(string)) + cpu = optional(number) + 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, false) + 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, 30) + retries = optional(number, 3) + startPeriod = optional(number) + timeout = optional(number, 5) + })) + hostname = optional(string) + image = optional(string) + interactive = optional(bool, false) + 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, false) + maxSwap = optional(number) + sharedMemorySize = optional(number) + swappiness = optional(number) + tmpfs = optional(list(object({ + containerPath = string + mountOptions = optional(list(string)) + size = number + }))) + }), + # Default + { + initProcessEnabled = false + } + ) + 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, false) + pseudoTerminal = optional(bool, false) + readonlyRootFilesystem = optional(bool, true) + repositoryCredentials = optional(object({ + credentialsParameter = optional(string) + })) + resourceRequirements = optional(list(object({ + type = string + value = string + }))) + restartPolicy = optional(object({ + enabled = optional(bool, true) + ignoredExitCodes = optional(list(number)) + restartAttemptPeriod = optional(number) + }), + # Default + { + enabled = true + } + ) + secrets = optional(list(object({ + name = string + valueFrom = string + }))) + startTimeout = optional(number, 30) + stopTimeout = optional(number, 120) + 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, "disabled") + volumesFrom = optional(list(object({ + readOnly = optional(bool) + sourceContainer = optional(string) + })), []) + workingDirectory = optional(string) + + # Cloudwatch Log Group + service = optional(string, "") + enable_cloudwatch_logging = optional(bool, true) + create_cloudwatch_log_group = optional(bool, true) + cloudwatch_log_group_name = optional(string) + cloudwatch_log_group_use_name_prefix = optional(bool, false) + cloudwatch_log_group_class = optional(string) + cloudwatch_log_group_retention_in_days = optional(number, 14) + cloudwatch_log_group_kms_key_id = optional(string) + })), + # Default + {} + ) + container_definition_defaults = optional(object({ + operating_system_family = optional(string, "LINUX") + tags = optional(map(string), {}) + + # Container definition + command = optional(list(string)) + cpu = optional(number) + 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, false) + 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, 30) + retries = optional(number, 3) + startPeriod = optional(number) + timeout = optional(number, 5) + })) + hostname = optional(string) + image = optional(string) + interactive = optional(bool, false) + 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, false) + maxSwap = optional(number) + sharedMemorySize = optional(number) + swappiness = optional(number) + tmpfs = optional(list(object({ + containerPath = string + mountOptions = optional(list(string)) + size = number + }))) + }), + # Default + { + initProcessEnabled = false + } + ) + 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, false) + pseudoTerminal = optional(bool, false) + readonlyRootFilesystem = optional(bool, true) + repositoryCredentials = optional(object({ + credentialsParameter = optional(string) + })) + resourceRequirements = optional(list(object({ + type = string + value = string + }))) + restartPolicy = optional(object({ + enabled = optional(bool, true) + ignoredExitCodes = optional(list(number)) + restartAttemptPeriod = optional(number) + }), + # Default + { + enabled = true + } + ) + secrets = optional(list(object({ + name = string + valueFrom = string + }))) + startTimeout = optional(number, 30) + stopTimeout = optional(number, 120) + 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, "disabled") + volumesFrom = optional(list(object({ + readOnly = optional(bool) + sourceContainer = optional(string) + })), []) + workingDirectory = optional(string) + + # Cloudwatch Log Group + service = optional(string, "") + enable_cloudwatch_logging = optional(bool, true) + create_cloudwatch_log_group = optional(bool, true) + cloudwatch_log_group_name = optional(string) + cloudwatch_log_group_use_name_prefix = optional(bool, false) + cloudwatch_log_group_class = optional(string) + cloudwatch_log_group_retention_in_days = optional(number, 14) + cloudwatch_log_group_kms_key_id = optional(string) + }), + # Default + {} + ) + 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, "awsvpc") + pid_mode = optional(string) + proxy_configuration = optional(object({ + container_name = string + properties = optional(map(string)) + type = optional(string) + })) + requires_compatibilities = optional(list(string), ["FARGATE"]) + runtime_platform = optional(object({ + cpu_architecture = optional(string, "X86_64") + operating_system_family = optional(string, "LINUX") + }), + # Default + { + cpu_architecture = "X86_64" + operating_system_family = "LINUX" + } + ) + skip_destroy = optional(bool) + task_definition_placement_constraints = optional(map(object({ + expression = optional(string) + type = string + }))) + track_latest = optional(bool, true) + 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, true) + task_exec_iam_role_arn = optional(string) + task_exec_iam_role_name = optional(string) + task_exec_iam_role_use_name_prefix = optional(bool, true) + 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, true) + 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, true) + tasks_iam_role_arn = optional(string) + tasks_iam_role_name = optional(string) + tasks_iam_role_use_name_prefix = optional(bool, true) + 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, true) + autoscaling_min_capacity = optional(number, 1) + autoscaling_max_capacity = optional(number, 10) + autoscaling_policies = optional(map(object({ + name = optional(string) # Will fall back to the key name if not provided + policy_type = optional(string, "TargetTrackingScaling") + step_scaling_policy_configuration = optional(object({ + adjustment_type = optional(string) + cooldown = optional(number) + metric_aggregation_type = optional(string) + min_adjustment_magnitude = optional(number) + step_adjustments = 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, 300) + scale_out_cooldown = optional(number, 60) + target_value = optional(number, 75) + })) + })), + # Default + { + cpu = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + memory = { + policy_type = "TargetTrackingScaling" + + target_tracking_scaling_policy_configuration = { + predefined_metric_specification = { + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + } + ) + 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) + }))) + # Security Group + create_security_group = optional(bool, true) + security_group_name = optional(string) + security_group_use_name_prefix = optional(bool, true) + 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, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })), + # Default + {} + ) + 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, "tcp") + prefix_list_id = optional(string) + referenced_security_group_id = optional(string) + tags = optional(map(string), {}) + to_port = optional(string) + })), + # Default + {} + ) + security_group_tags = optional(map(string), {}) + # ECS Infrastructure IAM Role + create_infrastructure_iam_role = optional(bool, true) + infrastructure_iam_role_arn = optional(string) + infrastructure_iam_role_name = optional(string) + infrastructure_iam_role_use_name_prefix = optional(bool, true) + 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), {}) + })) + default = null } diff --git a/versions.tf b/versions.tf index 682191e7..db13b0a8 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/wrappers/cluster/main.tf b/wrappers/cluster/main.tf index 91f929d7..e18fc627 100644 --- a/wrappers/cluster/main.tf +++ b/wrappers/cluster/main.tf @@ -3,26 +3,33 @@ module "wrapper" { for_each = var.items - autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, {}) + 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, {}) - cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, {}) - cluster_name = try(each.value.cluster_name, var.defaults.cluster_name, "") - cluster_service_connect_defaults = try(each.value.cluster_service_connect_defaults, var.defaults.cluster_service_connect_defaults, {}) - cluster_settings = try(each.value.cluster_settings, var.defaults.cluster_settings, [ + configuration = try(each.value.configuration, var.defaults.configuration, { + execute_command_configuration = { + log_configuration = { + cloud_watch_log_group_name = "placeholder" + } + } + }) + 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) + 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) + setting = try(each.value.setting, var.defaults.setting, [ { name = "containerInsights" value = "enabled" } ]) - 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_use_fargate = try(each.value.default_capacity_provider_use_fargate, var.defaults.default_capacity_provider_use_fargate, true) - fargate_capacity_providers = try(each.value.fargate_capacity_providers, var.defaults.fargate_capacity_providers, {}) 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) task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) @@ -31,7 +38,7 @@ module "wrapper" { task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) - task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) - task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) - task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_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, []) } diff --git a/wrappers/cluster/versions.tf b/wrappers/cluster/versions.tf index 682191e7..db13b0a8 100644 --- a/wrappers/cluster/versions.tf +++ b/wrappers/cluster/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/wrappers/container-definition/main.tf b/wrappers/container-definition/main.tf index 7bcba25d..3dc15a8f 100644 --- a/wrappers/container-definition/main.tf +++ b/wrappers/container-definition/main.tf @@ -3,53 +3,61 @@ module "wrapper" { for_each = var.items + 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, 30) + cloudwatch_log_group_retention_in_days = try(each.value.cloudwatch_log_group_retention_in_days, var.defaults.cloudwatch_log_group_retention_in_days, 14) cloudwatch_log_group_use_name_prefix = try(each.value.cloudwatch_log_group_use_name_prefix, var.defaults.cloudwatch_log_group_use_name_prefix, false) - command = try(each.value.command, var.defaults.command, []) + command = try(each.value.command, var.defaults.command, null) cpu = try(each.value.cpu, var.defaults.cpu, null) create_cloudwatch_log_group = try(each.value.create_cloudwatch_log_group, var.defaults.create_cloudwatch_log_group, true) - dependencies = try(each.value.dependencies, var.defaults.dependencies, []) - disable_networking = try(each.value.disable_networking, var.defaults.disable_networking, null) - dns_search_domains = try(each.value.dns_search_domains, var.defaults.dns_search_domains, []) - dns_servers = try(each.value.dns_servers, var.defaults.dns_servers, []) - docker_labels = try(each.value.docker_labels, var.defaults.docker_labels, {}) - docker_security_options = try(each.value.docker_security_options, var.defaults.docker_security_options, []) + dependsOn = try(each.value.dependsOn, var.defaults.dependsOn, null) + disableNetworking = try(each.value.disableNetworking, var.defaults.disableNetworking, null) + dnsSearchDomains = try(each.value.dnsSearchDomains, var.defaults.dnsSearchDomains, null) + dnsServers = try(each.value.dnsServers, var.defaults.dnsServers, null) + dockerLabels = try(each.value.dockerLabels, var.defaults.dockerLabels, null) + dockerSecurityOptions = try(each.value.dockerSecurityOptions, var.defaults.dockerSecurityOptions, null) enable_cloudwatch_logging = try(each.value.enable_cloudwatch_logging, var.defaults.enable_cloudwatch_logging, true) enable_execute_command = try(each.value.enable_execute_command, var.defaults.enable_execute_command, false) entrypoint = try(each.value.entrypoint, var.defaults.entrypoint, []) environment = try(each.value.environment, var.defaults.environment, []) - environment_files = try(each.value.environment_files, var.defaults.environment_files, []) + environmentFiles = try(each.value.environmentFiles, var.defaults.environmentFiles, []) essential = try(each.value.essential, var.defaults.essential, null) - extra_hosts = try(each.value.extra_hosts, var.defaults.extra_hosts, []) - firelens_configuration = try(each.value.firelens_configuration, var.defaults.firelens_configuration, {}) - health_check = try(each.value.health_check, var.defaults.health_check, {}) + extraHosts = try(each.value.extraHosts, var.defaults.extraHosts, null) + firelensConfiguration = try(each.value.firelensConfiguration, var.defaults.firelensConfiguration, null) + healthCheck = try(each.value.healthCheck, var.defaults.healthCheck, null) hostname = try(each.value.hostname, var.defaults.hostname, null) image = try(each.value.image, var.defaults.image, null) interactive = try(each.value.interactive, var.defaults.interactive, false) - links = try(each.value.links, var.defaults.links, []) - linux_parameters = try(each.value.linux_parameters, var.defaults.linux_parameters, {}) - log_configuration = try(each.value.log_configuration, var.defaults.log_configuration, {}) - memory = try(each.value.memory, var.defaults.memory, null) - memory_reservation = try(each.value.memory_reservation, var.defaults.memory_reservation, null) - mount_points = try(each.value.mount_points, var.defaults.mount_points, []) - name = try(each.value.name, var.defaults.name, null) - operating_system_family = try(each.value.operating_system_family, var.defaults.operating_system_family, "LINUX") - port_mappings = try(each.value.port_mappings, var.defaults.port_mappings, []) - privileged = try(each.value.privileged, var.defaults.privileged, false) - pseudo_terminal = try(each.value.pseudo_terminal, var.defaults.pseudo_terminal, false) - readonly_root_filesystem = try(each.value.readonly_root_filesystem, var.defaults.readonly_root_filesystem, true) - repository_credentials = try(each.value.repository_credentials, var.defaults.repository_credentials, {}) - resource_requirements = try(each.value.resource_requirements, var.defaults.resource_requirements, []) - secrets = try(each.value.secrets, var.defaults.secrets, []) - service = try(each.value.service, var.defaults.service, "") - start_timeout = try(each.value.start_timeout, var.defaults.start_timeout, 30) - stop_timeout = try(each.value.stop_timeout, var.defaults.stop_timeout, 120) - system_controls = try(each.value.system_controls, var.defaults.system_controls, []) - tags = try(each.value.tags, var.defaults.tags, {}) - ulimits = try(each.value.ulimits, var.defaults.ulimits, []) - user = try(each.value.user, var.defaults.user, null) - volumes_from = try(each.value.volumes_from, var.defaults.volumes_from, []) - working_directory = try(each.value.working_directory, var.defaults.working_directory, null) + links = try(each.value.links, var.defaults.links, null) + linuxParameters = try(each.value.linuxParameters, var.defaults.linuxParameters, { + initProcessEnabled = false + }) + logConfiguration = try(each.value.logConfiguration, var.defaults.logConfiguration, {}) + memory = try(each.value.memory, var.defaults.memory, null) + memoryReservation = try(each.value.memoryReservation, var.defaults.memoryReservation, null) + mountPoints = try(each.value.mountPoints, var.defaults.mountPoints, []) + name = try(each.value.name, var.defaults.name, null) + operating_system_family = try(each.value.operating_system_family, var.defaults.operating_system_family, "LINUX") + portMappings = try(each.value.portMappings, var.defaults.portMappings, null) + privileged = try(each.value.privileged, var.defaults.privileged, false) + pseudoTerminal = try(each.value.pseudoTerminal, var.defaults.pseudoTerminal, false) + readonlyRootFilesystem = try(each.value.readonlyRootFilesystem, var.defaults.readonlyRootFilesystem, true) + region = try(each.value.region, var.defaults.region, null) + repositoryCredentials = try(each.value.repositoryCredentials, var.defaults.repositoryCredentials, null) + resourceRequirements = try(each.value.resourceRequirements, var.defaults.resourceRequirements, null) + restartPolicy = try(each.value.restartPolicy, var.defaults.restartPolicy, { + enabled = true + }) + secrets = try(each.value.secrets, var.defaults.secrets, null) + service = try(each.value.service, var.defaults.service, null) + startTimeout = try(each.value.startTimeout, var.defaults.startTimeout, 30) + stopTimeout = try(each.value.stopTimeout, var.defaults.stopTimeout, 120) + systemControls = try(each.value.systemControls, var.defaults.systemControls, []) + tags = try(each.value.tags, var.defaults.tags, {}) + ulimits = try(each.value.ulimits, var.defaults.ulimits, null) + user = try(each.value.user, var.defaults.user, null) + versionConsistency = try(each.value.versionConsistency, var.defaults.versionConsistency, "disabled") + volumesFrom = try(each.value.volumesFrom, var.defaults.volumesFrom, []) + workingDirectory = try(each.value.workingDirectory, var.defaults.workingDirectory, null) } diff --git a/wrappers/container-definition/versions.tf b/wrappers/container-definition/versions.tf index 682191e7..db13b0a8 100644 --- a/wrappers/container-definition/versions.tf +++ b/wrappers/container-definition/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/wrappers/main.tf b/wrappers/main.tf index 5aca8372..13659ca0 100644 --- a/wrappers/main.tf +++ b/wrappers/main.tf @@ -3,15 +3,22 @@ module "wrapper" { for_each = var.items - autoscaling_capacity_providers = try(each.value.autoscaling_capacity_providers, var.defaults.autoscaling_capacity_providers, {}) + 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, {}) - cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, {}) - cluster_name = try(each.value.cluster_name, var.defaults.cluster_name, "") - cluster_service_connect_defaults = try(each.value.cluster_service_connect_defaults, var.defaults.cluster_service_connect_defaults, {}) - cluster_settings = try(each.value.cluster_settings, var.defaults.cluster_settings, [ + cluster_configuration = try(each.value.cluster_configuration, var.defaults.cluster_configuration, { + execute_command_configuration = { + log_configuration = { + cloud_watch_log_group_name = "placeholder" + } + } + }) + cluster_name = try(each.value.cluster_name, var.defaults.cluster_name, "") + cluster_service_connect_defaults = try(each.value.cluster_service_connect_defaults, var.defaults.cluster_service_connect_defaults, null) + cluster_setting = try(each.value.cluster_setting, var.defaults.cluster_setting, [ { name = "containerInsights" value = "enabled" @@ -22,9 +29,9 @@ module "wrapper" { 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_use_fargate = try(each.value.default_capacity_provider_use_fargate, var.defaults.default_capacity_provider_use_fargate, true) - fargate_capacity_providers = try(each.value.fargate_capacity_providers, var.defaults.fargate_capacity_providers, {}) - services = try(each.value.services, var.defaults.services, {}) + 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) + 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) task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null) @@ -33,7 +40,7 @@ module "wrapper" { task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) - task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) - task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) - task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_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, []) } diff --git a/wrappers/service/main.tf b/wrappers/service/main.tf index 9a7d6aec..add99a0f 100644 --- a/wrappers/service/main.tf +++ b/wrappers/service/main.tf @@ -3,7 +3,7 @@ module "wrapper" { for_each = var.items - alarms = try(each.value.alarms, var.defaults.alarms, {}) + alarms = try(each.value.alarms, var.defaults.alarms, null) assign_public_ip = try(each.value.assign_public_ip, var.defaults.assign_public_ip, false) autoscaling_max_capacity = try(each.value.autoscaling_max_capacity, var.defaults.autoscaling_max_capacity, 10) autoscaling_min_capacity = try(each.value.autoscaling_min_capacity, var.defaults.autoscaling_min_capacity, 1) @@ -27,77 +27,88 @@ module "wrapper" { } } }) - autoscaling_scheduled_actions = try(each.value.autoscaling_scheduled_actions, var.defaults.autoscaling_scheduled_actions, {}) - capacity_provider_strategy = try(each.value.capacity_provider_strategy, var.defaults.capacity_provider_strategy, {}) - cluster_arn = try(each.value.cluster_arn, var.defaults.cluster_arn, "") - container_definition_defaults = try(each.value.container_definition_defaults, var.defaults.container_definition_defaults, {}) - container_definitions = try(each.value.container_definitions, var.defaults.container_definitions, {}) - cpu = try(each.value.cpu, var.defaults.cpu, 1024) - create = try(each.value.create, var.defaults.create, true) - create_iam_role = try(each.value.create_iam_role, var.defaults.create_iam_role, true) - create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) - create_service = try(each.value.create_service, var.defaults.create_service, true) - create_task_definition = try(each.value.create_task_definition, var.defaults.create_task_definition, true) - create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, true) - create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) - create_tasks_iam_role = try(each.value.create_tasks_iam_role, var.defaults.create_tasks_iam_role, true) - deployment_circuit_breaker = try(each.value.deployment_circuit_breaker, var.defaults.deployment_circuit_breaker, {}) - deployment_controller = try(each.value.deployment_controller, var.defaults.deployment_controller, {}) - deployment_maximum_percent = try(each.value.deployment_maximum_percent, var.defaults.deployment_maximum_percent, 200) - deployment_minimum_healthy_percent = try(each.value.deployment_minimum_healthy_percent, var.defaults.deployment_minimum_healthy_percent, 66) - desired_count = try(each.value.desired_count, var.defaults.desired_count, 1) - enable_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) - ephemeral_storage = try(each.value.ephemeral_storage, var.defaults.ephemeral_storage, {}) - external_id = try(each.value.external_id, var.defaults.external_id, null) - family = try(each.value.family, var.defaults.family, null) - force_delete = try(each.value.force_delete, var.defaults.force_delete, null) - force_new_deployment = try(each.value.force_new_deployment, var.defaults.force_new_deployment, true) - health_check_grace_period_seconds = try(each.value.health_check_grace_period_seconds, var.defaults.health_check_grace_period_seconds, null) - iam_role_arn = try(each.value.iam_role_arn, var.defaults.iam_role_arn, null) - iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) - iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) - iam_role_path = try(each.value.iam_role_path, var.defaults.iam_role_path, null) - iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.defaults.iam_role_permissions_boundary, null) - iam_role_statements = try(each.value.iam_role_statements, var.defaults.iam_role_statements, {}) - iam_role_tags = try(each.value.iam_role_tags, var.defaults.iam_role_tags, {}) - iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) - ignore_task_definition_changes = try(each.value.ignore_task_definition_changes, var.defaults.ignore_task_definition_changes, false) - inference_accelerator = try(each.value.inference_accelerator, var.defaults.inference_accelerator, {}) - ipc_mode = try(each.value.ipc_mode, var.defaults.ipc_mode, null) - launch_type = try(each.value.launch_type, var.defaults.launch_type, "FARGATE") - load_balancer = try(each.value.load_balancer, var.defaults.load_balancer, {}) - memory = try(each.value.memory, var.defaults.memory, 2048) - name = try(each.value.name, var.defaults.name, null) - 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, {}) - pid_mode = try(each.value.pid_mode, var.defaults.pid_mode, null) - placement_constraints = try(each.value.placement_constraints, var.defaults.placement_constraints, {}) - platform_version = try(each.value.platform_version, var.defaults.platform_version, null) - propagate_tags = try(each.value.propagate_tags, var.defaults.propagate_tags, null) - proxy_configuration = try(each.value.proxy_configuration, var.defaults.proxy_configuration, {}) - requires_compatibilities = try(each.value.requires_compatibilities, var.defaults.requires_compatibilities, ["FARGATE"]) + autoscaling_scheduled_actions = try(each.value.autoscaling_scheduled_actions, var.defaults.autoscaling_scheduled_actions, null) + availability_zone_rebalancing = try(each.value.availability_zone_rebalancing, var.defaults.availability_zone_rebalancing, null) + capacity_provider_strategy = try(each.value.capacity_provider_strategy, var.defaults.capacity_provider_strategy, null) + cluster_arn = try(each.value.cluster_arn, var.defaults.cluster_arn, "") + container_definition_defaults = try(each.value.container_definition_defaults, var.defaults.container_definition_defaults, {}) + container_definitions = try(each.value.container_definitions, var.defaults.container_definitions, {}) + cpu = try(each.value.cpu, var.defaults.cpu, 1024) + create = try(each.value.create, var.defaults.create, true) + create_iam_role = try(each.value.create_iam_role, var.defaults.create_iam_role, true) + create_infrastructure_iam_role = try(each.value.create_infrastructure_iam_role, var.defaults.create_infrastructure_iam_role, true) + create_security_group = try(each.value.create_security_group, var.defaults.create_security_group, true) + create_service = try(each.value.create_service, var.defaults.create_service, true) + create_task_definition = try(each.value.create_task_definition, var.defaults.create_task_definition, true) + create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, true) + create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true) + create_tasks_iam_role = try(each.value.create_tasks_iam_role, var.defaults.create_tasks_iam_role, true) + deployment_circuit_breaker = try(each.value.deployment_circuit_breaker, var.defaults.deployment_circuit_breaker, null) + deployment_controller = try(each.value.deployment_controller, var.defaults.deployment_controller, null) + deployment_maximum_percent = try(each.value.deployment_maximum_percent, var.defaults.deployment_maximum_percent, 200) + deployment_minimum_healthy_percent = try(each.value.deployment_minimum_healthy_percent, var.defaults.deployment_minimum_healthy_percent, 66) + desired_count = try(each.value.desired_count, var.defaults.desired_count, 1) + enable_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) + enable_fault_injection = try(each.value.enable_fault_injection, var.defaults.enable_fault_injection, null) + ephemeral_storage = try(each.value.ephemeral_storage, var.defaults.ephemeral_storage, null) + external_id = try(each.value.external_id, var.defaults.external_id, null) + family = try(each.value.family, var.defaults.family, null) + force_delete = try(each.value.force_delete, var.defaults.force_delete, null) + force_new_deployment = try(each.value.force_new_deployment, var.defaults.force_new_deployment, true) + health_check_grace_period_seconds = try(each.value.health_check_grace_period_seconds, var.defaults.health_check_grace_period_seconds, null) + iam_role_arn = try(each.value.iam_role_arn, var.defaults.iam_role_arn, null) + iam_role_description = try(each.value.iam_role_description, var.defaults.iam_role_description, null) + iam_role_name = try(each.value.iam_role_name, var.defaults.iam_role_name, null) + iam_role_path = try(each.value.iam_role_path, var.defaults.iam_role_path, null) + iam_role_permissions_boundary = try(each.value.iam_role_permissions_boundary, var.defaults.iam_role_permissions_boundary, null) + iam_role_statements = try(each.value.iam_role_statements, var.defaults.iam_role_statements, null) + iam_role_tags = try(each.value.iam_role_tags, var.defaults.iam_role_tags, {}) + iam_role_use_name_prefix = try(each.value.iam_role_use_name_prefix, var.defaults.iam_role_use_name_prefix, true) + ignore_task_definition_changes = try(each.value.ignore_task_definition_changes, var.defaults.ignore_task_definition_changes, false) + infrastructure_iam_role_arn = try(each.value.infrastructure_iam_role_arn, var.defaults.infrastructure_iam_role_arn, null) + 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_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_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) + ipc_mode = try(each.value.ipc_mode, var.defaults.ipc_mode, null) + 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) + 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) + placement_constraints = try(each.value.placement_constraints, var.defaults.placement_constraints, null) + platform_version = try(each.value.platform_version, var.defaults.platform_version, null) + propagate_tags = try(each.value.propagate_tags, var.defaults.propagate_tags, null) + proxy_configuration = try(each.value.proxy_configuration, var.defaults.proxy_configuration, null) + region = try(each.value.region, var.defaults.region, null) + requires_compatibilities = try(each.value.requires_compatibilities, var.defaults.requires_compatibilities, ["FARGATE"]) runtime_platform = try(each.value.runtime_platform, var.defaults.runtime_platform, { operating_system_family = "LINUX" cpu_architecture = "X86_64" }) - scale = try(each.value.scale, var.defaults.scale, {}) + scale = try(each.value.scale, var.defaults.scale, null) scheduling_strategy = try(each.value.scheduling_strategy, var.defaults.scheduling_strategy, 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, {}) security_group_ids = try(each.value.security_group_ids, var.defaults.security_group_ids, []) + 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_rules = try(each.value.security_group_rules, var.defaults.security_group_rules, {}) security_group_tags = try(each.value.security_group_tags, var.defaults.security_group_tags, {}) security_group_use_name_prefix = try(each.value.security_group_use_name_prefix, var.defaults.security_group_use_name_prefix, true) - service_connect_configuration = try(each.value.service_connect_configuration, var.defaults.service_connect_configuration, {}) - service_registries = try(each.value.service_registries, var.defaults.service_registries, {}) + service_connect_configuration = try(each.value.service_connect_configuration, var.defaults.service_connect_configuration, null) + service_registries = try(each.value.service_registries, var.defaults.service_registries, null) service_tags = try(each.value.service_tags, var.defaults.service_tags, {}) skip_destroy = try(each.value.skip_destroy, var.defaults.skip_destroy, null) subnet_ids = try(each.value.subnet_ids, var.defaults.subnet_ids, []) tags = try(each.value.tags, var.defaults.tags, {}) task_definition_arn = try(each.value.task_definition_arn, var.defaults.task_definition_arn, null) - task_definition_placement_constraints = try(each.value.task_definition_placement_constraints, var.defaults.task_definition_placement_constraints, {}) + task_definition_placement_constraints = try(each.value.task_definition_placement_constraints, var.defaults.task_definition_placement_constraints, null) task_exec_iam_policy_path = try(each.value.task_exec_iam_policy_path, var.defaults.task_exec_iam_policy_path, null) task_exec_iam_role_arn = try(each.value.task_exec_iam_role_arn, var.defaults.task_exec_iam_role_arn, null) task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null) @@ -108,9 +119,9 @@ module "wrapper" { task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {}) task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {}) task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true) - task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, {}) - task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, ["arn:aws:secretsmanager:*:*:secret:*"]) - task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, ["arn:aws:ssm:*:*:parameter/*"]) + task_exec_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, []) task_tags = try(each.value.task_tags, var.defaults.task_tags, {}) tasks_iam_role_arn = try(each.value.tasks_iam_role_arn, var.defaults.tasks_iam_role_arn, null) tasks_iam_role_description = try(each.value.tasks_iam_role_description, var.defaults.tasks_iam_role_description, null) @@ -118,12 +129,15 @@ module "wrapper" { tasks_iam_role_path = try(each.value.tasks_iam_role_path, var.defaults.tasks_iam_role_path, null) tasks_iam_role_permissions_boundary = try(each.value.tasks_iam_role_permissions_boundary, var.defaults.tasks_iam_role_permissions_boundary, null) tasks_iam_role_policies = try(each.value.tasks_iam_role_policies, var.defaults.tasks_iam_role_policies, {}) - tasks_iam_role_statements = try(each.value.tasks_iam_role_statements, var.defaults.tasks_iam_role_statements, {}) + tasks_iam_role_statements = try(each.value.tasks_iam_role_statements, var.defaults.tasks_iam_role_statements, null) tasks_iam_role_tags = try(each.value.tasks_iam_role_tags, var.defaults.tasks_iam_role_tags, {}) tasks_iam_role_use_name_prefix = try(each.value.tasks_iam_role_use_name_prefix, var.defaults.tasks_iam_role_use_name_prefix, true) - timeouts = try(each.value.timeouts, var.defaults.timeouts, {}) - triggers = try(each.value.triggers, var.defaults.triggers, {}) - volume = try(each.value.volume, var.defaults.volume, {}) + timeouts = try(each.value.timeouts, var.defaults.timeouts, null) + track_latest = try(each.value.track_latest, var.defaults.track_latest, true) + triggers = try(each.value.triggers, var.defaults.triggers, null) + volume = try(each.value.volume, var.defaults.volume, null) + volume_configuration = try(each.value.volume_configuration, var.defaults.volume_configuration, null) + vpc_lattice_configurations = try(each.value.vpc_lattice_configurations, var.defaults.vpc_lattice_configurations, null) wait_for_steady_state = try(each.value.wait_for_steady_state, var.defaults.wait_for_steady_state, null) wait_until_stable = try(each.value.wait_until_stable, var.defaults.wait_until_stable, null) wait_until_stable_timeout = try(each.value.wait_until_stable_timeout, var.defaults.wait_until_stable_timeout, null) diff --git a/wrappers/service/versions.tf b/wrappers/service/versions.tf index 682191e7..db13b0a8 100644 --- a/wrappers/service/versions.tf +++ b/wrappers/service/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } } diff --git a/wrappers/versions.tf b/wrappers/versions.tf index 682191e7..db13b0a8 100644 --- a/wrappers/versions.tf +++ b/wrappers/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 1.0" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.66.1" + version = ">= 6.0" } } }