Skip to content

Commit 054ad89

Browse files
authored
fix: Correct variable optional argument defaults and container definition conditional logic (#332)
* fix: Correct variable defaults and container definition conditional logic * chore: Correct comment * chore: Add variable to enable/disable writing container definition to file
1 parent 24ab029 commit 054ad89

File tree

14 files changed

+225
-180
lines changed

14 files changed

+225
-180
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

examples/container-definition/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,13 @@ Note that this example may create resources which will incur monetary charges on
2323
|------|---------|
2424
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.5.7 |
2525
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.4 |
26-
| <a name="requirement_local"></a> [local](#requirement\_local) | >= 2.5 |
26+
| <a name="requirement_null"></a> [null](#requirement\_null) | >= 3.2 |
2727

2828
## Providers
2929

3030
| Name | Version |
3131
|------|---------|
32-
| <a name="provider_local"></a> [local](#provider\_local) | >= 2.5 |
32+
| <a name="provider_null"></a> [null](#provider\_null) | >= 3.2 |
3333

3434
## Modules
3535

@@ -41,11 +41,13 @@ Note that this example may create resources which will incur monetary charges on
4141

4242
| Name | Type |
4343
|------|------|
44-
| [local_file.container_definition_json](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
44+
| [null_resource.container_definition_json](https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource) | resource |
4545

4646
## Inputs
4747

48-
No inputs.
48+
| Name | Description | Type | Default | Required |
49+
|------|-------------|------|---------|:--------:|
50+
| <a name="input_write_container_definition_to_file"></a> [write\_container\_definition\_to\_file](#input\_write\_container\_definition\_to\_file) | Determines whether the container definition JSON should be written to a file. Used for debugging and checking diffs | `bool` | `true` | no |
4951

5052
## Outputs
5153

examples/container-definition/outputs.tf

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@ output "container_definition_json" {
1212
value = module.ecs_container_definition.container_definition_json
1313
}
1414

15-
resource "local_file" "container_definition_json" {
16-
content = module.ecs_container_definition.container_definition_json
17-
filename = "${path.module}/definition.json"
15+
resource "null_resource" "container_definition_json" {
16+
count = var.write_container_definition_to_file ? 1 : 0
17+
18+
triggers = {
19+
container_definition_json = timestamp()
20+
}
21+
22+
provisioner "local-exec" {
23+
# Need the output pretty-printed and sorted for comparison
24+
command = "echo '${module.ecs_container_definition.container_definition_json}' | jq -S > ./definition.json"
25+
}
1826
}
1927

2028
################################################################################
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
variable "write_container_definition_to_file" {
2+
description = "Determines whether the container definition JSON should be written to a file. Used for debugging and checking diffs"
3+
type = bool
4+
default = true
5+
}

examples/container-definition/versions.tf

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ terraform {
66
source = "hashicorp/aws"
77
version = ">= 6.4"
88
}
9-
local = {
10-
source = "hashicorp/local"
11-
version = ">= 2.5"
9+
null = {
10+
source = "hashicorp/null"
11+
version = ">= 3.2"
1212
}
1313
}
1414
}

examples/fargate/main.tf

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,18 @@ module "ecs_service" {
6565
strategy = "BLUE_GREEN"
6666
bake_time_in_minutes = 2
6767

68-
# example config using lifecycle hooks
68+
# # Example config using lifecycle hooks
6969
# lifecycle_hook = {
70-
# success = {
71-
# hook_target_arn = aws_lambda_function.hook_success.arn
72-
# role_arn = aws_iam_role.global.arn
73-
# lifecycle_stages = ["POST_SCALE_UP", "POST_TEST_TRAFFIC_SHIFT"]
74-
# }
75-
# failure = {
76-
# hook_target_arn = aws_lambda_function.hook_failure.arn
77-
# role_arn = aws_iam_role.global.arn
78-
# lifecycle_stages = ["TEST_TRAFFIC_SHIFT", "POST_PRODUCTION_TRAFFIC_SHIFT"]
79-
# }
70+
# success = {
71+
# hook_target_arn = aws_lambda_function.hook_success.arn
72+
# role_arn = aws_iam_role.global.arn
73+
# lifecycle_stages = ["POST_SCALE_UP", "POST_TEST_TRAFFIC_SHIFT"]
74+
# }
75+
# failure = {
76+
# hook_target_arn = aws_lambda_function.hook_failure.arn
77+
# role_arn = aws_iam_role.global.arn
78+
# lifecycle_stages = ["TEST_TRAFFIC_SHIFT", "POST_PRODUCTION_TRAFFIC_SHIFT"]
79+
# }
8080
# }
8181
}
8282

modules/container-definition/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ No modules.
166166
| <a name="input_image"></a> [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 |
167167
| <a name="input_interactive"></a> [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 |
168168
| <a name="input_links"></a> [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 |
169-
| <a name="input_linuxParameters"></a> [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) | <pre>object({<br/> capabilities = optional(object({<br/> add = optional(list(string))<br/> drop = optional(list(string))<br/> }))<br/> devices = optional(list(object({<br/> containerPath = optional(string)<br/> hostPath = optional(string)<br/> permissions = optional(list(string))<br/> })))<br/> initProcessEnabled = optional(bool, false)<br/> maxSwap = optional(number)<br/> sharedMemorySize = optional(number)<br/> swappiness = optional(number)<br/> tmpfs = optional(list(object({<br/> containerPath = string<br/> mountOptions = optional(list(string))<br/> size = number<br/> })))<br/> })</pre> | <pre>{<br/> "initProcessEnabled": false<br/>}</pre> | no |
169+
| <a name="input_linuxParameters"></a> [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) | <pre>object({<br/> capabilities = optional(object({<br/> add = optional(list(string))<br/> drop = optional(list(string))<br/> }))<br/> devices = optional(list(object({<br/> containerPath = optional(string)<br/> hostPath = optional(string)<br/> permissions = optional(list(string))<br/> })))<br/> initProcessEnabled = optional(bool)<br/> maxSwap = optional(number)<br/> sharedMemorySize = optional(number)<br/> swappiness = optional(number)<br/> tmpfs = optional(list(object({<br/> containerPath = string<br/> mountOptions = optional(list(string))<br/> size = number<br/> })))<br/> })</pre> | `{}` | no |
170170
| <a name="input_logConfiguration"></a> [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) | <pre>object({<br/> logDriver = optional(string)<br/> options = optional(map(string))<br/> secretOptions = optional(list(object({<br/> name = string<br/> valueFrom = string<br/> })))<br/> })</pre> | `{}` | no |
171171
| <a name="input_memory"></a> [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 |
172172
| <a name="input_memoryReservation"></a> [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 |
@@ -180,7 +180,7 @@ No modules.
180180
| <a name="input_region"></a> [region](#input\_region) | Region where the resource(s) will be managed. Defaults to the Region set in the provider configuration | `string` | `null` | no |
181181
| <a name="input_repositoryCredentials"></a> [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 | <pre>object({<br/> credentialsParameter = optional(string)<br/> })</pre> | `null` | no |
182182
| <a name="input_resourceRequirements"></a> [resourceRequirements](#input\_resourceRequirements) | The type and amount of a resource to assign to a container. The only supported resource is a GPU | <pre>list(object({<br/> type = string<br/> value = string<br/> }))</pre> | `null` | no |
183-
| <a name="input_restartPolicy"></a> [restartPolicy](#input\_restartPolicy) | Container restart policy; helps overcome transient failures faster and maintain task availability | <pre>object({<br/> enabled = optional(bool, true)<br/> ignoredExitCodes = optional(list(number), [])<br/> restartAttemptPeriod = optional(number)<br/> })</pre> | <pre>{<br/> "enabled": true<br/>}</pre> | no |
183+
| <a name="input_restartPolicy"></a> [restartPolicy](#input\_restartPolicy) | Container restart policy; helps overcome transient failures faster and maintain task availability | <pre>object({<br/> enabled = optional(bool)<br/> ignoredExitCodes = optional(list(number))<br/> restartAttemptPeriod = optional(number)<br/> })</pre> | <pre>{<br/> "enabled": true<br/>}</pre> | no |
184184
| <a name="input_secrets"></a> [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 | <pre>list(object({<br/> name = string<br/> valueFrom = string<br/> }))</pre> | `null` | no |
185185
| <a name="input_service"></a> [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 |
186186
| <a name="input_startTimeout"></a> [startTimeout](#input\_startTimeout) | Time duration (in seconds) to wait before giving up on resolving dependencies for a container | `number` | `30` | no |
@@ -200,7 +200,7 @@ No modules.
200200
| <a name="output_cloudwatch_log_group_arn"></a> [cloudwatch\_log\_group\_arn](#output\_cloudwatch\_log\_group\_arn) | ARN of CloudWatch log group created |
201201
| <a name="output_cloudwatch_log_group_name"></a> [cloudwatch\_log\_group\_name](#output\_cloudwatch\_log\_group\_name) | Name of CloudWatch log group created |
202202
| <a name="output_container_definition"></a> [container\_definition](#output\_container\_definition) | Container definition |
203-
| <a name="output_container_definition_json"></a> [container\_definition\_json](#output\_container\_definition\_json) | Container definition |
203+
| <a name="output_container_definition_json"></a> [container\_definition\_json](#output\_container\_definition\_json) | Container definition. NOTE: use `jsonencode([module.ecs_container_definition.container_definition])` instead of this output when passing into a Task Definition |
204204
<!-- END_TF_DOCS -->
205205

206206
## License

modules/container-definition/main.tf

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ data "aws_region" "current" {
22
region = var.region
33
}
44

5-
65
locals {
76
is_not_windows = contains(["LINUX"], var.operating_system_family)
87

@@ -23,8 +22,16 @@ locals {
2322
{ for k, v in var.logConfiguration : k => v if v != null }
2423
)
2524

25+
# 1. We remove any attributes that are set to `null` by default from the variable optional attributes
26+
# tflint-ignore: terraform_naming_convention
27+
trimedLinuxParameters = { for k, v in var.linuxParameters : k => v if v != null }
28+
# 2. We then merge in the `initProcessEnabled` attribute based on whether `enable_execute_command` is true or false
29+
# This also means we will always have something in `linuxParameters` (it will never be `null` or `{}`)
30+
# Terraform doesn't allow us to set `initProcessEnabled` to `true` on one side only of the conditional, so we have to merge it in on both sides
31+
# However, in the `true` case, we set it last to ensure `initProcessEnabled` is always `true` when `enable_execute_command` is true
32+
# and the "psuedo-default" is `false` when `enable_execute_command` is false (but can still be overridden by the user)
2633
# tflint-ignore: terraform_naming_convention
27-
linuxParameters = var.enable_execute_command ? merge({ "initProcessEnabled" : true }, var.linuxParameters) : merge({ "initProcessEnabled" : false }, var.linuxParameters)
34+
linuxParameters = var.enable_execute_command ? merge(local.trimedLinuxParameters, { "initProcessEnabled" : true }) : merge({ "initProcessEnabled" : false }, local.trimedLinuxParameters)
2835

2936
definition = {
3037
command = var.command
@@ -46,7 +53,7 @@ locals {
4653
image = var.image
4754
interactive = var.interactive
4855
links = local.is_not_windows ? var.links : null
49-
linuxParameters = local.is_not_windows ? { for k, v in local.linuxParameters : k => v if v != null } : null
56+
linuxParameters = local.is_not_windows ? local.linuxParameters : null
5057
logConfiguration = length(local.logConfiguration) > 0 ? local.logConfiguration : null
5158
memory = var.memory
5259
memoryReservation = var.memoryReservation

modules/container-definition/outputs.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ output "container_definition" {
77
value = local.container_definition
88
}
99

10+
# ToDo - remove at next breaking change. Not worth it
1011
output "container_definition_json" {
11-
description = "Container definition"
12+
description = "Container definition. NOTE: use `jsonencode([module.ecs_container_definition.container_definition])` instead of this output when passing into a Task Definition"
1213
value = jsonencode(local.container_definition)
1314
}
1415

modules/container-definition/variables.tf

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ variable "operating_system_family" {
88
description = "The OS family for task"
99
type = string
1010
default = "LINUX"
11+
nullable = false
1112
}
1213

1314
variable "tags" {
1415
description = "A map of tags to add to all resources"
1516
type = map(string)
1617
default = {}
18+
nullable = false
1719
}
1820

1921
################################################################################
@@ -81,12 +83,14 @@ variable "enable_execute_command" {
8183
description = "Specifies whether to enable Amazon ECS Exec for the tasks within the service"
8284
type = bool
8385
default = false
86+
nullable = false
8487
}
8588

8689
variable "entrypoint" {
8790
description = "The entry point that is passed to the container"
8891
type = list(string)
8992
default = []
93+
nullable = false
9094
}
9195

9296
variable "environment" {
@@ -95,7 +99,8 @@ variable "environment" {
9599
name = string
96100
value = string
97101
}))
98-
default = []
102+
default = []
103+
nullable = false
99104
}
100105

101106
# tflint-ignore: terraform_naming_convention
@@ -105,7 +110,8 @@ variable "environmentFiles" {
105110
value = string
106111
type = string
107112
}))
108-
default = []
113+
default = []
114+
nullable = false
109115
}
110116

111117
variable "essential" {
@@ -163,6 +169,7 @@ variable "interactive" {
163169
description = "When this parameter is `true`, you can deploy containerized applications that require `stdin` or a `tty` to be allocated"
164170
type = bool
165171
default = false
172+
nullable = false
166173
}
167174

168175
variable "links" {
@@ -184,7 +191,7 @@ variable "linuxParameters" {
184191
hostPath = optional(string)
185192
permissions = optional(list(string))
186193
})))
187-
initProcessEnabled = optional(bool, false)
194+
initProcessEnabled = optional(bool)
188195
maxSwap = optional(number)
189196
sharedMemorySize = optional(number)
190197
swappiness = optional(number)
@@ -194,9 +201,8 @@ variable "linuxParameters" {
194201
size = number
195202
})))
196203
})
197-
default = {
198-
initProcessEnabled = false
199-
}
204+
default = {}
205+
nullable = false
200206
}
201207

202208
# tflint-ignore: terraform_naming_convention
@@ -210,7 +216,8 @@ variable "logConfiguration" {
210216
valueFrom = string
211217
})))
212218
})
213-
default = {}
219+
default = {}
220+
nullable = false
214221
}
215222

216223
variable "memory" {
@@ -234,7 +241,8 @@ variable "mountPoints" {
234241
readOnly = optional(bool)
235242
sourceVolume = optional(string)
236243
}))
237-
default = []
244+
default = []
245+
nullable = false
238246
}
239247

240248
variable "name" {
@@ -261,20 +269,23 @@ variable "privileged" {
261269
description = "When this parameter is true, the container is given elevated privileges on the host container instance (similar to the root user)"
262270
type = bool
263271
default = false
272+
nullable = false
264273
}
265274

266275
# tflint-ignore: terraform_naming_convention
267276
variable "pseudoTerminal" {
268277
description = "When this parameter is true, a `TTY` is allocated"
269278
type = bool
270279
default = false
280+
nullable = false
271281
}
272282

273283
# tflint-ignore: terraform_naming_convention
274284
variable "readonlyRootFilesystem" {
275285
description = "When this parameter is true, the container is given read-only access to its root file system"
276286
type = bool
277287
default = true
288+
nullable = false
278289
}
279290

280291
# tflint-ignore: terraform_naming_convention
@@ -300,8 +311,8 @@ variable "resourceRequirements" {
300311
variable "restartPolicy" {
301312
description = "Container restart policy; helps overcome transient failures faster and maintain task availability"
302313
type = object({
303-
enabled = optional(bool, true)
304-
ignoredExitCodes = optional(list(number), [])
314+
enabled = optional(bool)
315+
ignoredExitCodes = optional(list(number))
305316
restartAttemptPeriod = optional(number)
306317
})
307318
default = {
@@ -339,7 +350,8 @@ variable "systemControls" {
339350
namespace = optional(string)
340351
value = optional(string)
341352
}))
342-
default = []
353+
default = []
354+
nullable = false
343355
}
344356

345357
variable "ulimits" {
@@ -363,6 +375,7 @@ variable "versionConsistency" {
363375
description = "Specifies whether Amazon ECS will resolve the container image tag provided in the container definition to an image digest"
364376
type = string
365377
default = "disabled"
378+
nullable = false
366379
}
367380

368381
# tflint-ignore: terraform_naming_convention
@@ -372,7 +385,8 @@ variable "volumesFrom" {
372385
readOnly = optional(bool)
373386
sourceContainer = optional(string)
374387
}))
375-
default = []
388+
default = []
389+
nullable = false
376390
}
377391

378392
# tflint-ignore: terraform_naming_convention
@@ -396,12 +410,14 @@ variable "enable_cloudwatch_logging" {
396410
description = "Determines whether CloudWatch logging is configured for this container definition. Set to `false` to use other logging drivers"
397411
type = bool
398412
default = true
413+
nullable = false
399414
}
400415

401416
variable "create_cloudwatch_log_group" {
402417
description = "Determines whether a log group is created by this module. If not, AWS will automatically create one if logging is enabled"
403418
type = bool
404419
default = true
420+
nullable = false
405421
}
406422

407423
variable "cloudwatch_log_group_name" {
@@ -414,6 +430,7 @@ variable "cloudwatch_log_group_use_name_prefix" {
414430
description = "Determines whether the log group name should be used as a prefix"
415431
type = bool
416432
default = false
433+
nullable = false
417434
}
418435

419436
variable "cloudwatch_log_group_class" {
@@ -426,6 +443,7 @@ variable "cloudwatch_log_group_retention_in_days" {
426443
description = "Number of days to retain log events. Set to `0` to keep logs indefinitely"
427444
type = number
428445
default = 14
446+
nullable = false
429447
}
430448

431449
variable "cloudwatch_log_group_kms_key_id" {

0 commit comments

Comments
 (0)