0

I have a map that looks like this:

stuff = {
  object-1 = {
    id                    = "1"
    main_value            = "blue"
    additional_in_values  = ["red"]
    additional_out_values = ["green"]
    more_values           = "foo"
  },
  object-2 = {
    id                    = "2"
    main_value            = "green"
    additional_in_values  = ["blue"]
    additional_out_values = ["red"]
    more_values           = "fee"
  },
  object-3 = {
    id                    = "3"
    main_value            = "red"
    additional_in_values  = ["green"]
    additional_out_values = ["blue"]
    more_values           = "fie"
  }
}

The essence of the challenge is to be able to freely iterate over an object in a map and selectively add, remove and modify attributes of the object without having to explicitly enumerate them.

The specific use case is to augment each object by either inserting the "main_value" into "additional_in_values" and "additional_out_values", or much better, combining the main_value with "additional_in_values" and "additional_out_values" to form two new pairs called "inbound_all" and "outbound_all". There are additional use cases that involve inserting new key-value pairs in the object or dropping named key-value pairs (for example additional_out could be dropped if we had "outbound_all").

A successful result would look like:

transformed-stuff = {
  object-1 = {
    id                    = "1"
    main_value            = "blue"
    additional_in_values  = ["red"]
    additional_out_values = ["green"]
    inbound_all           = ["blue", "red"]   # concat main_value and additional_in_values
    outbound_all          = ["blue", "green"] # concat main_value and additional_out_values
    more_values           = "foo"
  },
  object-2 = {
    id                    = "2"
    main_value            = "green"
    additional_in_values  = ["blue"]
    additional_out_values = ["red"]
    inbound_all           = ["green", "blue"]
    outbound_all          = ["green", "red"]
    more_values           = "fee"
  },
  object-3 = {
    id                    = "3"
    main_value            = "red"
    additional_in_values  = ["green"]
    additional_out_values = ["blue"]
    inbound_all           = ["red", "green"]
    outbound_all          = ["red", "blue"]
    more_values           = "fie"
  }
}

The key constraint is to do these insert or concatenate operations in a way that preserves the existing object attributes without explicitly naming each attribute. Naming each attribute would work fine but would be unmaintainable as each of the objects actually has many more attributes than shown, and the attributes come and go, so a solution that explicitly enumerates won't do, per the "unmaintainable" transform in the sample code below.

It seems like it should be possible in the loop to implement logic like:

(pseudocode)
maintainable = for stuff_key, stuff_details in var.stuff :
 stuff_key => {
    stuff_detail # just insert the existing detail item in the new detail
    inbound_all     = concat([stuff_details.main_value], stuff_details.additional_in_values) # insert a new element
 }

Or even a solution that selectively drops an element of a given value

Any ideas? I'd think a nested for..in loop should work but my own experiments are failing. If it were possible the inner for..in would iterate over stuff_details and selectively act on each existing item, as well as inserting the new items after

Code is:

variable "stuff" {
  type = map(object({
    id                    = string
    main_value            = string
    additional_in_values  = list(string)
    additional_out_values = list(string)
    more_values           = string
  }))
}

locals {
  unmaintainable = {
    for stuff_key, stuff_details in var.stuff :
      stuff_key => {
        id = stuff_details.id
        main_value = stuff_details.main_value
        additional_out_values = stuff_details.additional_out_values
        additional_in_values = stuff_details.additional_in_values
        more_values = stuff_details.more_values
        inbound_all     = concat([stuff_details.main_value], stuff_details.additional_in_values)
        outbound_all    = concat([stuff_details.main_value], stuff_details.additional_out_values)
      }
   }

  transformed-stuff = {
    for stuff_key, stuff_details in var.stuff :
    stuff_key => {
      inbound_all     = concat([stuff_details.main_value], stuff_details.additional_in_values)
      outbound_all    = concat([stuff_details.main_value], stuff_details.additional_out_values)
    }
  }
}


output "stuff" {
  value = var.stuff
}

output "unmaintainable" {
    value = local.unmaintainable
}

output "transformed-stuff" {
  value = local.transformed-stuff
}

The original value is at the top of the post and can be placed in terraform.tfvars

The not-satisfactory output of the code in its current state is:

Outputs:

stuff = {
  "object-1" = {
    "additional_in_values" = [
      "red",
    ]
    "additional_out_values" = [
      "green",
    ]
    "id" = "1"
    "main_value" = "blue"
    "more_values" = "foo"
  }
  "object-2" = {
    "additional_in_values" = [
      "blue",
    ]
    "additional_out_values" = [
      "red",
    ]
    "id" = "2"
    "main_value" = "green"
    "more_values" = "foo"
  }
  "object-3" = {
    "additional_in_values" = [
      "green",
    ]
    "additional_out_values" = [
      "blue",
    ]
    "id" = "3"
    "main_value" = "red"
    "more_values" = "foo"
  }
}
transformed-stuff = {
  "object-1" = {
    "inbound_all" = [
      "blue",
      "red",
    ]
    "outbound_all" = [
      "blue",
      "green",
    ]
  }
  "object-2" = {
    "inbound_all" = [
      "green",
      "blue",
    ]
    "outbound_all" = [
      "green",
      "red",
    ]
  }
  "object-3" = {
    "inbound_all" = [
      "red",
      "green",
    ]
    "outbound_all" = [
      "red",
      "blue",
    ]
  }
}
unmaintainable = {
  "object-1" = {
    "additional_in_values" = [
      "red",
    ]
    "additional_out_values" = [
      "green",
    ]
    "id" = "1"
    "inbound_all" = [
      "blue",
      "red",
    ]
    "main_value" = "blue"
    "more_values" = "foo"
    "outbound_all" = [
      "blue",
      "green",
    ]
  }
  "object-2" = {
    "additional_in_values" = [
      "blue",
    ]
    "additional_out_values" = [
      "red",
    ]
    "id" = "2"
    "inbound_all" = [
      "green",
      "blue",
    ]
    "main_value" = "green"
    "more_values" = "foo"
    "outbound_all" = [
      "green",
      "red",
    ]
  }
  "object-3" = {
    "additional_in_values" = [
      "green",
    ]
    "additional_out_values" = [
      "blue",
    ]
    "id" = "3"
    "inbound_all" = [
      "red",
      "green",
    ]
    "main_value" = "red"
    "more_values" = "fee"
    "outbound_all" = [
      "red",
      "blue",
    ]
  }
}
rpc
  • 79
  • 7
  • Move to a newer version. :) – Marko E Dec 11 '22 at 18:56
  • What's wrong with the iteration? That's how you would do it in TF. – Marcin Dec 11 '22 at 22:58
  • The problem with the iteration is that it is explicit ... each k/v pair needs to be mentioned by name, which is what makes the explicit solution unmaintainable in the larger operational context. It looks like I'll be able to add the new elements (inbound_all, outbound_all) via a merge without being explicit, but so far no good solution for pruning unwanted attributes while keeping the rest of the object not explicit. Partial solution at https://discuss.hashicorp.com/t/terraform-0-12-nested-for-in-or-other-technique-to-augment-an-object-in-a-map/47853/4 – rpc Dec 12 '22 at 01:30

0 Answers0