Terraform-provider-aws: Cannot parametrize the portMappings for a Task Definition

Created on 8 Feb 2018  ยท  5Comments  ยท  Source: hashicorp/terraform-provider-aws

Terraform Version

v0.11.2

Affected Resource(s)

  • aws_ecs_task_definition

Terraform Configuration Files

The task definition resource as written in my module

resource "aws_ecs_task_definition" "main" {
  family = "${var.name}-${var.environment}"
  task_role_arn = "${var.task_role_arn}"

  lifecycle {
    create_before_destroy = true
  }

  container_definitions = <<EOF
[
  {
    "cpu": ${var.cpu},
    "environment": ${jsonencode(var.env_variables)},
    "essential": true,
    "image": "${var.ecr_repository_url}:${var.image_version}",
    "memory": ${var.memory},
    "memoryReservation": ${var.memory_reservation},
    "name": "${var.name}-${var.environment}",
    "portMappings": ${jsonencode(var.container_port_mappings)},
    "entryPoint": null,
    "command": null,
    "mountPoints": [],
    "logConfiguration": null 
  }
]
EOF

  network_mode = "bridge"
}

The relevant lines of the configuration where the module is called

module "queue_service" {
  source = "../service-simple"
  [...]
  container_port_mappings = [
    {
      hostPort = 12345
      containerPort = 12345
      protocol  = "tcp"
    },
    {
      hostPort = 123456
      containerPort = 123456
      protocol = "tcp"
    }
  ]
}

Expected Behavior

Should be able to pass the container port mappings as a parameter to the aws_ecs_task_definition

Actual Behavior

Unable to do so due to Terraform converting integers to strings when they are passed as a parameter to
a module. The error thrown is the following:

ECS Task Definition container_definitions is invalid: Error decoding JSON: json: cannot unmarshal string into Go struct field PortMapping.ContainerPort of type int64
bug servicecs terraform-0.12

Most helpful comment

Hi folks ๐Ÿ‘‹ This issue is resolved in Terraform 0.12, which supports rich data types as well as a fully featured jsonencode() function.

For example, given this configuration:

terraform {
  required_providers {
    aws = "2.20.0"
  }
  required_version = "0.12.5"
}

provider "aws" {
  region = "us-east-1"
}

variable "container_port_mappings" {
  type = list(object({
    hostPort      = number
    containerPort = number
    protocol      = string
  }))
  default = [{
    hostPort      = 12345
    containerPort = 12345
    protocol      = "tcp"
  }]
}

resource "aws_ecs_task_definition" "test" {
  family = "test"

  container_definitions = jsonencode([
    {
      cpu          = 10
      essential    = true
      image        = "busybox"
      memory       = 128
      name         = "test"
      portMappings = var.container_port_mappings
    }
  ])
}

Produces the following apply output:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_ecs_task_definition.test will be created
  + resource "aws_ecs_task_definition" "test" {
      + arn                   = (known after apply)
      + container_definitions = jsonencode(
            [
              + {
                  + cpu          = 10
                  + essential    = true
                  + image        = "busybox"
                  + memory       = 128
                  + name         = "test"
                  + portMappings = [
                      + {
                          + containerPort = 12345
                          + hostPort      = 12345
                          + protocol      = "tcp"
                        },
                    ]
                },
            ]
        )
      + family                = "test"
      + id                    = (known after apply)
      + network_mode          = (known after apply)
      + revision              = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_ecs_task_definition.test: Creating...
aws_ecs_task_definition.test: Creation complete after 1s [id=test]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Enjoy! ๐Ÿš€

All 5 comments

This looks like a Terraform bug to me. According to their API, jsonencode has the following contract:

Returns a JSON-encoded representation of the given value, which can contain arbitrarily-nested lists and maps. Note that if the value is a string then its value will be placed in quotes.

However, when using numbers it still will place them in quotes. Example:

variable "data" {
  default = [
    {
      value1 = 1
      value2 = "2"
      value3 = 3
    },
    {
      value1 = 1
      value2 = "2"
      value3 = 3
    },
  ]
}

output "test" {
  value = "${jsonencode(var.data)}"
}

Running terraform apply will lead to the following output:

test = [{"value1":"1","value2":"2","value3":"3"},{"value1":"1","value2":"2","value3":"3"}]

Which does not seem to be correct. Can you please check that this issue has been reported to the official Terraform project. If you can not find anything related, please create a new ticket.

EDIT

Found it: https://github.com/hashicorp/terraform/issues/17033

Thanks for finding the issue - looks like this is already being tracked in the correct place so you can close this issue if you feel it's unnecessary in this repository as it's related to Terraform itself rather than the aws provider :+1:

A work around that I have been using:

portMappings      = "${replace(jsonencode(var.VARIABLE_NAME), "/\"([0-9]+\\.?[0-9]*)\"/", "$1")}"

If you want to apply this to ulimits, you might want to use a slightly different RegEx since ulimit might be negative:

 ulimits           = "${replace(jsonencode(var.VARIABLE_NAME), "/\"(-?[0-9]+\\.?[0-9]*)\"/", "$1")}"

In your @cdimitroulas original issue, you can use the same workaround:

resource "aws_ecs_task_definition" "main" {
  family = "${var.name}-${var.environment}"
  task_role_arn = "${var.task_role_arn}"

  lifecycle {
    create_before_destroy = true
  }

  container_definitions = <<EOF
[
  {
    "cpu": ${var.cpu},
    "environment": ${jsonencode(var.env_variables)},
    "essential": true,
    "image": "${var.ecr_repository_url}:${var.image_version}",
    "memory": ${var.memory},
    "memoryReservation": ${var.memory_reservation},
    "name": "${var.name}-${var.environment}",
    "portMappings": ${replace(jsonencode(var.container_port_mappings), "/\"([0-9]+\\.?[0-9]*)\"/", "$1")},
    "entryPoint": null,
    "command": null,
    "mountPoints": [],
    "logConfiguration": null 
  }
]
EOF

  network_mode = "bridge"
}

This issue seems to apply to other data type such as boolean (like in mountPointsr.readOnly) as well

P/S: I know the RegEx for potential . and subsequent part (\\.?[0-9]* is intended for some floating point value and is not necessary at all, but I applied it all the way just for consistency among my code

Hi folks ๐Ÿ‘‹ This issue is resolved in Terraform 0.12, which supports rich data types as well as a fully featured jsonencode() function.

For example, given this configuration:

terraform {
  required_providers {
    aws = "2.20.0"
  }
  required_version = "0.12.5"
}

provider "aws" {
  region = "us-east-1"
}

variable "container_port_mappings" {
  type = list(object({
    hostPort      = number
    containerPort = number
    protocol      = string
  }))
  default = [{
    hostPort      = 12345
    containerPort = 12345
    protocol      = "tcp"
  }]
}

resource "aws_ecs_task_definition" "test" {
  family = "test"

  container_definitions = jsonencode([
    {
      cpu          = 10
      essential    = true
      image        = "busybox"
      memory       = 128
      name         = "test"
      portMappings = var.container_port_mappings
    }
  ])
}

Produces the following apply output:

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_ecs_task_definition.test will be created
  + resource "aws_ecs_task_definition" "test" {
      + arn                   = (known after apply)
      + container_definitions = jsonencode(
            [
              + {
                  + cpu          = 10
                  + essential    = true
                  + image        = "busybox"
                  + memory       = 128
                  + name         = "test"
                  + portMappings = [
                      + {
                          + containerPort = 12345
                          + hostPort      = 12345
                          + protocol      = "tcp"
                        },
                    ]
                },
            ]
        )
      + family                = "test"
      + id                    = (known after apply)
      + network_mode          = (known after apply)
      + revision              = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_ecs_task_definition.test: Creating...
aws_ecs_task_definition.test: Creation complete after 1s [id=test]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Enjoy! ๐Ÿš€

I'm going to lock this issue because it has been closed for _30 days_ โณ. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thanks!

Was this page helpful?
0 / 5 - 0 ratings