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

Length of list or map? #1389

Closed
nevir opened this issue Apr 4, 2015 · 19 comments
Closed

Length of list or map? #1389

nevir opened this issue Apr 4, 2015 · 19 comments

Comments

@nevir
Copy link
Contributor

nevir commented Apr 4, 2015

I figure I'm missing something here. How can I calculate the length of a list/map variable?

I'd like to specify a list of AWS availability zones, and generate a subnet per zone (either via a map of values, or the split() hack for now)

variable "zones" {
  default = {
    "0" = "us-west-2a"
    "1" = "us-west-2b"
    "2" = "us-west-2c"
  }
}

resource "aws_subnet" "public" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.0.${count.index * 4}.0/22"
  availability_zone = "${lookup(var.zones, count.index)}"
  count = 3 # How do I calculate this?
}
@radeksimko
Copy link
Member

👍

@knuckolls
Copy link
Contributor

Agreed. IMO some sort of count / length function should be in core.

@c4milo
Copy link
Contributor

c4milo commented Apr 11, 2015

I just hit the need for getting the length of a list too.

@radeksimko
Copy link
Member

See #1495

@radeksimko
Copy link
Member

This has been implemented & merged in #1495 , so the issue can be closed I guess?

@mikattack
Copy link

It appears like #1495 references strings and lists but not maps, which was part of the original request.

@radeksimko
Copy link
Member

@mikattack The original example can be reworked to the following:

variable "zones" {
  default = "us-west-2a,us-west-2b,us-west-2c"
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id = "${aws_vpc.main.id}"
  cidr_block = "10.0.${count.index * 4}.0/22"
  availability_zone = "${element(split(",", var.zones), count.index)}"
  count = "${length(split(",", var.zones))}"
}

not as elegant as if we'd have support for real lists default = ["us-west-2a","us-west-2b","us-west-2c"] , but we'll get there someday. So far this works pretty well for me.

I don't believe zones nor regions are good candidates for maps, it should really IMO be a list instead (whether that's a pseudo-list we've got now or real list in the future).

That said, if there is any valid use-case for ${length(map)}, it can be added, I can't see any at the moment.

@mikattack
Copy link

This is exactly the problem we ran into. Your workaround with delimited strings and length() solves our problem until lists can be supported.

Agree that there maybe isn't an immediate need for map lengths. Though, if we had key(map) we could achieve the same thing, and that'd be useful for other things.

@phinze
Copy link
Contributor

phinze commented Dec 9, 2015

Going to close this since the core of the issue is implemented and there hasn't been activity since then. Anybody on this thread can follow up here or file a new thread if there's additional work to be tracked here! 👍

@phinze phinze closed this as completed Dec 9, 2015
@ejoubaud
Copy link

@radeksimko: Actually I expected ${length(var.some_map)} to work out of the box, it was quite surprising to get a number that didn't match the number of entries in the map.

Maps with count, as described in the doc, are arguably a way more elegant solution than split-parsed comma-separated lists, pending actual lists.

The map technique also has the advantage over the list (comma-separated or actual) that it lets you specify several attributes varying over your otherwise similar resources (say a username and a path). Though doable with several lists, you need to keep them in sync, which is harder and uglier to express, and more error-prone:

variable "users" {
  default = {
    "0" = {
      "username" = "username.ofuser1"
      "path" = "/app1"
    }
    "1" = {
      "username" = "username.ofuser2"
      "path" = "/app2"
    }
  }
}

is more declarative than

variable "usernames" {
  default = "username.ofuser1,username.ofuser2,username.ofuser3"
}
variables "userpaths" {
  default = "path1,path2,path1"
}

[Edit: Just realized that I was repeating the first example, removed that part]

👍 for map length

@matthughes
Copy link

@ejoubaud Does that variable "users" syntax with the map of maps work? When I try that I get: ```Errors:

  • 1 error(s) occurred:
  • module root: 1 error(s) occurred:
  • Variable 'users': must be a string or a map```

@ejoubaud
Copy link

@matthughes: No, good point, it doesn't, not sure why, maybe nested maps. I tend to wish it did though, as it does in JSON. I think I just came up with the example on the spot to illustrate how maps are more expressive than parseable string lists.

@dryoni
Copy link

dryoni commented Jun 22, 2016

You can use the map helper functions to get the length of a map variable:

#1915

e.g.:

variable "aws_az_subnet_map" {
description = "The subnet id for each availability zone"
default = {
us-west-2a = "subnet-abcd1234"
us-west-2b = "subnet-efab5678"
us-west-2c = "subnet-cdef9012"
}
}
resource "aws_instance" "my_instance" {
count = "${length( keys(var.aws_az_subnet_map))}"
subnet_id = "${ element(values(var.aws_az_subnet_map)), count.index }"
}

@ocsi01
Copy link

ocsi01 commented Jun 28, 2016

subnet_id = "${ element(values(var.aws_az_subnet_map)), count.index }"

If I'm right, it should be:
subnet_id = "${ element(values(var.aws_az_subnet_map), count.index )}"

@tuannvm
Copy link

tuannvm commented Aug 23, 2017

what if we need to add another availability zone? Say:

default = {
us-west-2a = "subnet-abcd1234"
us-west-2b = "subnet-efab5678"
us-west-2c = "subnet-cdef9012"
us-west-2d = "subnet-11223344"
}

when I run terraform plan again, it seems terraform will shuffle the map sequence instead of keeping the same --> force new resource is the last thing we want somehow...

@LukeCarrier
Copy link

LukeCarrier commented Sep 11, 2017

The problem raised by @tuannvm still occurs -- I think @phinze may have closed this issue a little too quickly. For instance, the following azurerm_storage_share resource will attempt to destroy shares (potentially losing data!) when adding an item, regardless of the order within the map:

resource "azurerm_storage_share" "storage_shares" {
  count                = "${length(var.storage_shares)}"
  name                 = "${element(keys(var.storage_shares), count.index)}"
  resource_group_name  = "${var.resource_group_name}"
  storage_account_name = "${azurerm_storage_account.storage_account.name}"
  quota                = "${lookup(var.storage_shares, element(keys(var.storage_shares), count.index))}"
}

The problem also occurs when replacing the quota line with the following interpolation expression:

"${element(values(var.storage_shares), count.index))}"

var.storage_shares looks something like:

{
  "someshare": "100",
  "someothershare": "10"
}

@tuannvm
Copy link

tuannvm commented Sep 11, 2017

@LukeCarrier I found out that terraform map will follow alpha numeric order! It means no matter how you change the order, it'll be kept like this:

resource "azurerm_storage_share" "storage_shares" {
  count                = "${length(var.storage_shares)}"
  name                 = "${element(keys(var.storage_shares), count.index)}"
  quota                = "${lookup(var.storage_shares, element(keys(var.storage_shares), count.index))}"
  resource_group_name  = "${var.resource_group_name}"
  storage_account_name = "${azurerm_storage_account.storage_account.name}"
}

So, if you add new item that potentially to sit between c (count) and s (storage_account_name), then the map will be suffered. The quick hack will be adding the item with the prefix character from t to z...

@LukeCarrier
Copy link

@tuannvm I think you're right -- thanks for saving us a ton of time troubleshooting this issue 😕

It looks like this is a known bug: #14477

@ghost
Copy link

ghost commented Apr 7, 2020

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@ghost ghost locked and limited conversation to collaborators Apr 7, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests