Terraform: Count
Description
Here is a basic example of using the count
meta argument to deploy multiple resources. Note that it is generally preferred (see the ‘When to Use’ section) to use for_each
which I will cover in a different post.
To Resolve:
-
So I have
main.tf
with the following:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
terraform { required_providers { random = { source = "hashicorp/random" version = "~>3.3.2" } azurerm = { source = "hashicorp/azurerm" version = "~>3.10.0" } } required_version = "~>1.1.0" } provider "random" { } provider "azurerm" { client_id = var.client_id client_secret = var.client_secret subscription_id = var.subscription_id tenant_id = var.tenant_id skip_provider_registration = true features {} } variable "rg_count" { description = "(Optional) Number of Resource Groups to deploy." type = number default = 2 } variable "tenant_id" { description = "(Required) Service Principal AD Tenant ID - Azure AD for terraform authentication." type = string } variable "subscription_id" { description = "(Required) Azure Subscription Id used to connect to AzureRM provider." type = string } variable "client_id" { description = "(Required) Service Principal App ID - Azure AD for terraform authentication." type = string } variable "client_secret" { description = "(Required) Service Principal Client Secret - Azure AD for terraform authentication." type = string } resource "random_string" "naming_convention_unique" { count = var.rg_count length = 5 upper = false lower = true numeric = true special = false } resource "azurerm_resource_group" "rg" { count = var.rg_count name = "aa-dev-tx-test-${random_string.naming_convention_unique[count.index].result}" location = "westus" tags = { Owner = "Automation Admin" CostCenter = "100" EntAppname = "Automation Admin Terraform POC" Environment = "tst" Contact = "gerry@automationadmin.com" } } output "res_out_rg_name" { value = azurerm_resource_group.rg.*.name } output "res_out_rg_id" { value = azurerm_resource_group.rg.*.id } output "ids" { value = random_string.naming_convention_unique.*.result }
-
Running our regular:
1 2 3
terraform init terraform plan -var-file="env.tfvars" -out="tf.plan" terraform apply -auto-approve -input=false ./tf.plan
- I get the following output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: ids = [ "kxu7e", "tqoxw", ] res_out_rg_id = [ "/subscriptions/my-subscription-guid/resourceGroups/aa-dev-tx-test-kxu7e", "/subscriptions/my-subscription-guid/resourceGroups/aa-dev-tx-test-tqoxw", ] res_out_rg_name = [ "aa-dev-tx-test-kxu7e", "aa-dev-tx-test-tqoxw", ]
-
So count works good with an integer, no surprise. Let’s see how it handles a list:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
# replace # variable "rg_count" { # description = "(Optional) Number of Resource Groups to deploy." # type = number # default = 2 # } # with: variable "rgs_to_create" { description = "(Optional) List of Resource Groups to deploy." type = list(string) default = ["management", "organization"] } # Then down in the resources, do: count = length(var.rgs_to_create)
-
As expected, this shows that we will create 2 resource groups since the length of the
rgs_to_create
is2
. -
Lastly, I wanted to create a more advanced example, so I created this
main.tf
:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
terraform { required_providers { random = { source = "hashicorp/random" version = "~>3.3.2" } azurerm = { source = "hashicorp/azurerm" version = "~>3.10.0" } } required_version = "~>1.1.0" } provider "random" { } provider "azurerm" { client_id = var.client_id client_secret = var.client_secret subscription_id = var.subscription_id tenant_id = var.tenant_id skip_provider_registration = true features {} } variable "resource_group_name" { description = "(Optional) The name of a Resource Group" type = string default = "aa-dev-tx-test-2" } variable "tenant_id" { description = "(Required) Service Principal AD Tenant ID - Azure AD for terraform authentication." type = string } variable "subscription_id" { description = "(Required) Azure Subscription Id used to connect to AzureRM provider." type = string } variable "client_id" { description = "(Required) Service Principal App ID - Azure AD for terraform authentication." type = string } variable "client_secret" { description = "(Required) Service Principal Client Secret - Azure AD for terraform authentication." type = string } locals { resource_groups = [ "aa-dev-tx-test-2", "aa-dev-tx-test-3", "aa-dev-tx-test-4" ] aa_config = [ ["automation-1", "Basic", "some-value"], ["automation-2", "Basic", "some-value"], ["automation-3", "Basic", "some-value"] ] tst_tags = { Owner = "Automation Admin" CostCenter = "100" EntAppname = "Automation Admin Terraform POC" Environment = "tst" Contact = "gerry@automationadmin.com" } } resource "azurerm_resource_group" "rg" { count = length(local.resource_groups) name = local.resource_groups[count.index] location = "westus" tags = local.tst_tags } resource "azurerm_automation_account" "example" { count = length(local.resource_groups) name = local.aa_config[count.index][0] location = "westus" resource_group_name = local.resource_groups[count.index] sku_name = local.aa_config[count.index][1] tags = local.tst_tags } output "rg_names" { value = azurerm_resource_group.rg.*.name } output "aa_names" { value = azurerm_automation_account.example.*.name }
-
Which resulted in:
1 2 3 4 5 6 7 8 9 10 11 12 13
Plan: 6 to add, 0 to change, 0 to destroy. Changes to Outputs: + aa_names = [ + "automation-1", + "automation-2", + "automation-3", ] + rg_names = [ + "aa-dev-tx-test-2", + "aa-dev-tx-test-3", + "aa-dev-tx-test-4", ]
-
So this is not how I would do this in real life since it is almost always better to use
for_each
, but it does show how thecount
meta-argument works.- So what it does is creates a list object
local.resource_groups
which is a list of strings (typelist(string)
) with a length of3
. - It then creates a
local.aa_config
which is a list of lists ( typelist(list(string))
) with the same length of3
. - Since these happen to match up, what will happen is that
local.resource_groups[0]
will match up withlocal.aa_config[count.index][0]
andlocal.aa_config[count.index][1]
perfectly. - This works because both have the same amounts of elements. But again, I would use
for_each
as mentioned in the top description for the reason that it is hard to modify this. If you add a new element it will force you to destroy and recreate all of it instead of just adding that one element!
- So what it does is creates a list object
-
If you noticed that
local.aa_config[count.index][2]
with the value of"some-value"
was never used, good catch! This was on purpose to demonstrate that you don’t have to reference every value in a list if you don’t want to.- For example, you could have skipped
local.aa_config[count.index][1]
and referencedlocal.aa_config[count.index][2]
if the values were switched like:
1 2 3 4 5 6 7 8 9
aa_config = [ ["automation-1", "some-value", "Basic"], ["automation-2", "some-value", "Basic"], ["automation-3", "some-value", "Basic"] ] # Then update sku name to be the third element [2] instead of the second [1] # sku_name = local.aa_config[count.index][2]
-
It is real important with any language that you understand that they almost always start with
[0]
as the first element and then move on. So if something is inn
position in a list, just internally think “to reference this I will usen
- 1”. Example, to referencebob
in listnames = [ "jim", "bob", "anna"]
you would donames[1]
even thoughbob
is in position 2. -
Likewise, you can usually do backwards references like to reference
anna
it would benames[-1]
which is always the last element in the array. You can step backwards and saynames[-3]
and that would get youjim
but if you donames[-4]
you would get anout of bounds
type error which means you tried referencing something that doesn’t exist. Definitely play around with lists as they are used in many languages! -
Also, a list of lists just takes it one step further so you when you reference
local.aa_config[0]
for example you end up with value["automation-1", "some-value", "Basic"]
so you then have to reference one step further to get the one you want. For example, if I want to getsome-value
in the middle then I would targetlocal.aa_config[0][1]
since[1]
would be the second value in the list that was returned. -
Again, spend some time working on learning this, it is used in many languages and is usually at the heart of most leet-code challenges used in interviews. See some python examples on my Github for example.
- For example, you could have skipped
Comments