For-Loop fails if just one object is given

Hi :wave:t4:

I’m currently trying the following:

  1. Requesting allocated subnets for a network using the HTTP data source (REST call)
  2. Using jsondecode to convert the response body
  3. Convert the response to a list to use it in the resource

This is my code:

locals {
 assigned_subnets_list = jsondecode(data.http.assigned_subnets.body) == null ? [] : [ for subnet in jsondecode(data.http.assigned_subnets.body) : subnet.CIDR ]
}

data "http" "assigned_subnets" {
  url = "http://127.0.0.1/api/v1/subnets/?WorkloadName=FOOBAR&WorkloadEnvironment=PROD&select=cidr"
}

Depending on the API response this works or not:

Working response (array/multiple subnets):

[
  {
    "CIDR": "172.28.0.0/27"
  },
  {
    "CIDR": "172.28.0.32/27"
  }
]

Not working if just one CIDR is returned:

{
  "CIDR": "172.30.1.128/27"
}

This is the error I’m getting if the API returns 1 object:

Error: Unsupported attribute

  on main.tf line 71, in locals:
  71:    assigned_subnets_list = jsondecode(data.http.assigned_subnets.body) == null ? [] : [ for subnet in jsondecode(data.http.assigned_subnets.body) : subnet.CIDR ]

This value does not have any attributes.

Any ideas how I can fix this?

Thanks in advance!

So I’ve actually got it working with the new try function:

assigned_subnets = jsondecode(data.http.assigned_subnets[0].body) == null ? [] : try(
    [for subnet in jsondecode(data.http.assigned_subnets[0].body) : subnet.CIDR],
    [(jsondecode(data.http.assigned_subnets[0].body))["CIDR"]]
)

Hi @tiwood!

The try function was what I was going to suggest when I initially read your question too, so great!

With that said, I’d recommend doing the normalization step separately from the rest because the expressions you’ve written here are pretty complicated and have lots of possible failure conditions: invalid JSON syntax, mis-shapen object in the JSON, etc. If any one of those fails in the first expression then you’d get no feedback at all about it.

If we instead do the normalization alone with try then we can see proper error messages if any of the other operations fail unexpectedly:

data "http" "assigned_subnets" {
  url = "http://127.0.0.1/api/v1/subnets/?WorkloadName=FOOBAR&WorkloadEnvironment=PROD&select=cidr"
}

locals {
  assigned_subnets_raw = jsondecode(jsondecode(data.http.assigned_subnets.body))
  assigned_subnets = try(
    # If it's already list-compatible then just use it
    tolist(local.assigned_subnets_raw),
    # ...otherwise, wrap it in a single-element list.
    [local.assigned_subnets_raw],
  )
  assigned_subnets_cidr = local.assigned_subnets[*].CIDR
}

With it separated out like the above, the try will only handle the case where it’s not a list, letting JSON decoding failures and the absense of the CIDR property both still appear as normal errors, so you’ll get clearer feedback if this API endpoint returns something unexpected.