hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.4k stars 9.5k forks source link

New Function keylistmap() #33655

Open seanturner026 opened 1 year ago

seanturner026 commented 1 year ago

Terraform Version

Terraform v1.5.4
on darwin_arm64

Your version of Terraform is out of date! The latest version
is 1.5.5. You can update by downloading from https://www.terraform.io/downloads.html

Use Cases

Problem to be solved

It is not possible to for_each over a list(map) unless a map comprehension is written which is slower and more tedious to write than for_each = local.map_object.

Overview

for_each requires a map object as it unpacks to at least each.key and optionally includes each.value

Generally this is done with the following:

resource "foo" "this" {
  for_each = { x = 1, y = 2 }

  name = each.value
}
resource "foo" "this" {
  for_each = toset([1, 2])

  name = each.key
}

for_each often loops over a long map object somewhere in locals{}. These object keys can become harder to read at a glance with the rest of the object as the map object gets longer.

locals {
  eventbridge_map = {
    update_available = {
      description = "blah"
      event_pattern = {
        detail = {
          status      = ["Available"]
          severity    = ["Informational"]
          description = [{ prefix = "Service software update" }]
        }
      }
    }
    update_failed = {
      description = "blah blah blah"
      event_pattern = {
        detail = {
          status      = ["Failed"]
          severity    = ["High"]
          description = [{ prefix = "Installation of service software update" }]
        }
      }
    }
    update_required = {
      description = "blah blah"
      event_pattern = {
        detail = {
          status      = ["Required"]
          severity    = ["High"]
          description = [{ prefix = "Service software update [" }]
        }
      }
    }
    update_restarted = {
      description = "blah blah blahh"
      event_pattern = {
        detail = {
          status      = ["Required"]
          severity    = ["High"]
          description = [{ prefix = "Service software restarted [" }]
        }
      }
    }
  }
}

I believe that it is easier and quicker to read the equivalent list(map) as the indentation is more consistent. Further, most modern editors will also render the value of the name attribute as a different color which makes it stand out more compared to the update_* map keys in the previous example, which are the same color as the other map keys. While not exactly the same idea, this makes me think of Mat Ryer's argument of line of sight (Medium.com -- Code: Align the happy path to the left edge

locals {
  eventbridge_list_map = [
    {
      name        = "update_available"
      description = "blah"
      event_pattern = {
        detail = {
          status      = ["Available"]
          severity    = ["Informational"]
          description = [{ prefix = "Service software update" }]
        }
      }
    },
    {
      name        = "update_failed"
      description = "blah blah blah"
      event_pattern = {
        detail = {
          status      = ["Failed"]
          severity    = ["High"]
          description = [{ prefix = "Installation of service software update" }]
        }
      }
    },
    {
      name        = "update_required"
      description = "blah blah"
      event_pattern = {
        detail = {
          status      = ["Required"]
          severity    = ["High"]
          description = [{ prefix = "Service software required [" }]
        }
      }
    },
    {
      name        = "update_restarted"
      description = "blah blah blahh"
      event_pattern = {
        detail = {
          status      = ["Required"]
          severity    = ["High"]
          description = [{ prefix = "Service software restarted [" }]
        }
      }
    }
  ]
}

Attempted Solutions

Considering the above example where local.eventbridge_list_map is a list(map), the only way to use that object in for_each is with a map compression.

resource "foo" "this" {
  for_each = { for v in local.eventbridge_list_map :  v.name => v }

  name = each.value.name
}

However, using the list(map) here is more tedious to write and harder to read than using local.eventbridge_map despite having the same outcome

resource "foo" "this" {
  for_each = local.eventbridge_map  

  name = each.value.name
}

Proposal

Create a new function keylistmap() which has the following interface:

keylistmap(key, listmap)

Invoking keylistmap with the local.eventbridge_list_map object would have the following behavior:

$ terraform console
> keylistmap("name", local.eventbridge_list_map)
{
    update_available = {
      name        = "update_available"
      description = "blah"
      event_pattern = {
        detail = {
          status      = ["Available"]
          severity    = ["Informational"]
          description = [{ prefix = "Service software update" }]
        }
      }
    },
    update_failed = {
      name        = "update_failed"
      description = "blah blah blah"
      event_pattern = {
        detail = {
          status      = ["Failed"]
          severity    = ["High"]
          description = [{ prefix = "Installation of service software update" }]
        }
      }
    },
    update_required = {
      name        = "update_required"
      description = "blah blah"
      event_pattern = {
        detail = {
          status      = ["Required"]
          severity    = ["High"]
          description = [{ prefix = "Service software required [" }]
        }
      }
    },
    update_restarted = {
      name        = "update_restarted"
      description = "blah blah blahh"
      event_pattern = {
        detail = {
          status      = ["Required"]
          severity    = ["High"]
          description = [{ prefix = "Service software restarted [" }]
        }
      }
    }
  }

This would have the benefit of turning the map comprehension example...

resource "foo" "this" {
  for_each = { for v in local.eventbridge_list_map :  v.name => v }

  name = each.value.name
}

Into something more native:

resource "foo" "this" {
  for_each = keylistmap("name", local.eventbridge_list_map)

  name = each.value.name
}

This aims to maximize readability in the iterable through utilizing list(map)s as well as in the for_each statement by via an ergonomic alternative to a map comprehension.

References

No response

crw commented 6 months ago

Thank you for your continued interest in this issue.

Terraform version 1.8 launches with support of provider-defined functions. It is now possible to implement your own functions! We would love to see this implemented as a provider-defined function.

Please see the provider-defined functions documentation to learn how to implement functions in your providers. If you are new to provider development, learn how to create a new provider with the Terraform Plugin Framework. If you have any questions, please visit the Terraform Plugin Development category in our official forum.

We hope this feature unblocks future function development and provides more flexibility for the Terraform community. Thank you for your continued support of Terraform!