0

Given this structure in a variable in Terraform:

https_listener_rules = [

{
  https_listener_index = 0
  priority             = 1

  actions = [{
    type               = "forward"
    target_group_index = 1
  }]

  conditions = [{
    host_headers = ["foo.example.com"]
  }]
},
{
  https_listener_index = 0
  priority             = 2

  actions = [{
    type               = "forward"
    target_group_index = 0
  }]

  conditions = [{
    host_headers = ["bar.example.com"]
  }]
}
]

I would like to update each of the listener rules' conditions list with an additional map.

This is where my current effort sits. I'm not sure how to get it to return the whole value of the original var.https_listener_rules to local.https_listener_rules with the new condition inserted.

https_listener_rules = var.create_secret_cloudfront_header ? flatten([for r in var.https_listener_rules : [concat(r.conditions, [{
http_headers = [
  {
    http_header_name = "X-Origin-Secret"
    values           = [random_string.origin_secret[0].result]
  }
]
}])]]) : var.https_listener_rules

Right now it just returns a list with 4 maps (the conditions only) rather than the list of two listeners each with two conditions.

18:   https_listener_rules = var.create_secret_cloudfront_header ? flatten([for r in var.https_listener_rules : [concat(r.conditions, [{
│   19:     http_headers = [
│   20:       {
│   21:         http_header_name = "X-Origin-Secret"
│   22:         values           = [random_string.origin_secret[0].result]
│   23:       }
│   24:     ]
│   25:   }])]]) : var.https_listener_rules
│     ├────────────────
│     │ random_string.origin_secret[0].result is "[snip]"
│     │ var.create_secret_cloudfront_header is true
│     │ var.https_listener_rules is tuple with 2 elements
│ 
│ The true and false result expressions must have consistent types. The 'true' tuple has length 4, but the 'false' tuple has length 2.

EDIT:

Expected result:

https_listener_rules = [

{
  https_listener_index = 0
  priority             = 1

  actions = [{
    type               = "forward"
    target_group_index = 1
  }]

  conditions = [{
    host_headers = ["foo.example.com"]
  },{
    http_headers = [
        {
            http_header_name = "X-Origin-Secret"
            values = ["snip"]
        }
    ]
 }]
},
{
  https_listener_index = 0
  priority             = 2

  actions = [{
    type               = "forward"
    target_group_index = 0
  }]

  conditions = [{
    host_headers = ["bar.example.com"]
  },{
    http_headers = [
        {
            http_header_name = "X-Origin-Secret"
            values = ["snip"]
        }
    ]
 }]
}
]

Actual result:

[
  {
    "host_headers" = [
      "foo.example.com",
    ]
  },
  {
    "http_headers" = [
      {
        "http_header_name" = "X-Origin-Secret"
        "values" = [
          "snip",
        ]
      },
    ]
  },
  {
    "host_headers" = [
      "bar.example.com",
    ]
  },
  {
    "http_headers" = [
      {
        "http_header_name" = "X-Origin-Secret"
        "values" = [
          "snip",
        ]
      },
    ]
  },
]
Fo.
  • 3,752
  • 7
  • 28
  • 44
  • The input and current effort are helpful, and normally an error message is helpful, but in this situation the actual versus expected result would be more helpful than the error message. Could the question please be updated with that info? – Matthew Schuchard Apr 26 '23 at 21:00
  • Thank you for your response. I have added this information – Fo. Apr 26 '23 at 21:33

2 Answers2

1

in this scenario you could use the merge function, for example

Define the new condition in locals

locals {
  new_condition = {
    http_headers = [
      {
        http_header_name = "X-Origin-Secret"
        values           = ["snip"]
      }
    ]
  }
}

Then iterate the variable https_listener_rules and assigns the final result to the local https_listener_rules that merges the current condition with the new_condition

https_listener_rules = [
    for rule in var.https_listener_rules : merge(rule, {
      conditions = concat(rule.conditions, [local.new_condition])
    })
  ]
Luis Herrera
  • 1,092
  • 1
  • 7
  • 11
0

In simple cases Terraform can automatically infer when a tuple value is really intended to be a list based on context, but these expressions are sufficiently complicated that Terraform wasn't able to infer your intentions automatically.

A tuple type has a specific number of elements that may each have different types, while a list type can have an arbitrary number of elements that must all be of the same type. Your conditional expression failed here because you have provided different tuple types (with different numbers of elements) for the two results of your conditional expression, but I assume you actually intended to choose between two values of the same list type that differ in their number of elements.

When Terraform's automatic type deduction doesn't guess correctly, you can force a different interpretation by including explicit type conversion functions. In this case you want lists of objects, so you can use tolist to convert your expression results into lists:

  https_listener_rules = (
    var.create_secret_cloudfront_header ?
    tolist(flatten([
      for r in var.https_listener_rules : [
        concat(r.conditions, [{
          http_headers = [
            {
              http_header_name = "X-Origin-Secret"
              values           = [random_string.origin_secret[0].result]
            }
          ]
        }])
      ]
    ])) :
    tolist(var.https_listener_rules)
  )

(I reformatted this because I found it very hard to follow the syntax tree in your example, but the only meaningful change here is that I added tolist calls around both of the arms of the conditional expression.)

Because this expression is a rather complex mix of object types and collection types, I suspect that the above example will also fail with a new error one level deeper.

You will need to ensure that all of the objects in these lists are also of the same type, which means that they must all have the same attributes and those attributes must have consistent types across all of the objects. Therefore you may need to add additional tolist calls to nested tuples in your data structure, and you may need to add in some missing attributes to make the object types all match.

If there are attributes that don't make sense for a particular element in the list, you can make the object types still match by assigning null to the attribute, which represents the absence of a value for that attribute. You can also help Terraform infer the types you intend by applying the type conversion functions to the nulls, like tostring(null), although once you have everything else matching Terraform should typically be able to automatically deduce a type for an attribute whose value is null by comparing it to the non-null values of the same attribute in other elements of the list.

Martin Atkins
  • 62,420
  • 8
  • 120
  • 138
  • Thank you for your response. It does fail with the following: Invalid value for "v" parameter: cannot convert tuple to list of any single type. – Fo. Apr 27 '23 at 01:02
  • In that case I think it would be helpful to start a new question where you can share your updated configuration and the full version of that error message and then we can try to deal with the next level of error. – Martin Atkins Apr 28 '23 at 01:19