diff --git a/examples/task/README.md b/examples/task/README.md
new file mode 100644
index 0000000..e5cacb1
--- /dev/null
+++ b/examples/task/README.md
@@ -0,0 +1,62 @@
+# ECS Task Definition Example
+
+Configuration in this directory creates:
+
+- ECS Task Definition using the standalone task module
+- ECS Cluster with a task definition using the complete module
+- Associated IAM roles for task execution and tasks
+
+## 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.4 |
+
+## Providers
+
+No providers.
+
+## Modules
+
+| Name | Source | Version |
+| ----------------------------------------------------------------------- | ------------------ | ------- |
+| [ecs_complete](#module_ecs_complete) | ../../ | n/a |
+| [ecs_task](#module_ecs_task) | ../../modules/task | n/a |
+
+## Resources
+
+No resources.
+
+## Inputs
+
+No inputs.
+
+## Outputs
+
+| Name | Description |
+| ----------------------------------------------------------------------------------------------------- | ----------------------------------------- |
+| [cluster_arn](#output_cluster_arn) | ARN that identifies the cluster |
+| [cluster_id](#output_cluster_id) | ID that identifies the cluster |
+| [cluster_name](#output_cluster_name) | Name that identifies the cluster |
+| [task_definition_arn](#output_task_definition_arn) | Full ARN of the task definition |
+| [task_definition_family](#output_task_definition_family) | The unique name of the task definition |
+| [task_exec_iam_role_arn](#output_task_exec_iam_role_arn) | Task execution IAM role ARN |
+| [tasks](#output_tasks) | Map of tasks created and their attributes |
+| [tasks_iam_role_arn](#output_tasks_iam_role_arn) | Tasks IAM role ARN |
+
+
diff --git a/examples/task/main.tf b/examples/task/main.tf
new file mode 100644
index 0000000..eefdc3b
--- /dev/null
+++ b/examples/task/main.tf
@@ -0,0 +1,111 @@
+provider "aws" {
+ region = local.region
+}
+
+locals {
+ region = "us-east-1"
+ name = "ex-${basename(path.cwd)}"
+
+ tags = {
+ Name = local.name
+ Example = local.name
+ Repository = "https://github.com/terraform-aws-modules/terraform-aws-ecs"
+ }
+}
+
+################################################################################
+# ECS Module - Task Only
+################################################################################
+
+module "ecs_task" {
+ source = "../../modules/task"
+
+ name = "${local.name}-task"
+
+ # Container definitions
+ container_definitions = {
+ nginx = {
+ cpu = 256
+ memory = 512
+ essential = true
+ image = "public.ecr.aws/nginx/nginx:latest"
+ portMappings = [
+ {
+ name = "nginx"
+ containerPort = 80
+ protocol = "tcp"
+ }
+ ]
+
+ # Enable logging
+ enable_cloudwatch_logging = true
+ create_cloudwatch_log_group = true
+ cloudwatch_log_group_retention_in_days = 1
+ }
+ }
+
+ cpu = 512
+ memory = 1024
+ requires_compatibilities = ["FARGATE"]
+ network_mode = "awsvpc"
+
+ runtime_platform = {
+ operating_system_family = "LINUX"
+ cpu_architecture = "X86_64"
+ }
+
+ # Task execution role
+ create_task_exec_iam_role = true
+
+ # Task role
+ create_tasks_iam_role = true
+
+ tags = local.tags
+}
+
+################################################################################
+# ECS Module - Complete (Cluster + Task)
+################################################################################
+
+module "ecs_complete" {
+ source = "../../"
+
+ cluster_name = local.name
+
+ # Task definitions
+ tasks = {
+ standalone-task = {
+ name = "${local.name}-standalone"
+
+ container_definitions = {
+ httpd = {
+ cpu = 256
+ memory = 512
+ essential = true
+ image = "public.ecr.aws/docker/library/httpd:latest"
+ portMappings = [
+ {
+ name = "httpd"
+ containerPort = 80
+ protocol = "tcp"
+ }
+ ]
+
+ enable_cloudwatch_logging = true
+ create_cloudwatch_log_group = true
+ cloudwatch_log_group_retention_in_days = 1
+ }
+ }
+
+ cpu = 512
+ memory = 1024
+ requires_compatibilities = ["FARGATE"]
+ network_mode = "awsvpc"
+
+ create_task_exec_iam_role = true
+ create_tasks_iam_role = true
+ }
+ }
+
+ tags = local.tags
+}
diff --git a/examples/task/outputs.tf b/examples/task/outputs.tf
new file mode 100644
index 0000000..954a5b4
--- /dev/null
+++ b/examples/task/outputs.tf
@@ -0,0 +1,47 @@
+################################################################################
+# Task Module
+################################################################################
+
+output "task_definition_arn" {
+ description = "Full ARN of the task definition"
+ value = module.ecs_task.task_definition_arn
+}
+
+output "task_definition_family" {
+ description = "The unique name of the task definition"
+ value = module.ecs_task.task_definition_family
+}
+
+output "task_exec_iam_role_arn" {
+ description = "Task execution IAM role ARN"
+ value = module.ecs_task.task_exec_iam_role_arn
+}
+
+output "tasks_iam_role_arn" {
+ description = "Tasks IAM role ARN"
+ value = module.ecs_task.tasks_iam_role_arn
+}
+
+################################################################################
+# Complete Module
+################################################################################
+
+output "cluster_arn" {
+ description = "ARN that identifies the cluster"
+ value = module.ecs_complete.cluster_arn
+}
+
+output "cluster_id" {
+ description = "ID that identifies the cluster"
+ value = module.ecs_complete.cluster_id
+}
+
+output "cluster_name" {
+ description = "Name that identifies the cluster"
+ value = module.ecs_complete.cluster_name
+}
+
+output "tasks" {
+ description = "Map of tasks created and their attributes"
+ value = module.ecs_complete.tasks
+}
diff --git a/examples/task/versions.tf b/examples/task/versions.tf
new file mode 100644
index 0000000..497e3e6
--- /dev/null
+++ b/examples/task/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.5.7"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.4"
+ }
+ }
+}
diff --git a/main.tf b/main.tf
index 5e1d1fe..e8013c9 100644
--- a/main.tf
+++ b/main.tf
@@ -192,3 +192,70 @@ module "service" {
tags = merge(var.tags, each.value.tags)
}
+
+################################################################################
+# Task(s)
+################################################################################
+
+module "task" {
+ source = "./modules/task"
+
+ for_each = var.create && var.tasks != null ? var.tasks : {}
+
+ create = each.value.create
+ region = var.region
+
+ # Task definition
+ name = coalesce(each.value.name, each.key)
+ enable_execute_command = each.value.enable_execute_command
+ create_task_definition = each.value.create_task_definition
+ task_definition_arn = each.value.task_definition_arn
+ container_definitions = each.value.container_definitions
+ 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 = each.value.task_exec_iam_role_arn
+ 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
+ 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 = 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
+
+ tags = merge(var.tags, each.value.tags)
+}
diff --git a/modules/service/main.tf b/modules/service/main.tf
index d463378..2f82948 100644
--- a/modules/service/main.tf
+++ b/modules/service/main.tf
@@ -793,509 +793,79 @@ resource "aws_iam_role_policy_attachment" "service" {
policy_arn = aws_iam_policy.service[0].arn
}
-################################################################################
-# Container Definition
-################################################################################
-
-module "container_definition" {
- source = "../container-definition"
-
- region = var.region
-
- for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition && v.create }
-
- enable_execute_command = var.enable_execute_command
- operating_system_family = var.runtime_platform.operating_system_family
-
- # Container Definition
- command = each.value.command
- cpu = each.value.cpu
- dependsOn = each.value.dependsOn
- disableNetworking = each.value.disableNetworking
- dnsSearchDomains = each.value.dnsSearchDomains
- dnsServers = each.value.dnsServers
- dockerLabels = each.value.dockerLabels
- dockerSecurityOptions = each.value.dockerSecurityOptions
- entrypoint = each.value.entrypoint
- environment = each.value.environment
- environmentFiles = each.value.environmentFiles
- essential = each.value.essential
- extraHosts = each.value.extraHosts
- firelensConfiguration = each.value.firelensConfiguration
- healthCheck = each.value.healthCheck
- hostname = each.value.hostname
- image = each.value.image
- interactive = each.value.interactive
- links = each.value.links
- linuxParameters = each.value.linuxParameters
- logConfiguration = each.value.logConfiguration
- memory = each.value.memory
- memoryReservation = each.value.memoryReservation
- mountPoints = each.value.mountPoints
- name = coalesce(each.value.name, each.key)
- portMappings = each.value.portMappings
- privileged = each.value.privileged
- pseudoTerminal = each.value.pseudoTerminal
- readonlyRootFilesystem = each.value.readonlyRootFilesystem
- repositoryCredentials = each.value.repositoryCredentials
- resourceRequirements = each.value.resourceRequirements
- restartPolicy = each.value.restartPolicy
- secrets = each.value.secrets
- startTimeout = each.value.startTimeout
- stopTimeout = each.value.stopTimeout
- systemControls = each.value.systemControls
- ulimits = each.value.ulimits
- user = each.value.user
- versionConsistency = each.value.versionConsistency
- volumesFrom = each.value.volumesFrom
- workingDirectory = each.value.workingDirectory
-
- # CloudWatch Log Group
- service = var.name
- enable_cloudwatch_logging = each.value.enable_cloudwatch_logging
- create_cloudwatch_log_group = each.value.create_cloudwatch_log_group
- cloudwatch_log_group_name = each.value.cloudwatch_log_group_name
- cloudwatch_log_group_use_name_prefix = each.value.cloudwatch_log_group_use_name_prefix
- cloudwatch_log_group_class = each.value.cloudwatch_log_group_class
- cloudwatch_log_group_retention_in_days = each.value.cloudwatch_log_group_retention_in_days
- cloudwatch_log_group_kms_key_id = each.value.cloudwatch_log_group_kms_key_id
-
- tags = var.tags
-}
-
################################################################################
# Task Definition
################################################################################
-locals {
- create_task_definition = var.create && var.create_task_definition
- task_definition = local.create_task_definition ? aws_ecs_task_definition.this[0].arn : var.task_definition_arn
-}
+module "task" {
+ source = "../task"
-resource "aws_ecs_task_definition" "this" {
count = local.create_task_definition ? 1 : 0
+ create = var.create
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
- enable_fault_injection = var.enable_fault_injection
-
- dynamic "ephemeral_storage" {
- for_each = var.ephemeral_storage != null ? [var.ephemeral_storage] : []
-
- content {
- size_in_gib = ephemeral_storage.value.size_in_gib
- }
- }
-
- execution_role_arn = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn)
- family = coalesce(var.family, var.name)
-
- 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 != null ? var.task_definition_placement_constraints : {}
-
- content {
- expression = placement_constraints.value.expression
- type = placement_constraints.value.type
- }
- }
-
- dynamic "proxy_configuration" {
- for_each = var.proxy_configuration != null ? [var.proxy_configuration] : []
-
- content {
- container_name = proxy_configuration.value.container_name
- properties = proxy_configuration.value.properties
- type = proxy_configuration.value.type
- }
- }
-
- requires_compatibilities = var.requires_compatibilities
-
- dynamic "runtime_platform" {
- for_each = var.runtime_platform != null ? [var.runtime_platform] : []
-
- content {
- 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 != null ? var.volume : {}
-
- content {
- configure_at_launch = volume.value.configure_at_launch
-
- dynamic "docker_volume_configuration" {
- for_each = volume.value.docker_volume_configuration != null ? [volume.value.docker_volume_configuration] : []
-
- content {
- 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 = volume.value.efs_volume_configuration != null ? [volume.value.efs_volume_configuration] : []
-
- content {
- dynamic "authorization_config" {
- for_each = efs_volume_configuration.value.authorization_config != null ? [efs_volume_configuration.value.authorization_config] : []
-
- content {
- 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 = 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 = volume.value.fsx_windows_file_server_volume_configuration != null ? [volume.value.fsx_windows_file_server_volume_configuration] : []
-
- content {
- dynamic "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
- domain = authorization_config.value.domain
- }
- }
+ # Task definition
+ name = var.name
+ enable_execute_command = var.enable_execute_command
+ create_task_definition = var.create_task_definition
+ task_definition_arn = var.task_definition_arn
+ container_definitions = var.container_definitions
+ cpu = var.cpu
+ enable_fault_injection = var.enable_fault_injection
+ ephemeral_storage = var.ephemeral_storage
+ family = var.family
+ ipc_mode = var.ipc_mode
+ memory = var.memory
+ network_mode = var.network_mode
+ pid_mode = var.pid_mode
+ proxy_configuration = var.proxy_configuration
+ requires_compatibilities = var.requires_compatibilities
+ runtime_platform = var.runtime_platform
+ skip_destroy = var.skip_destroy
+ task_definition_placement_constraints = var.task_definition_placement_constraints
+ track_latest = var.track_latest
+ volume = var.volume
+ task_tags = var.task_tags
+
+ # Task Execution IAM role
+ create_task_exec_iam_role = var.create_task_exec_iam_role
+ task_exec_iam_role_arn = var.task_exec_iam_role_arn
+ task_exec_iam_role_name = var.task_exec_iam_role_name
+ task_exec_iam_role_use_name_prefix = var.task_exec_iam_role_use_name_prefix
+ task_exec_iam_role_path = var.task_exec_iam_role_path
+ task_exec_iam_role_description = var.task_exec_iam_role_description
+ task_exec_iam_role_permissions_boundary = var.task_exec_iam_role_permissions_boundary
+ task_exec_iam_role_tags = var.task_exec_iam_role_tags
+ task_exec_iam_role_policies = var.task_exec_iam_role_policies
+ task_exec_iam_role_max_session_duration = var.task_exec_iam_role_max_session_duration
+ create_task_exec_policy = var.create_task_exec_policy
+ task_exec_ssm_param_arns = var.task_exec_ssm_param_arns
+ task_exec_secret_arns = var.task_exec_secret_arns
+ task_exec_iam_statements = var.task_exec_iam_statements
+ task_exec_iam_policy_path = var.task_exec_iam_policy_path
+
+ # Tasks IAM role
+ create_tasks_iam_role = var.create_tasks_iam_role
+ tasks_iam_role_arn = var.tasks_iam_role_arn
+ tasks_iam_role_name = var.tasks_iam_role_name
+ tasks_iam_role_use_name_prefix = var.tasks_iam_role_use_name_prefix
+ tasks_iam_role_path = var.tasks_iam_role_path
+ tasks_iam_role_description = var.tasks_iam_role_description
+ tasks_iam_role_permissions_boundary = var.tasks_iam_role_permissions_boundary
+ tasks_iam_role_tags = var.tasks_iam_role_tags
+ tasks_iam_role_policies = var.tasks_iam_role_policies
+ tasks_iam_role_statements = var.tasks_iam_role_statements
- file_system_id = fsx_windows_file_server_volume_configuration.value.file_system_id
- root_directory = fsx_windows_file_server_volume_configuration.value.root_directory
- }
- }
-
- host_path = volume.value.host_path
- name = coalesce(volume.value.name, volume.key)
- }
- }
-
- tags = merge(var.tags, var.task_tags)
-
- depends_on = [
- aws_iam_role_policy_attachment.tasks,
- aws_iam_role_policy_attachment.task_exec,
- aws_iam_role_policy_attachment.task_exec_additional,
- ]
-
- lifecycle {
- create_before_destroy = true
- }
-}
-
-################################################################################
-# Task Execution - IAM Role
-# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html
-################################################################################
-
-locals {
- task_exec_iam_role_name = 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
-}
-
-data "aws_iam_policy_document" "task_exec_assume" {
- count = local.create_task_exec_iam_role ? 1 : 0
-
- statement {
- sid = "ECSTaskExecutionAssumeRole"
- actions = ["sts:AssumeRole"]
-
- principals {
- type = "Service"
- identifiers = ["ecs-tasks.amazonaws.com"]
- }
- }
-}
-
-resource "aws_iam_role" "task_exec" {
- count = local.create_task_exec_iam_role ? 1 : 0
-
- name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name
- name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null
- path = var.task_exec_iam_role_path
- description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${local.task_exec_iam_role_name}")
-
- assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json
- max_session_duration = var.task_exec_iam_role_max_session_duration
- permissions_boundary = var.task_exec_iam_role_permissions_boundary
- force_detach_policies = true
-
- tags = merge(var.tags, var.task_exec_iam_role_tags)
-}
-
-resource "aws_iam_role_policy_attachment" "task_exec_additional" {
- for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role }
-
- role = aws_iam_role.task_exec[0].name
- policy_arn = each.value
-}
-
-data "aws_iam_policy_document" "task_exec" {
- count = local.create_task_exec_policy ? 1 : 0
-
- # Pulled from AmazonECSTaskExecutionRolePolicy
- statement {
- sid = "Logs"
- actions = [
- "logs:CreateLogStream",
- "logs:PutLogEvents",
- ]
- resources = ["*"]
- }
-
- # Pulled from AmazonECSTaskExecutionRolePolicy
- statement {
- sid = "ECR"
- actions = [
- "ecr:GetAuthorizationToken",
- "ecr:BatchCheckLayerAvailability",
- "ecr:GetDownloadUrlForLayer",
- "ecr:BatchGetImage",
- ]
- resources = ["*"]
- }
-
- dynamic "statement" {
- for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : []
-
- content {
- sid = "GetSSMParams"
- actions = ["ssm:GetParameters"]
- resources = var.task_exec_ssm_param_arns
- }
- }
-
- dynamic "statement" {
- for_each = length(var.task_exec_secret_arns) > 0 ? [1] : []
-
- content {
- sid = "GetSecrets"
- actions = ["secretsmanager:GetSecretValue"]
- resources = var.task_exec_secret_arns
- }
- }
-
- dynamic "statement" {
- for_each = var.task_exec_iam_statements != null ? var.task_exec_iam_statements : []
-
- content {
- 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 = statement.value.principals != null ? statement.value.principals : []
-
- content {
- type = principals.value.type
- identifiers = principals.value.identifiers
- }
- }
-
- dynamic "not_principals" {
- for_each = statement.value.not_principals != null ? statement.value.not_principals : []
-
- content {
- type = not_principals.value.type
- identifiers = not_principals.value.identifiers
- }
- }
-
- dynamic "condition" {
- for_each = statement.value.condition != null ? statement.value.condition : []
-
- content {
- test = condition.value.test
- values = condition.value.values
- variable = condition.value.variable
- }
- }
- }
- }
-}
-
-resource "aws_iam_policy" "task_exec" {
- count = local.create_task_exec_policy ? 1 : 0
-
- name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name
- name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null
- description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy")
- policy = data.aws_iam_policy_document.task_exec[0].json
- path = var.task_exec_iam_policy_path
- tags = merge(var.tags, var.task_exec_iam_role_tags)
-}
-
-resource "aws_iam_role_policy_attachment" "task_exec" {
- count = local.create_task_exec_policy ? 1 : 0
-
- role = aws_iam_role.task_exec[0].name
- policy_arn = aws_iam_policy.task_exec[0].arn
+ tags = var.tags
}
-################################################################################
-# Tasks - IAM role
-# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
-################################################################################
-
locals {
- 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
-}
-
-data "aws_iam_policy_document" "tasks_assume" {
- count = local.create_tasks_iam_role ? 1 : 0
-
- statement {
- sid = "ECSTasksAssumeRole"
- actions = ["sts:AssumeRole"]
-
- principals {
- type = "Service"
- identifiers = ["ecs-tasks.amazonaws.com"]
- }
-
- # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role
- condition {
- test = "ArnLike"
- variable = "aws:SourceArn"
- values = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:*"]
- }
-
- condition {
- test = "StringEquals"
- variable = "aws:SourceAccount"
- values = [local.account_id]
- }
- }
-}
-
-resource "aws_iam_role" "tasks" {
- count = local.create_tasks_iam_role ? 1 : 0
-
- name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name
- name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null
- path = var.tasks_iam_role_path
- description = var.tasks_iam_role_description
-
- assume_role_policy = data.aws_iam_policy_document.tasks_assume[0].json
- permissions_boundary = var.tasks_iam_role_permissions_boundary
- force_detach_policies = true
-
- tags = merge(var.tags, var.tasks_iam_role_tags)
-}
-
-data "aws_iam_policy_document" "tasks" {
- 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] : []
-
- content {
- sid = "ECSExec"
- actions = [
- "ssmmessages:CreateControlChannel",
- "ssmmessages:CreateDataChannel",
- "ssmmessages:OpenControlChannel",
- "ssmmessages:OpenDataChannel",
- ]
- resources = ["*"]
- }
- }
-
- dynamic "statement" {
- for_each = var.tasks_iam_role_statements != null ? var.tasks_iam_role_statements : []
-
- content {
- 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 = statement.value.principals != null ? statement.value.principals : []
-
- content {
- type = principals.value.type
- identifiers = principals.value.identifiers
- }
- }
-
- dynamic "not_principals" {
- for_each = statement.value.not_principals != null ? statement.value.not_principals : []
-
- content {
- type = not_principals.value.type
- identifiers = not_principals.value.identifiers
- }
- }
-
- dynamic "condition" {
- for_each = statement.value.condition != null ? statement.value.condition : []
-
- content {
- test = condition.value.test
- values = condition.value.values
- variable = condition.value.variable
- }
- }
- }
- }
-}
-
-resource "aws_iam_policy" "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
- 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
+ create_task_definition = var.create && var.create_task_definition
+ task_definition = local.create_task_definition ? module.task[0].task_definition_arn : var.task_definition_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
-}
################################################################################
# Task Set
diff --git a/modules/service/outputs.tf b/modules/service/outputs.tf
index 0d81c8f..ce800c0 100644
--- a/modules/service/outputs.tf
+++ b/modules/service/outputs.tf
@@ -32,31 +32,27 @@ output "iam_role_unique_id" {
}
################################################################################
-# Container Definition
-################################################################################
-
-output "container_definitions" {
- description = "Container definitions"
- value = module.container_definition
-}
-
-################################################################################
-# Task Definition
+# Task Module
################################################################################
output "task_definition_arn" {
description = "Full ARN of the Task Definition (including both `family` and `revision`)"
- value = try(aws_ecs_task_definition.this[0].arn, var.task_definition_arn)
+ value = local.create_task_definition ? try(module.task[0].task_definition_arn, null) : var.task_definition_arn
}
output "task_definition_revision" {
description = "Revision of the task in a particular family"
- value = try(aws_ecs_task_definition.this[0].revision, null)
+ value = local.create_task_definition ? try(module.task[0].task_definition_revision, null) : null
}
output "task_definition_family" {
description = "The unique name of the task definition"
- value = try(aws_ecs_task_definition.this[0].family, null)
+ value = local.create_task_definition ? try(module.task[0].task_definition_family, null) : null
+}
+
+output "container_definitions" {
+ description = "Container definitions"
+ value = local.create_task_definition ? try(module.task[0].container_definitions, {}) : {}
}
################################################################################
@@ -66,17 +62,17 @@ output "task_definition_family" {
output "task_exec_iam_role_name" {
description = "Task execution IAM role name"
- value = try(aws_iam_role.task_exec[0].name, null)
+ value = local.create_task_definition ? try(module.task[0].task_exec_iam_role_name, null) : null
}
output "task_exec_iam_role_arn" {
description = "Task execution IAM role ARN"
- value = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn)
+ value = local.create_task_definition ? module.task[0].task_exec_iam_role_arn : var.task_exec_iam_role_arn
}
output "task_exec_iam_role_unique_id" {
description = "Stable and unique string identifying the task execution IAM role"
- value = try(aws_iam_role.task_exec[0].unique_id, null)
+ value = local.create_task_definition ? try(module.task[0].task_exec_iam_role_unique_id, null) : null
}
################################################################################
@@ -86,17 +82,17 @@ output "task_exec_iam_role_unique_id" {
output "tasks_iam_role_name" {
description = "Tasks IAM role name"
- value = try(aws_iam_role.tasks[0].name, null)
+ value = local.create_task_definition ? try(module.task[0].tasks_iam_role_name, null) : null
}
output "tasks_iam_role_arn" {
description = "Tasks IAM role ARN"
- value = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn)
+ value = local.create_task_definition ? module.task[0].tasks_iam_role_arn : var.tasks_iam_role_arn
}
output "tasks_iam_role_unique_id" {
description = "Stable and unique string identifying the tasks IAM role"
- value = try(aws_iam_role.tasks[0].unique_id, null)
+ value = local.create_task_definition ? try(module.task[0].tasks_iam_role_unique_id, null) : null
}
################################################################################
diff --git a/modules/task/README.md b/modules/task/README.md
new file mode 100644
index 0000000..55bb548
--- /dev/null
+++ b/modules/task/README.md
@@ -0,0 +1,102 @@
+# AWS ECS Task Terraform sub-module
+
+Terraform sub-module which creates ECS (Elastic Container Service) task definition and related IAM resources on AWS.
+
+## Usage
+
+```hcl
+module "ecs_task" {
+ source = "terraform-aws-modules/ecs/aws//modules/task"
+
+ name = "my-task"
+
+ container_definitions = {
+ app = {
+ cpu = 512
+ memory = 1024
+ essential = true
+ image = "nginx:latest"
+ portMappings = [
+ {
+ name = "app"
+ containerPort = 80
+ protocol = "tcp"
+ }
+ ]
+ }
+ }
+
+ # Task execution IAM role
+ create_task_exec_iam_role = true
+ task_exec_iam_role_name = "my-task-exec-role"
+
+ # Tasks IAM role
+ create_tasks_iam_role = true
+ tasks_iam_role_name = "my-task-role"
+
+ tags = {
+ Environment = "dev"
+ Project = "example"
+ }
+}
+```
+
+## Examples
+
+- [Complete ECS Task](../../examples/)
+
+
+
+## Requirements
+
+| Name | Version |
+| ------------------------------------------------------------------------ | -------- |
+| [terraform](#requirement_terraform) | >= 1.5.7 |
+| [aws](#requirement_aws) | >= 6.4 |
+
+## Providers
+
+| Name | Version |
+| ------------------------------------------------ | ------- |
+| [aws](#provider_aws) | >= 6.4 |
+
+## Modules
+
+| Name | Source | Version |
+| ----------------------------------------------------------------------------------------------- | ----------------------- | ------- |
+| [container_definition](#module_container_definition) | ../container-definition | n/a |
+
+## Resources
+
+| Name | Type |
+| ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
+| [aws_ecs_task_definition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_task_definition) | 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.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource |
+| [aws_iam_role_policy_attachment.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_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
+| [aws_iam_policy_document.task_exec](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.task_exec_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.tasks](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_iam_policy_document.tasks_assume](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
+| [aws_partition.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source |
+| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source |
+
+## Inputs
+
+See [variables.tf](./variables.tf) for a complete list and description of all configurable inputs.
+
+## Outputs
+
+See [outputs.tf](./outputs.tf) for a complete list and description of all outputs.
+
+
+
+## License
+
+Apache-2.0 Licensed. See [LICENSE](https://github.com/terraform-aws-modules/terraform-aws-ecs/blob/master/LICENSE).
diff --git a/modules/task/main.tf b/modules/task/main.tf
new file mode 100644
index 0000000..f0ba905
--- /dev/null
+++ b/modules/task/main.tf
@@ -0,0 +1,523 @@
+data "aws_region" "current" {
+ region = var.region
+
+ count = var.create ? 1 : 0
+}
+data "aws_partition" "current" {
+ count = var.create ? 1 : 0
+}
+data "aws_caller_identity" "current" {
+ count = var.create ? 1 : 0
+}
+
+locals {
+ account_id = try(data.aws_caller_identity.current[0].account_id, "")
+ partition = try(data.aws_partition.current[0].partition, "")
+ region = try(data.aws_region.current[0].region, "")
+}
+
+################################################################################
+# Container Definition
+################################################################################
+
+module "container_definition" {
+ source = "../container-definition"
+
+ region = var.region
+
+ for_each = { for k, v in var.container_definitions : k => v if local.create_task_definition && v.create }
+
+ enable_execute_command = var.enable_execute_command
+ operating_system_family = var.runtime_platform.operating_system_family
+
+ # Container Definition
+ command = each.value.command
+ cpu = each.value.cpu
+ dependsOn = each.value.dependsOn
+ disableNetworking = each.value.disableNetworking
+ dnsSearchDomains = each.value.dnsSearchDomains
+ dnsServers = each.value.dnsServers
+ dockerLabels = each.value.dockerLabels
+ dockerSecurityOptions = each.value.dockerSecurityOptions
+ entrypoint = each.value.entrypoint
+ environment = each.value.environment
+ environmentFiles = each.value.environmentFiles
+ essential = each.value.essential
+ extraHosts = each.value.extraHosts
+ firelensConfiguration = each.value.firelensConfiguration
+ healthCheck = each.value.healthCheck
+ hostname = each.value.hostname
+ image = each.value.image
+ interactive = each.value.interactive
+ links = each.value.links
+ linuxParameters = each.value.linuxParameters
+ logConfiguration = each.value.logConfiguration
+ memory = each.value.memory
+ memoryReservation = each.value.memoryReservation
+ mountPoints = each.value.mountPoints
+ name = coalesce(each.value.name, each.key)
+ portMappings = each.value.portMappings
+ privileged = each.value.privileged
+ pseudoTerminal = each.value.pseudoTerminal
+ readonlyRootFilesystem = each.value.readonlyRootFilesystem
+ repositoryCredentials = each.value.repositoryCredentials
+ resourceRequirements = each.value.resourceRequirements
+ restartPolicy = each.value.restartPolicy
+ secrets = each.value.secrets
+ startTimeout = each.value.startTimeout
+ stopTimeout = each.value.stopTimeout
+ systemControls = each.value.systemControls
+ ulimits = each.value.ulimits
+ user = each.value.user
+ versionConsistency = each.value.versionConsistency
+ volumesFrom = each.value.volumesFrom
+ workingDirectory = each.value.workingDirectory
+
+ # CloudWatch Log Group
+ service = var.name
+ enable_cloudwatch_logging = each.value.enable_cloudwatch_logging
+ create_cloudwatch_log_group = each.value.create_cloudwatch_log_group
+ cloudwatch_log_group_name = each.value.cloudwatch_log_group_name
+ cloudwatch_log_group_use_name_prefix = each.value.cloudwatch_log_group_use_name_prefix
+ cloudwatch_log_group_class = each.value.cloudwatch_log_group_class
+ cloudwatch_log_group_retention_in_days = each.value.cloudwatch_log_group_retention_in_days
+ cloudwatch_log_group_kms_key_id = each.value.cloudwatch_log_group_kms_key_id
+
+ tags = var.tags
+}
+
+################################################################################
+# Task Definition
+################################################################################
+
+locals {
+ create_task_definition = var.create && var.create_task_definition
+ 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
+ enable_fault_injection = var.enable_fault_injection
+
+ dynamic "ephemeral_storage" {
+ for_each = var.ephemeral_storage != null ? [var.ephemeral_storage] : []
+
+ content {
+ size_in_gib = ephemeral_storage.value.size_in_gib
+ }
+ }
+
+ execution_role_arn = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn)
+ family = coalesce(var.family, var.name)
+
+ 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 != null ? var.task_definition_placement_constraints : {}
+
+ content {
+ expression = placement_constraints.value.expression
+ type = placement_constraints.value.type
+ }
+ }
+
+ dynamic "proxy_configuration" {
+ for_each = var.proxy_configuration != null ? [var.proxy_configuration] : []
+
+ content {
+ container_name = proxy_configuration.value.container_name
+ properties = proxy_configuration.value.properties
+ type = proxy_configuration.value.type
+ }
+ }
+
+ requires_compatibilities = var.requires_compatibilities
+
+ dynamic "runtime_platform" {
+ for_each = var.runtime_platform != null ? [var.runtime_platform] : []
+
+ content {
+ 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 != null ? var.volume : {}
+
+ content {
+ configure_at_launch = volume.value.configure_at_launch
+
+ dynamic "docker_volume_configuration" {
+ for_each = volume.value.docker_volume_configuration != null ? [volume.value.docker_volume_configuration] : []
+
+ content {
+ 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 = volume.value.efs_volume_configuration != null ? [volume.value.efs_volume_configuration] : []
+
+ content {
+ dynamic "authorization_config" {
+ for_each = efs_volume_configuration.value.authorization_config != null ? [efs_volume_configuration.value.authorization_config] : []
+
+ content {
+ 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 = 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 = volume.value.fsx_windows_file_server_volume_configuration != null ? [volume.value.fsx_windows_file_server_volume_configuration] : []
+
+ content {
+ dynamic "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
+ domain = authorization_config.value.domain
+ }
+ }
+
+ file_system_id = fsx_windows_file_server_volume_configuration.value.file_system_id
+ root_directory = fsx_windows_file_server_volume_configuration.value.root_directory
+ }
+ }
+
+ host_path = volume.value.host_path
+ name = coalesce(volume.value.name, volume.key)
+ }
+ }
+
+ tags = merge(var.tags, var.task_tags)
+
+ depends_on = [
+ aws_iam_role_policy_attachment.tasks,
+ aws_iam_role_policy_attachment.task_exec,
+ aws_iam_role_policy_attachment.task_exec_additional,
+ ]
+
+ lifecycle {
+ create_before_destroy = true
+ }
+}
+
+################################################################################
+# Task Execution - IAM Role
+# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html
+################################################################################
+
+locals {
+ task_exec_iam_role_name = 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
+}
+
+data "aws_iam_policy_document" "task_exec_assume" {
+ count = local.create_task_exec_iam_role ? 1 : 0
+
+ statement {
+ sid = "ECSTaskExecutionAssumeRole"
+ actions = ["sts:AssumeRole"]
+
+ principals {
+ type = "Service"
+ identifiers = ["ecs-tasks.amazonaws.com"]
+ }
+ }
+}
+
+resource "aws_iam_role" "task_exec" {
+ count = local.create_task_exec_iam_role ? 1 : 0
+
+ name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name
+ name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null
+ path = var.task_exec_iam_role_path
+ description = coalesce(var.task_exec_iam_role_description, "Task execution role for ${local.task_exec_iam_role_name}")
+
+ assume_role_policy = data.aws_iam_policy_document.task_exec_assume[0].json
+ max_session_duration = var.task_exec_iam_role_max_session_duration
+ permissions_boundary = var.task_exec_iam_role_permissions_boundary
+ force_detach_policies = true
+
+ tags = merge(var.tags, var.task_exec_iam_role_tags)
+}
+
+resource "aws_iam_role_policy_attachment" "task_exec_additional" {
+ for_each = { for k, v in var.task_exec_iam_role_policies : k => v if local.create_task_exec_iam_role }
+
+ role = aws_iam_role.task_exec[0].name
+ policy_arn = each.value
+}
+
+data "aws_iam_policy_document" "task_exec" {
+ count = local.create_task_exec_policy ? 1 : 0
+
+ # Pulled from AmazonECSTaskExecutionRolePolicy
+ statement {
+ sid = "Logs"
+ actions = [
+ "logs:CreateLogStream",
+ "logs:PutLogEvents",
+ ]
+ resources = ["*"]
+ }
+
+ # Pulled from AmazonECSTaskExecutionRolePolicy
+ statement {
+ sid = "ECR"
+ actions = [
+ "ecr:GetAuthorizationToken",
+ "ecr:BatchCheckLayerAvailability",
+ "ecr:GetDownloadUrlForLayer",
+ "ecr:BatchGetImage",
+ ]
+ resources = ["*"]
+ }
+
+ dynamic "statement" {
+ for_each = length(var.task_exec_ssm_param_arns) > 0 ? [1] : []
+
+ content {
+ sid = "GetSSMParams"
+ actions = ["ssm:GetParameters"]
+ resources = var.task_exec_ssm_param_arns
+ }
+ }
+
+ dynamic "statement" {
+ for_each = length(var.task_exec_secret_arns) > 0 ? [1] : []
+
+ content {
+ sid = "GetSecrets"
+ actions = ["secretsmanager:GetSecretValue"]
+ resources = var.task_exec_secret_arns
+ }
+ }
+
+ dynamic "statement" {
+ for_each = var.task_exec_iam_statements != null ? var.task_exec_iam_statements : []
+
+ content {
+ 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 = statement.value.principals != null ? statement.value.principals : []
+
+ content {
+ type = principals.value.type
+ identifiers = principals.value.identifiers
+ }
+ }
+
+ dynamic "not_principals" {
+ for_each = statement.value.not_principals != null ? statement.value.not_principals : []
+
+ content {
+ type = not_principals.value.type
+ identifiers = not_principals.value.identifiers
+ }
+ }
+
+ dynamic "condition" {
+ for_each = statement.value.condition != null ? statement.value.condition : []
+
+ content {
+ test = condition.value.test
+ values = condition.value.values
+ variable = condition.value.variable
+ }
+ }
+ }
+ }
+}
+
+resource "aws_iam_policy" "task_exec" {
+ count = local.create_task_exec_policy ? 1 : 0
+
+ name = var.task_exec_iam_role_use_name_prefix ? null : local.task_exec_iam_role_name
+ name_prefix = var.task_exec_iam_role_use_name_prefix ? "${local.task_exec_iam_role_name}-" : null
+ description = coalesce(var.task_exec_iam_role_description, "Task execution role IAM policy")
+ policy = data.aws_iam_policy_document.task_exec[0].json
+ path = var.task_exec_iam_policy_path
+ tags = merge(var.tags, var.task_exec_iam_role_tags)
+}
+
+resource "aws_iam_role_policy_attachment" "task_exec" {
+ count = local.create_task_exec_policy ? 1 : 0
+
+ role = aws_iam_role.task_exec[0].name
+ policy_arn = aws_iam_policy.task_exec[0].arn
+}
+
+################################################################################
+# Tasks - IAM role
+# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
+################################################################################
+
+locals {
+ tasks_iam_role_name = coalesce(var.tasks_iam_role_name, var.name, "NotProvided")
+ create_tasks_iam_role = local.create_task_definition && var.create_tasks_iam_role
+}
+
+data "aws_iam_policy_document" "tasks_assume" {
+ count = local.create_tasks_iam_role ? 1 : 0
+
+ statement {
+ sid = "ECSTasksAssumeRole"
+ actions = ["sts:AssumeRole"]
+
+ principals {
+ type = "Service"
+ identifiers = ["ecs-tasks.amazonaws.com"]
+ }
+
+ # https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html#create_task_iam_policy_and_role
+ condition {
+ test = "ArnLike"
+ variable = "aws:SourceArn"
+ values = ["arn:${local.partition}:ecs:${local.region}:${local.account_id}:*"]
+ }
+
+ condition {
+ test = "StringEquals"
+ variable = "aws:SourceAccount"
+ values = [local.account_id]
+ }
+ }
+}
+
+resource "aws_iam_role" "tasks" {
+ count = local.create_tasks_iam_role ? 1 : 0
+
+ name = var.tasks_iam_role_use_name_prefix ? null : local.tasks_iam_role_name
+ name_prefix = var.tasks_iam_role_use_name_prefix ? "${local.tasks_iam_role_name}-" : null
+ path = var.tasks_iam_role_path
+ description = var.tasks_iam_role_description
+
+ assume_role_policy = data.aws_iam_policy_document.tasks_assume[0].json
+ permissions_boundary = var.tasks_iam_role_permissions_boundary
+ force_detach_policies = true
+
+ tags = merge(var.tags, var.tasks_iam_role_tags)
+}
+
+data "aws_iam_policy_document" "tasks" {
+ 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] : []
+
+ content {
+ sid = "ECSExec"
+ actions = [
+ "ssmmessages:CreateControlChannel",
+ "ssmmessages:CreateDataChannel",
+ "ssmmessages:OpenControlChannel",
+ "ssmmessages:OpenDataChannel",
+ ]
+ resources = ["*"]
+ }
+ }
+
+ dynamic "statement" {
+ for_each = var.tasks_iam_role_statements != null ? var.tasks_iam_role_statements : []
+
+ content {
+ 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 = statement.value.principals != null ? statement.value.principals : []
+
+ content {
+ type = principals.value.type
+ identifiers = principals.value.identifiers
+ }
+ }
+
+ dynamic "not_principals" {
+ for_each = statement.value.not_principals != null ? statement.value.not_principals : []
+
+ content {
+ type = not_principals.value.type
+ identifiers = not_principals.value.identifiers
+ }
+ }
+
+ dynamic "condition" {
+ for_each = statement.value.condition != null ? statement.value.condition : []
+
+ content {
+ test = condition.value.test
+ values = condition.value.values
+ variable = condition.value.variable
+ }
+ }
+ }
+ }
+}
+
+resource "aws_iam_policy" "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
+ 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
+}
+
+
diff --git a/modules/task/outputs.tf b/modules/task/outputs.tf
new file mode 100644
index 0000000..806f548
--- /dev/null
+++ b/modules/task/outputs.tf
@@ -0,0 +1,67 @@
+################################################################################
+# Container Definition
+################################################################################
+
+output "container_definitions" {
+ description = "Container definitions"
+ value = module.container_definition
+}
+
+################################################################################
+# Task Definition
+################################################################################
+
+output "task_definition_arn" {
+ description = "Full ARN of the Task Definition (including both `family` and `revision`)"
+ value = try(aws_ecs_task_definition.this[0].arn, var.task_definition_arn)
+}
+
+output "task_definition_revision" {
+ description = "Revision of the task in a particular family"
+ value = try(aws_ecs_task_definition.this[0].revision, null)
+}
+
+output "task_definition_family" {
+ description = "The unique name of the task definition"
+ value = try(aws_ecs_task_definition.this[0].family, null)
+}
+
+################################################################################
+# Task Execution - IAM Role
+# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html
+################################################################################
+
+output "task_exec_iam_role_name" {
+ description = "Task execution IAM role name"
+ value = try(aws_iam_role.task_exec[0].name, null)
+}
+
+output "task_exec_iam_role_arn" {
+ description = "Task execution IAM role ARN"
+ value = try(aws_iam_role.task_exec[0].arn, var.task_exec_iam_role_arn)
+}
+
+output "task_exec_iam_role_unique_id" {
+ description = "Stable and unique string identifying the task execution IAM role"
+ value = try(aws_iam_role.task_exec[0].unique_id, null)
+}
+
+################################################################################
+# Tasks - IAM role
+# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
+################################################################################
+
+output "tasks_iam_role_name" {
+ description = "Tasks IAM role name"
+ value = try(aws_iam_role.tasks[0].name, null)
+}
+
+output "tasks_iam_role_arn" {
+ description = "Tasks IAM role ARN"
+ value = try(aws_iam_role.tasks[0].arn, var.tasks_iam_role_arn)
+}
+
+output "tasks_iam_role_unique_id" {
+ description = "Stable and unique string identifying the tasks IAM role"
+ value = try(aws_iam_role.tasks[0].unique_id, null)
+}
diff --git a/modules/task/variables.tf b/modules/task/variables.tf
new file mode 100644
index 0000000..34681f5
--- /dev/null
+++ b/modules/task/variables.tf
@@ -0,0 +1,550 @@
+variable "create" {
+ description = "Determines whether resources will be created (affects all resources)"
+ type = bool
+ default = true
+ nullable = false
+}
+
+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)
+ default = {}
+ nullable = false
+}
+
+variable "name" {
+ description = "Name of the task (up to 255 letters, numbers, hyphens, and underscores)"
+ type = string
+ default = null
+}
+
+variable "enable_execute_command" {
+ description = "Specifies whether to enable Amazon ECS Exec for the tasks"
+ type = bool
+ default = false
+ nullable = false
+}
+
+################################################################################
+# Task Definition
+################################################################################
+
+variable "create_task_definition" {
+ description = "Determines whether to create a task definition or use existing/provided"
+ type = bool
+ default = true
+ nullable = false
+}
+
+variable "task_definition_arn" {
+ description = "Existing task definition ARN. Required when `create_task_definition` is `false`"
+ type = string
+ default = null
+}
+
+variable "container_definitions" {
+ description = "A map of valid [container definitions](http://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_ContainerDefinition.html). Please note that you should only provide values that are part of the container definition document"
+ type = map(object({
+ create = optional(bool, true)
+ 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))
+ 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)
+ 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, 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)
+ 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" {
+ description = "Number of cpu units used by the task. If the `requires_compatibilities` is `FARGATE` this field is required"
+ type = number
+ 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 = object({
+ size_in_gib = number
+ })
+ default = null
+}
+
+variable "family" {
+ description = "A unique name for your task definition"
+ type = string
+ default = null
+}
+
+variable "ipc_mode" {
+ description = "IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`"
+ type = string
+ default = null
+}
+
+variable "memory" {
+ description = "Amount (in MiB) of memory used by the task. If the `requires_compatibilities` is `FARGATE` this field is required"
+ type = number
+ default = 2048
+}
+
+variable "network_mode" {
+ description = "Docker networking mode to use for the containers in the task. Valid values are `none`, `bridge`, `awsvpc`, and `host`"
+ type = string
+ default = "awsvpc"
+ nullable = false
+}
+
+variable "pid_mode" {
+ description = "Process namespace to use for the containers in the task. The valid values are `host` and `task`"
+ type = string
+ default = null
+}
+
+variable "proxy_configuration" {
+ description = "Configuration block for the App Mesh proxy"
+ type = object({
+ container_name = string
+ properties = optional(map(string))
+ type = optional(string)
+ })
+ default = null
+}
+
+variable "requires_compatibilities" {
+ description = "Set of launch types required by the task. The valid values are `EC2` and `FARGATE`"
+ type = list(string)
+ default = ["FARGATE"]
+ nullable = false
+}
+
+variable "runtime_platform" {
+ description = "Configuration block for `runtime_platform` that containers in your task may use"
+ type = object({
+ cpu_architecture = optional(string, "X86_64")
+ operating_system_family = optional(string, "LINUX")
+ })
+ default = {
+ operating_system_family = "LINUX"
+ cpu_architecture = "X86_64"
+ }
+ nullable = false
+}
+
+variable "skip_destroy" {
+ description = "If true, the task is not deleted when the service is deleted"
+ type = bool
+ 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. Useful in the event the task definition is modified outside of this resource"
+ type = bool
+ default = true
+ nullable = false
+}
+
+variable "volume" {
+ description = "Configuration block for volumes that containers in your task may use"
+ 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" {
+ description = "A map of additional tags to add to the task definition/set created"
+ type = map(string)
+ default = {}
+ nullable = false
+}
+
+################################################################################
+# Task Execution - IAM Role
+# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html
+################################################################################
+
+variable "create_task_exec_iam_role" {
+ description = "Determines whether the ECS task definition IAM role should be created"
+ type = bool
+ default = true
+ nullable = false
+}
+
+variable "task_exec_iam_role_arn" {
+ description = "Existing IAM role ARN"
+ type = string
+ default = null
+}
+
+variable "task_exec_iam_role_name" {
+ description = "Name to use on IAM role created"
+ type = string
+ default = null
+}
+
+variable "task_exec_iam_role_use_name_prefix" {
+ description = "Determines whether the IAM role name (`task_exec_iam_role_name`) is used as a prefix"
+ type = bool
+ default = true
+ nullable = false
+}
+
+variable "task_exec_iam_role_path" {
+ description = "IAM role path"
+ type = string
+ default = null
+}
+
+variable "task_exec_iam_role_description" {
+ description = "Description of the role"
+ type = string
+ default = null
+}
+
+variable "task_exec_iam_role_permissions_boundary" {
+ description = "ARN of the policy that is used to set the permissions boundary for the IAM role"
+ type = string
+ default = null
+}
+
+variable "task_exec_iam_role_tags" {
+ description = "A map of additional tags to add to the IAM role created"
+ type = map(string)
+ default = {}
+ nullable = false
+}
+
+variable "task_exec_iam_role_policies" {
+ description = "Map of IAM role policy ARNs to attach to the IAM role"
+ type = map(string)
+ default = {}
+ nullable = false
+}
+
+variable "task_exec_iam_role_max_session_duration" {
+ description = "Maximum session duration (in seconds) for ECS task execution role. Default is 3600."
+ type = number
+ default = null
+}
+
+variable "create_task_exec_policy" {
+ description = "Determines whether the ECS task definition IAM policy should be created. This includes permissions included in AmazonECSTaskExecutionRolePolicy as well as access to secrets and SSM parameters"
+ type = bool
+ default = true
+ nullable = false
+}
+
+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 = []
+ nullable = false
+}
+
+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 = []
+ nullable = false
+}
+
+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 = 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" {
+ description = "Path for the iam role"
+ type = string
+ default = null
+}
+
+################################################################################
+# Tasks - IAM role
+# https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
+################################################################################
+
+variable "create_tasks_iam_role" {
+ description = "Determines whether the ECS tasks IAM role should be created"
+ type = bool
+ default = true
+ nullable = false
+}
+
+variable "tasks_iam_role_arn" {
+ description = "Existing IAM role ARN"
+ type = string
+ default = null
+}
+
+variable "tasks_iam_role_name" {
+ description = "Name to use on IAM role created"
+ type = string
+ default = null
+}
+
+variable "tasks_iam_role_use_name_prefix" {
+ description = "Determines whether the IAM role name (`tasks_iam_role_name`) is used as a prefix"
+ type = bool
+ default = true
+ nullable = false
+}
+
+variable "tasks_iam_role_path" {
+ description = "IAM role path"
+ type = string
+ default = null
+}
+
+variable "tasks_iam_role_description" {
+ description = "Description of the role"
+ type = string
+ default = null
+}
+
+variable "tasks_iam_role_permissions_boundary" {
+ description = "ARN of the policy that is used to set the permissions boundary for the IAM role"
+ type = string
+ default = null
+}
+
+variable "tasks_iam_role_tags" {
+ description = "A map of additional tags to add to the IAM role created"
+ type = map(string)
+ default = {}
+ nullable = false
+}
+
+variable "tasks_iam_role_policies" {
+ description = "Map of additioanl IAM role policy ARNs to attach to the IAM role"
+ type = map(string)
+ default = {}
+ nullable = false
+}
+
+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 = 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
+}
+
+
diff --git a/modules/task/versions.tf b/modules/task/versions.tf
new file mode 100644
index 0000000..497e3e6
--- /dev/null
+++ b/modules/task/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.5.7"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.4"
+ }
+ }
+}
diff --git a/outputs.tf b/outputs.tf
index 6e462c6..e7fc3da 100644
--- a/outputs.tf
+++ b/outputs.tf
@@ -60,3 +60,8 @@ output "services" {
description = "Map of services created and their attributes"
value = module.service
}
+
+output "tasks" {
+ description = "Map of tasks created and their attributes"
+ value = module.task
+}
diff --git a/variables.tf b/variables.tf
index 9aba2f2..7f6d633 100644
--- a/variables.tf
+++ b/variables.tf
@@ -815,3 +815,286 @@ variable "services" {
}))
default = null
}
+
+################################################################################
+# Task(s)
+################################################################################
+
+variable "tasks" {
+ description = "Map of task definitions to create"
+ type = map(object({
+ create = optional(bool)
+ tags = optional(map(string))
+
+ # Task definition
+ name = optional(string) # Will fall back to use map key if not set
+ enable_execute_command = optional(bool)
+ create_task_definition = optional(bool)
+ task_definition_arn = optional(string)
+ container_definitions = optional(map(object({
+ create = optional(bool, true)
+ 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))
+ entrypoint = optional(list(string))
+ environment = optional(list(object({
+ name = string
+ value = string
+ })))
+ environmentFiles = optional(list(object({
+ type = string
+ value = string
+ })))
+ essential = optional(bool)
+ extraHosts = optional(list(object({
+ hostname = string
+ ipAddress = string
+ })))
+ firelensConfiguration = optional(object({
+ options = optional(map(string))
+ type = optional(string)
+ }))
+ healthCheck = optional(object({
+ command = optional(list(string))
+ interval = optional(number)
+ retries = optional(number)
+ startPeriod = optional(number)
+ timeout = optional(number)
+ }))
+ hostname = optional(string)
+ image = optional(string)
+ interactive = optional(bool)
+ links = optional(list(string))
+ linuxParameters = optional(object({
+ capabilities = optional(object({
+ add = optional(list(string))
+ drop = optional(list(string))
+ }))
+ devices = optional(list(object({
+ containerPath = optional(string)
+ hostPath = optional(string)
+ permissions = optional(list(string))
+ })))
+ initProcessEnabled = optional(bool)
+ maxSwap = optional(number)
+ sharedMemorySize = optional(number)
+ swappiness = optional(number)
+ tmpfs = optional(list(object({
+ containerPath = string
+ mountOptions = optional(list(string))
+ size = number
+ })))
+ }))
+ logConfiguration = optional(object({
+ logDriver = optional(string)
+ options = optional(map(string))
+ secretOptions = optional(list(object({
+ name = string
+ valueFrom = string
+ })))
+ }))
+ memory = optional(number)
+ memoryReservation = optional(number)
+ mountPoints = optional(list(object({
+ containerPath = optional(string)
+ readOnly = optional(bool)
+ sourceVolume = optional(string)
+ })))
+ name = optional(string)
+ portMappings = optional(list(object({
+ appProtocol = optional(string)
+ containerPort = optional(number)
+ containerPortRange = optional(string)
+ hostPort = optional(number)
+ name = optional(string)
+ protocol = optional(string)
+ })))
+ privileged = optional(bool)
+ pseudoTerminal = optional(bool)
+ readonlyRootFilesystem = optional(bool)
+ repositoryCredentials = optional(object({
+ credentialsParameter = optional(string)
+ }))
+ resourceRequirements = optional(list(object({
+ type = string
+ value = string
+ })))
+ restartPolicy = optional(object({
+ enabled = optional(bool)
+ ignoredExitCodes = optional(list(number))
+ restartAttemptPeriod = optional(number)
+ }))
+ secrets = optional(list(object({
+ name = string
+ valueFrom = string
+ })))
+ startTimeout = optional(number)
+ stopTimeout = optional(number)
+ systemControls = optional(list(object({
+ namespace = optional(string)
+ value = optional(string)
+ })))
+ ulimits = optional(list(object({
+ hardLimit = number
+ name = string
+ softLimit = number
+ })))
+ user = optional(string)
+ versionConsistency = optional(string)
+ volumesFrom = optional(list(object({
+ readOnly = optional(bool)
+ sourceContainer = optional(string)
+ })))
+ workingDirectory = optional(string)
+
+ # Cloudwatch Log Group
+ service = optional(string)
+ enable_cloudwatch_logging = optional(bool)
+ create_cloudwatch_log_group = optional(bool)
+ cloudwatch_log_group_name = optional(string)
+ cloudwatch_log_group_use_name_prefix = optional(bool)
+ cloudwatch_log_group_class = optional(string)
+ cloudwatch_log_group_retention_in_days = optional(number)
+ cloudwatch_log_group_kms_key_id = optional(string)
+ })))
+ cpu = optional(number, 1024)
+ enable_fault_injection = optional(bool)
+ ephemeral_storage = optional(object({
+ size_in_gib = number
+ }))
+ family = optional(string)
+ ipc_mode = optional(string)
+ memory = optional(number, 2048)
+ network_mode = optional(string)
+ pid_mode = optional(string)
+ proxy_configuration = optional(object({
+ container_name = string
+ properties = optional(map(string))
+ type = optional(string)
+ }))
+ requires_compatibilities = optional(list(string))
+ runtime_platform = optional(object({
+ cpu_architecture = optional(string)
+ operating_system_family = optional(string)
+ }))
+ skip_destroy = optional(bool)
+ task_definition_placement_constraints = optional(map(object({
+ expression = optional(string)
+ type = string
+ })))
+ track_latest = optional(bool)
+ volume = optional(map(object({
+ configure_at_launch = optional(bool)
+ docker_volume_configuration = optional(object({
+ autoprovision = optional(bool)
+ driver = optional(string)
+ driver_opts = optional(map(string))
+ labels = optional(map(string))
+ scope = optional(string)
+ }))
+ efs_volume_configuration = optional(object({
+ authorization_config = optional(object({
+ access_point_id = optional(string)
+ iam = optional(string)
+ }))
+ file_system_id = string
+ root_directory = optional(string)
+ transit_encryption = optional(string)
+ transit_encryption_port = optional(number)
+ }))
+ fsx_windows_file_server_volume_configuration = optional(object({
+ authorization_config = optional(object({
+ credentials_parameter = string
+ domain = string
+ }))
+ file_system_id = string
+ root_directory = string
+ }))
+ host_path = optional(string)
+ name = optional(string)
+ })))
+ task_tags = optional(map(string))
+
+ # Task Execution IAM role
+ create_task_exec_iam_role = optional(bool)
+ task_exec_iam_role_arn = optional(string)
+ task_exec_iam_role_name = optional(string)
+ task_exec_iam_role_use_name_prefix = optional(bool)
+ task_exec_iam_role_path = optional(string)
+ task_exec_iam_role_description = optional(string)
+ task_exec_iam_role_permissions_boundary = optional(string)
+ task_exec_iam_role_tags = optional(map(string))
+ task_exec_iam_role_policies = optional(map(string))
+ task_exec_iam_role_max_session_duration = optional(number)
+ create_task_exec_policy = optional(bool)
+ task_exec_ssm_param_arns = optional(list(string))
+ task_exec_secret_arns = optional(list(string))
+ task_exec_iam_statements = optional(list(object({
+ sid = optional(string)
+ actions = optional(list(string))
+ not_actions = optional(list(string))
+ effect = optional(string)
+ resources = optional(list(string))
+ not_resources = optional(list(string))
+ principals = optional(list(object({
+ type = string
+ identifiers = list(string)
+ })))
+ not_principals = optional(list(object({
+ type = string
+ identifiers = list(string)
+ })))
+ condition = optional(list(object({
+ test = string
+ values = list(string)
+ variable = string
+ })))
+ })))
+ task_exec_iam_policy_path = optional(string)
+
+ # Tasks IAM role
+ create_tasks_iam_role = optional(bool)
+ tasks_iam_role_arn = optional(string)
+ tasks_iam_role_name = optional(string)
+ tasks_iam_role_use_name_prefix = optional(bool)
+ tasks_iam_role_path = optional(string)
+ tasks_iam_role_description = optional(string)
+ tasks_iam_role_permissions_boundary = optional(string)
+ tasks_iam_role_tags = optional(map(string))
+ tasks_iam_role_policies = optional(map(string))
+ tasks_iam_role_statements = optional(list(object({
+ sid = optional(string)
+ actions = optional(list(string))
+ not_actions = optional(list(string))
+ effect = optional(string)
+ resources = optional(list(string))
+ not_resources = optional(list(string))
+ principals = optional(list(object({
+ type = string
+ identifiers = list(string)
+ })))
+ not_principals = optional(list(object({
+ type = string
+ identifiers = list(string)
+ })))
+ condition = optional(list(object({
+ test = string
+ values = list(string)
+ variable = string
+ })))
+ })))
+ }))
+ default = null
+}
diff --git a/wrappers/task/README.md b/wrappers/task/README.md
new file mode 100644
index 0000000..37aba12
--- /dev/null
+++ b/wrappers/task/README.md
@@ -0,0 +1,79 @@
+# Terraform AWS ECS Task Module Wrapper
+
+Configuration in this directory creates ECS task definition resources in various combinations.
+
+This module is a wrapper over the [task](../../modules/task/) module, which allows managing several task resources in one place.
+
+## Usage
+
+```hcl
+module "ecs_task_wrapper" {
+ source = "terraform-aws-modules/ecs/aws//wrappers/task"
+
+ defaults = {
+ create_task_exec_iam_role = true
+ create_tasks_iam_role = true
+ }
+
+ items = {
+ task1 = {
+ name = "my-task-1"
+ container_definitions = {
+ app = {
+ image = "nginx:latest"
+ }
+ }
+ }
+ task2 = {
+ name = "my-task-2"
+ container_definitions = {
+ app = {
+ image = "httpd:latest"
+ }
+ }
+ }
+ }
+}
+```
+
+## Examples
+
+See the [examples](../../examples/) directory for a complete example.
+
+
+
+## Requirements
+
+| Name | Version |
+| ------------------------------------------------------------------------ | -------- |
+| [terraform](#requirement_terraform) | >= 1.5.7 |
+| [aws](#requirement_aws) | >= 6.4 |
+
+## Providers
+
+No providers.
+
+## Modules
+
+| Name | Source | Version |
+| -------------------------------------------------------- | ------------------ | ------- |
+| [wrapper](#module_wrapper) | ../../modules/task | n/a |
+
+## Resources
+
+No resources.
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+| --------------------------------------------------------- | ------------------------------------------------------- | ----- | ------- | :------: |
+| [defaults](#input_defaults) | Map of default values which will be used for each item. | `any` | `{}` | no |
+| [items](#input_items) | Map of objects. Each object represents one item. | `any` | `{}` | no |
+
+## Outputs
+
+| Name | Description |
+| -------------------------------------------------------- | ---------------------------- |
+| [wrapper](#output_wrapper) | Map of outputs of a wrapper. |
+
+
diff --git a/wrappers/task/main.tf b/wrappers/task/main.tf
new file mode 100644
index 0000000..1da9a74
--- /dev/null
+++ b/wrappers/task/main.tf
@@ -0,0 +1,59 @@
+module "wrapper" {
+ source = "../../modules/task"
+
+ for_each = var.items
+
+ create = try(each.value.create, var.defaults.create, true)
+ region = try(each.value.region, var.defaults.region, null)
+ tags = try(each.value.tags, var.defaults.tags, {})
+ name = try(each.value.name, var.defaults.name, null)
+ enable_execute_command = try(each.value.enable_execute_command, var.defaults.enable_execute_command, false)
+ create_task_definition = try(each.value.create_task_definition, var.defaults.create_task_definition, true)
+ task_definition_arn = try(each.value.task_definition_arn, var.defaults.task_definition_arn, null)
+ container_definitions = try(each.value.container_definitions, var.defaults.container_definitions, {})
+ cpu = try(each.value.cpu, var.defaults.cpu, 1024)
+ 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)
+ family = try(each.value.family, var.defaults.family, null)
+ ipc_mode = try(each.value.ipc_mode, var.defaults.ipc_mode, null)
+ memory = try(each.value.memory, var.defaults.memory, 2048)
+ network_mode = try(each.value.network_mode, var.defaults.network_mode, "awsvpc")
+ pid_mode = try(each.value.pid_mode, var.defaults.pid_mode, null)
+ proxy_configuration = try(each.value.proxy_configuration, var.defaults.proxy_configuration, 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" })
+ skip_destroy = try(each.value.skip_destroy, var.defaults.skip_destroy, null)
+ task_definition_placement_constraints = try(each.value.task_definition_placement_constraints, var.defaults.task_definition_placement_constraints, null)
+ track_latest = try(each.value.track_latest, var.defaults.track_latest, true)
+ volume = try(each.value.volume, var.defaults.volume, null)
+ task_tags = try(each.value.task_tags, var.defaults.task_tags, {})
+
+ # Task Execution IAM role
+ create_task_exec_iam_role = try(each.value.create_task_exec_iam_role, var.defaults.create_task_exec_iam_role, true)
+ task_exec_iam_role_arn = try(each.value.task_exec_iam_role_arn, var.defaults.task_exec_iam_role_arn, null)
+ task_exec_iam_role_name = try(each.value.task_exec_iam_role_name, var.defaults.task_exec_iam_role_name, null)
+ task_exec_iam_role_use_name_prefix = try(each.value.task_exec_iam_role_use_name_prefix, var.defaults.task_exec_iam_role_use_name_prefix, true)
+ task_exec_iam_role_path = try(each.value.task_exec_iam_role_path, var.defaults.task_exec_iam_role_path, null)
+ task_exec_iam_role_description = try(each.value.task_exec_iam_role_description, var.defaults.task_exec_iam_role_description, null)
+ task_exec_iam_role_permissions_boundary = try(each.value.task_exec_iam_role_permissions_boundary, var.defaults.task_exec_iam_role_permissions_boundary, null)
+ task_exec_iam_role_tags = try(each.value.task_exec_iam_role_tags, var.defaults.task_exec_iam_role_tags, {})
+ task_exec_iam_role_policies = try(each.value.task_exec_iam_role_policies, var.defaults.task_exec_iam_role_policies, {})
+ task_exec_iam_role_max_session_duration = try(each.value.task_exec_iam_role_max_session_duration, var.defaults.task_exec_iam_role_max_session_duration, null)
+ create_task_exec_policy = try(each.value.create_task_exec_policy, var.defaults.create_task_exec_policy, true)
+ task_exec_ssm_param_arns = try(each.value.task_exec_ssm_param_arns, var.defaults.task_exec_ssm_param_arns, [])
+ task_exec_secret_arns = try(each.value.task_exec_secret_arns, var.defaults.task_exec_secret_arns, [])
+ task_exec_iam_statements = try(each.value.task_exec_iam_statements, var.defaults.task_exec_iam_statements, null)
+ task_exec_iam_policy_path = try(each.value.task_exec_iam_policy_path, var.defaults.task_exec_iam_policy_path, null)
+
+ # Tasks IAM role
+ create_tasks_iam_role = try(each.value.create_tasks_iam_role, var.defaults.create_tasks_iam_role, true)
+ tasks_iam_role_arn = try(each.value.tasks_iam_role_arn, var.defaults.tasks_iam_role_arn, null)
+ tasks_iam_role_name = try(each.value.tasks_iam_role_name, var.defaults.tasks_iam_role_name, null)
+ tasks_iam_role_use_name_prefix = try(each.value.tasks_iam_role_use_name_prefix, var.defaults.tasks_iam_role_use_name_prefix, true)
+ tasks_iam_role_path = try(each.value.tasks_iam_role_path, var.defaults.tasks_iam_role_path, null)
+ tasks_iam_role_description = try(each.value.tasks_iam_role_description, var.defaults.tasks_iam_role_description, null)
+ tasks_iam_role_permissions_boundary = try(each.value.tasks_iam_role_permissions_boundary, var.defaults.tasks_iam_role_permissions_boundary, null)
+ tasks_iam_role_tags = try(each.value.tasks_iam_role_tags, var.defaults.tasks_iam_role_tags, {})
+ tasks_iam_role_policies = try(each.value.tasks_iam_role_policies, var.defaults.tasks_iam_role_policies, {})
+ tasks_iam_role_statements = try(each.value.tasks_iam_role_statements, var.defaults.tasks_iam_role_statements, null)
+}
diff --git a/wrappers/task/outputs.tf b/wrappers/task/outputs.tf
new file mode 100644
index 0000000..d460254
--- /dev/null
+++ b/wrappers/task/outputs.tf
@@ -0,0 +1,4 @@
+output "wrapper" {
+ description = "Map of outputs of a wrapper."
+ value = module.wrapper
+}
diff --git a/wrappers/task/variables.tf b/wrappers/task/variables.tf
new file mode 100644
index 0000000..26153c1
--- /dev/null
+++ b/wrappers/task/variables.tf
@@ -0,0 +1,11 @@
+variable "defaults" {
+ description = "Map of default values which will be used for each item."
+ type = any
+ default = {}
+}
+
+variable "items" {
+ description = "Map of objects. Each object represents one item."
+ type = any
+ default = {}
+}
diff --git a/wrappers/task/versions.tf b/wrappers/task/versions.tf
new file mode 100644
index 0000000..497e3e6
--- /dev/null
+++ b/wrappers/task/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.5.7"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.4"
+ }
+ }
+}