Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Splat for all resources of a type #19931

Open
philomory opened this issue Jan 7, 2019 · 27 comments
Open

Feature Request: Splat for all resources of a type #19931

philomory opened this issue Jan 7, 2019 · 27 comments

Comments

@philomory
Copy link
Contributor

philomory commented Jan 7, 2019

Current Terraform Version

Terraform v0.12.0-alpha4 (2c36829d3265661d8edbd5014de8090ea7e2a076)
+ provider.random v2.0.0-5-g612dff2-dev

Use-cases

I'd like to be able to use resource_type.*.id to get a list of the id of every resource of a type; currently, you can only do this for resources that have a count attribute (or eventually for_each, I guess). Sometimes, you have a bunch of resources you want to declare explicitly because they don't necessarily have very much in common, but you still want to refer to them all from another resource.

My explicit use-case is the third-party auth0 provider. I've got a bunch of auth0_client resources that all have their own custom configuration. Then, I've got an auth0_connection resource which represents our actual identity provider (an LDAP server). In creating the auth0_connection resource, I need to pass a list of auth0_client ids that that connection will be used for. And I'd love to get that list of ids by simply writing auth0_client.*.client_id. But that's not possible.

Attempted Solutions

This didn't work:

resource "random_id" "foo" {       
  byte_length = 1                  
}                                  
                                   
resource "random_id" "bar" {       
  byte_length = 1                  
}                                  
                                   
output "example" {                 
  value = "${random_id.*.hex}"     
}                                  

I got this error:

$terraform validate

Error: Invalid reference

  on test.tf line 12, in output "example":
  12:   value = "${random_id.*.hex}"

A reference to a resource type must be followed by at least one attribute
access, specifying the resource name.

A similar attempt with random_id[*].hex also failed.

@Justin-W
Copy link

I have a need for the same use case as described above.

In addition, I'd like this 'resource type splat' feature to be usable in conjunction with the feature described in issue #9067 (and/or the upcoming for/for_each with optional filtering via an if qualifier) to enable me to create a single output variable whose value is a list of some or all of the resources (entire resources, not just IDs, or names) of a given type, without specifying or knowing the names or IDs of the included resources.

The need for this is based on my use of code generation tools to generate HCL dynamically. I want separate parts of my code generation to be able to generate HCL that will be combined with each other and applied as a single set of TF configuration, but also to have my separate code generation components to remain encapsulated and unaware of each other. But I'd like to have 1 component that generates output variables that collect all or some of the resources (and/or specific computed/exported attributes of them) into named output variables.

For example:

output "all_eips_list" {
  value = "${aws_eip.*}"
}

output "all_eip_public_ips_list" {
  value = "${aws_eip.*.public_ip}"
}

output "all_eip_public_to_private_ips_map" {
  # Result is a map from eip public IP to private IP address, such as:
  #  {"192.168.1.2" = "10.1.2.3", "192.168.1.5" = "10.2.3.4"}
  value = {
    for eip in aws_eip.*:
    eip.public_ip => eip.private_ip
  }
}

output "some_eip_public_to_private_ips_map" {
  # Result is a map from eip public IP to private IP address, such as:
  #  {"192.168.1.2" = "10.1.2.3", "192.168.1.5" = "10.2.3.4"}
  value = {
    for eip in aws_eip.*:
    eip.public_ip => eip.private_ip
    if eip.associate_with_private_ip_address
  }
}

Similar use cases exist for aws_lb.dns_name, aws_s3_bucket, and many other resources where the HCL configuration may contain a dynamic number of a specific type of resource (but not count-based repetition; each resource of the same type is explicitly declared separately, distinctly, and independently, as each resource of the same type has unique properties and configuration) and computed or exported attributes that aren't known until terraform apply or terraform refresh is executed.

@Justin-W
Copy link

A further (related but distinct) use case is that output variables similar to the examples above would (in many cases) be useful as 'reusable' HCL code, and could be included (without requiring any changes to the output variable HCL nor any configuration via input variables, etc.) in almost any arbitrary TF configuration that includes the referenced providers and resource types.

This would be useful to people that manage multiple TF configurations as a way of providing standardized (i.e. consistent across TF configurations, but with custom content and format determined by the output variables' reusable HCL definition) outputs.

This 'reusable outputs' use case applies equally well regardless of whether the separately managed TF configurations are manually generated or generated by code or by some other means.

@Justin-W
Copy link

Also, given that this change probably requires a change to HCL itself, it'd be great if this could be included in the initial v0.12 release. Is there still a chance of that?

@zygimantas
Copy link

One more use case:

I am creating N identical dynamodb tables in a module.
Module is called for each region (regional module).
In a root module, I need to create global dynamodb tables and I need regional table names.
It would be good, to return this output from a regional module:

output "table_names" {
  value = aws_dynamodb_table.*.name
}

@uutest74
Copy link

yes need this for output vars - different customized vms (windows and linux) created by module as different resources the same type!

@acdha
Copy link

acdha commented Mar 13, 2021

I was recently working on getting some of the less common resources tagged and some kind of ability like this would have been really useful to avoid needing to maintain a list of resources:

locals {
  resources_tags = setproduct(
    concat(
      values(aws_efs_mount_target.a)[*].network_interface_id,
      values(aws_efs_mount_target.b)[*].network_interface_id,
      values(aws_efs_mount_target.c)[*].network_interface_id,
      values(aws_efs_mount_target.d)[*].network_interface_id,
      values(aws_efs_mount_target.e)[*].network_interface_id,
      module.vpc.vpc_endpoint_efs_network_interface_ids,
      module.vpc.private_route_table_ids,
      module.vpc.public_route_table_ids,
    ),
    [for key, value in local.tags :
      [key, value]
    ]
  )
}

resource "aws_ec2_tag" "all" {
  count       = length(local.resources_tags)
  resource_id = local.resources_tags[count.index][0]
  key         = local.resources_tags[count.index][1][0]
  value       = local.resources_tags[count.index][1][1]
}

@farfromunique
Copy link

Here we are, approaching 3 years after this was opened. Is this at all likely to happen?

@HariSekhon
Copy link

HariSekhon commented Feb 28, 2022

I need this to for_each for github_repositories too.

For now, I've quickly worked around it using an external program to generate the list from the terraform state for any given resource type. The terraform_resources.sh script can be found here:

https://github.com/HariSekhon/DevOps-Bash-tools

and working Terraform code using it can be found here:

https://github.com/HariSekhon/Terraform

in the github_team_repository.tf and github_branch.tf files - each of which are getting different attributes using that script.

@Charles-Stuart
Copy link

Jumping on this bandwagon, for called modules within a workspace. I need to output certain metadata values for each system built by a module, and I can't use foreach as each system needs to be able to have different providers specified for its module call.

Example: This is currently the only way I got it working. This is NOT manageable at scale at all. Each new server built requires the sys admins to also add to the output value... which people have already forgotten, causing failed builds.

module "srv-1" {
  providers = {...}
}
module "srv-2" {
  providers = {... something different ...}
}

output "all-systems" {
  value = merge(module.srv-1, module.srv-2)
}

All we really need is the ability for "outputs" to iterate over the built resources and modules in state, instead of needing to know the exact resource id ahead of time.

@markpendlebury
Copy link

Adding my use case:

Currently creating a bunch of aws_apigatewayv2_domain_name's to be later used in aws_apigatewayv2_api_mappings

Would be nice to have something similar to the following:

resource "aws_apigatewayv2_domain_name" "www"{
  domain_name = var.env == "prod" ? "www.${var.domain}.co.uk"  : "www.${var.env}.${var.domain}.co.uk"
}

resource "aws_apigatewayv2_domain_name" "top"{
  domain_name = var.env == "prod" ? "${var.domain}.co.uk"  : "${var.env}.${var.domain}.co.uk"
}

then a local similar to this:

locals {
  domains = [aws_apigatewayv2_domain_name.*.domain_name]
}

to then be referenced with:

resource "aws_apigatewayv2_api_mapping" "domain" {
    for_each = for_each(local.domains, "domain")
    ....
}

@skgsergio
Copy link

A use case in Azure: creating diagnostics settings for a bunch of resources of a type

resource "azurerm_monitor_diagnostic_setting" "keyvaults" {
  for_each = toset(azurerm_key_vault.*.id)  # <-- or something like that

  name               = "storage-export"
  target_resource_id = each.key
  storage_account_id = azurerm_storage_account.diag_logs.id

  log {
    category = "AuditEvent"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 0
    }
  }

  log {
    category = "AzurePolicyEvaluationDetails"
    enabled  = true

    retention_policy {
      enabled = true
      days    = 0
    }
  }
}

@divy4
Copy link

divy4 commented Oct 25, 2022

Piling on the use case list, this would be great for setting dynamic resource dependencies when you want a resource to depend on all resources of another type:

resource "type" "name" {
  depends_on = trigger_resource_type.*
  ...
}

@ZacharyWallace
Copy link

ZacharyWallace commented Oct 27, 2022

Useful for working with the Okta provider, specifically when using okta_group_role resource.

resource "okta_group" "admins-group" {
    name        = "admins"
    description = "Admins"
 }
 
  resource "okta_group" "reader-group" {
    name        = "readers"
    description = "Readers"
  }

  resource "okta_group_role" "admin-group-role" {
    group_id  = okta_group.admins-group.id
    role_type = "USER_ADMIN"
    target_group_list = okta_group.*.id
 }

@davidcorrigan714
Copy link

Useful for working with the Okta provider, specifically when using okta_group_role resource.

resource "okta_group" "admins-group" {
    name        = "admins"
    description = "Admins"
 }
 
  resource "okta_group" "reader-group" {
    name        = "readers"
    description = "Readers"
  }

  resource "okta_group_role" "admin-group-role" {
    group_id  = okta_group.admins-group.id
    role_type = "USER_ADMIN"
    target_group_list = okta_group.*.id
 }

A similar problem led me here too. What I ended up doing was putting the group constituents into YAML files so I could then use fileset to consolidate in more creative ways in the terraform files.

@ZacharyWallace
Copy link

Useful for working with the Okta provider, specifically when using okta_group_role resource.

resource "okta_group" "admins-group" {
    name        = "admins"
    description = "Admins"
 }
 
  resource "okta_group" "reader-group" {
    name        = "readers"
    description = "Readers"
  }

  resource "okta_group_role" "admin-group-role" {
    group_id  = okta_group.admins-group.id
    role_type = "USER_ADMIN"
    target_group_list = okta_group.*.id
 }

A similar problem led me here too. What I ended up doing was putting the group constituents into YAML files so I could then use fileset to consolidate in more creative ways in the terraform files.

Can you expand on this? I will likely need to end up doing similar as a workaround until this feature has been implemented.

Did the files just contain the individual groups? How did you reference the files when building your list for okta_group_role? I am not familiar with fileset.

@davidcorrigan714
Copy link

Mine is for Azure DevOps and I specifically wanted a file per set of groups because of how our code review process works, but I also wanted a distinct list of all users in all defined groups. The idea should allow more creative groups of things in general though. I made a locals that looks something like:

locals {
  groups = flatten([
    for group_file in fileset(path.module, "groups/*.yml") : yamldecode(file("${path.module}/${group_file}"))["groups"]
  ])

  data_users             = distinct(flatten([for group in local.groups : lookup(group.members, "users", [])]))
  data_groups = distinct(flatten([for group in local.groups : lookup(group.members, "groups", [])]))
}


data "azuredevops_group" "groups" {
  project_id = data.azuredevops_project.project.project_id
  for_each   = toset(local.data_groups)
  name       = each.value
}

@suttin
Copy link

suttin commented Jan 4, 2023

To add another use case on a (almost) 3 year old issue, I am trying to create a cloudwatch dashboard in aws with the alarm widget. The cloudwatch widget only accepts arns. Would be super useful if I could make my code look something like this:

  resource "aws_cloudwatch_dashboard" "Alarm-Dashboard" {
  dashboard_name = "Alarm-Dashboard"
  dashboard_body = <<EOF
{
    "widgets": [
        {
            "type": "alarm",
            "x": 0,
            "y": 0,
            "width": 24,
            "height": 11,
            "properties": {
                "title": "",
                "alarms": "[${aws_cloudwatch_metric_alarm.*.id}]"
            }
        }
    ]
}
EOF
}

@mBlomsterberg
Copy link

Can we get insights from the developers if this is worked on or why this is being withheld?

@crw
Copy link
Collaborator

crw commented Jan 25, 2023

@mBlomsterberg It is not being worked on. It is currently at # 33 of the most-upvoted issues. We use upvotes as a method to help feed our product backlog. Thanks for your question!

@EzekielEnns
Copy link

EzekielEnns commented Jun 23, 2023

Got another use case for making it easier to access lambdas in a template file

resource "aws_api_gateway_rest_api" "example" {
  name        = "example"
  description = "example"
  body = templatefile("${file("../backend/api.yaml")}", {
    //gives access to lambda[func_name] inside api.yml
    lambdas = { for lambda in aws_lambda_function : lambda.handler => lambda.invoke_arn }
  })
}

barthap added a commit to CommE2E/comm that referenced this issue Aug 8, 2023
Summary:
> This stack resolves issues encountered when setting up fresh staging AWS account with Terraform.

This diff resolves an issue when running terraform plan on plain fresh AWS account. The `aws_dynamodb_table` data doesn't resolve to anything because the DDB table isn't yet created.

Resources from inside module aren't globally exposed, so I created a `outputs.tf` file in the shared module and iterated over explicitly-specified table resources to expose them.

> I really wanted to do it in a more automated way, but TF has no good mechanism for iterating over all resources yet. There's an [[ hashicorp/terraform#19931 | open issue ]] for that where people share other usecases for such feature.

Depends on D8714

Test Plan: Production `terraform plan` with no changes. Staging plan no longer fails.

Reviewers: jon, varun

Reviewed By: jon

Subscribers: ashoat, tomek

Differential Revision: https://phab.comm.dev/D8715
@webstean
Copy link

Any progress - would be very handy!

@EzekielEnns
Copy link

EzekielEnns commented Sep 28, 2023

Any progress - would be very handy!

Here's a workaround I found:

resource "aws_lambda_function" "test" {
}

//Dont forget to add this
//once this issue is passed https://github.com/hashicorp/terraform/issues/19931#
locals {
  lambdas = [
    aws_lambda_function.test
  ]

}

resource "aws_api_gateway_rest_api" "example_api" {
  body = templatefile("../backend/api.yaml",
    { for lambda in local.lambdas : lambda.handler => lambda.invoke_arn }
  )
}

just adding all the resources you need (addressed by name)
to an array, it's a little annoying, but I've found it gets the job done for now, it should let you splat the array and therefore splat the resources inside 🤷

@chris-ng-scmp
Copy link

I thought this was a very basic syntax that should be supported

Getting the list of the same resource type is very useful

@Spince
Copy link

Spince commented Oct 31, 2023

would love this feature

@cturner-confluent
Copy link

Adding another +1 on this feature would be great to have.

@andersro93
Copy link

This is something our organization really needs. Please prioritize this as it also seems that others have need for this feature as well 🚀

@hikerspath
Copy link

hikerspath commented Apr 11, 2024

For sure this is exactly what I would like to have so that I could do something like this for dynamically defined outputs:

resource "coralogix_webhook" "opsgenie" {
  for_each = local.opsgenie_team_keys

  name     = "opsgenie-${each.key}"
  opsgenie = {
    url = "https://api.opsgenie.com/v1/json/?apiKey=${each.value.api_key}"
  }
}

resource "coralogix_webhook" "jira" {
  for_each = local.jira_team_keys

  name     = "jira-${each.key}"
  jira     = {
    url         = "https://redacted.atlassian.net"
    project_key = each.value.project_key
  }
}

#
# Then with this type of output we would be able to define the following
#
output "coralogix_webhooks" {
  value = { for hook in coralogix_webhook.* : hook.name => hook.id }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests