Monday 9 May 2022

Terraform Meta-Arguments

 

Terraform meta-arguments are special syntax constructs which help in specifying how we want resources to be managed.

There are 5 meta-arguments in Terraform:

  •     depends_on
  •     lifecycle
  •     count
  •     for_each
  •     provider

I've already covered depends_on and lifecycle in my previous articles Terraform Resource Dependencies | My Public Notepad and Terraform State | My Public Notepad.


 count

 

It specifies the number of infrastructure instances to be created. 


resource "local_file" "foo" {
    filename = "${path.cwd}/temp/foo.txt"
    content = "This is a text content of the foo file!"
    count = 3
}

Let's create multiple instances of local_file:

$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following
symbols:
  + create

Terraform will perform the following actions:

  # local_file.foo[0] will be created
  + resource "local_file" "foo" {
      + content              = "This is a text content of the foo file!"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "/home/bojan/dev/github/demo-terraform/local_resources_demo/temp/foo.txt"
      + id                   = (known after apply)
    }

  # local_file.foo[1] will be created
  + resource "local_file" "foo" {
      + content              = "This is a text content of the foo file!"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "/home/bojan/dev/github/demo-terraform/local_resources_demo/temp/foo.txt"
      + id                   = (known after apply)
    }

  # local_file.foo[2] will be created
  + resource "local_file" "foo" {
      + content              = "This is a text content of the foo file!"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "/home/bojan/dev/github/demo-terraform/local_resources_demo/temp/foo.txt"
      + id                   = (known after apply)
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.foo[0]: Creating...
local_file.foo[2]: Creating...
local_file.foo[1]: Creating...
local_file.foo[1]: Creation complete after 0s [id=db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e]
local_file.foo[2]: Creation complete after 0s [id=db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e]
local_file.foo[0]: Creation complete after 0s [id=db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

 

Only one file is actually created (3 times) though as filename is the same:

$ ls ./temp/
foo.txt

To prevent this, we can interpolate count.index:

filename = "${path.cwd}/temp/foo${count.index}.txt"

After terraform apply we now have 3 files:
 
$ ls ./temp/
foo0.txt  foo1.txt  foo2.txt

 
If names are defined in a variable of a tuple type, we can access them by using indexing.
 
If variables.tf contains:
 
# var.filename is tuple with 3 elements
variable "filename" {
  default = [
    "foo.txt",
    "foo2.txt",
    "foo3.txt",
  ]
}
 
...then we can refer to each element via index which will actually be the current value of count's index attribute:
 
filename = "${path.cwd}/temp/${var.filename[count.index]}" 

count is set above to the static value but it can automatically pick up the length of the list/tuple:

count = length(var.filename)

 
A drawback of using count for iterating a list is that TF creates resources as lists.

If we delete element at index 0 from the var.filename and apply terraform apply, TF will re-create foo2 and foo3 and destroy foo. But ideally, only foo destruction should be performed. This is because TF uses only list index to resources, not vales of the elements (file names in this case).


for_each


If we use for_each for creating multiple resources, they will be created as elements of map, not a list which is the case when count is used.
 
variables.tf:
 
variable "filename" {
  # Use this in order to fix Error: Invalid for_each argument
  # type = set(string)

  type = list(string)

  default = [
    "foo.txt",
    "foo2.txt",
    "foo3.txt",
  ]
}

 
main.tf:
 
resource "local_file" "foo" {
  # use this if var.filename has type set
  # for_each = var.filename

  # toset() converts list into set. Use it if var.filename is a list.
  for_each = toset(var.filename)

  filename = "${path.cwd}/temp/${each.value}"
  content = "This is a text content of the foo file!"
}
 
We can see that resource is created as a map where keys are values based on the each.value current value during the iteration and values are resource instances:
 
$ terraform apply
...
local_file.foo["foo2.txt"]: Creating...
local_file.foo["foo3.txt"]: Creating...
local_file.foo["foo.txt"]: Creating...
...

$ terraform show
# local_file.foo["foo.txt"]:
resource "local_file" "foo" {
    content              = "This is a text content of the foo file!"
    directory_permission = "0777"
    file_permission      = "0777"
    filename             = "/home/bojan/dev/github/demo-terraform/meta_args_demo/for_each/temp/foo.txt"
    id                   = "db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e"
}

# local_file.foo["foo2.txt"]:
resource "local_file" "foo" {
    content              = "This is a text content of the foo file!"
    directory_permission = "0777"
    file_permission      = "0777"
    filename             = "/home/bojan/dev/github/demo-terraform/meta_args_demo/for_each/temp/foo2.txt"
    id                   = "db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e"
}

# local_file.foo["foo3.txt"]:
resource "local_file" "foo" {
    content              = "This is a text content of the foo file!"
    directory_permission = "0777"
    file_permission      = "0777"
    filename             = "/home/bojan/dev/github/demo-terraform/meta_args_demo/for_each/temp/foo3.txt"
    id                   = "db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e"
}


Outputs:

foo_files = (sensitive value)
 
$ terraform output foo_files
{
  "foo.txt" = {
    "content" = "This is a text content of the foo file!"
    "content_base64" = tostring(null)
    "directory_permission" = "0777"
    "file_permission" = "0777"
    "filename" = "/home/bojan/dev/github/demo-terraform/meta_args_demo/for_each/temp/foo.txt"
    "id" = "db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e"
    "sensitive_content" = tostring(null)
    "source" = tostring(null)
  }
  "foo2.txt" = {
    "content" = "This is a text content of the foo file!"
    "content_base64" = tostring(null)
    "directory_permission" = "0777"
    "file_permission" = "0777"
    "filename" = "/home/bojan/dev/github/demo-terraform/meta_args_demo/for_each/temp/foo2.txt"
    "id" = "db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e"
    "sensitive_content" = tostring(null)
    "source" = tostring(null)
  }
  "foo3.txt" = {
    "content" = "This is a text content of the foo file!"
    "content_base64" = tostring(null)
    "directory_permission" = "0777"
    "file_permission" = "0777"
    "filename" = "/home/bojan/dev/github/demo-terraform/meta_args_demo/for_each/temp/foo3.txt"
    "id" = "db5ca40b5588d44e9ec6c1b4005e11a6fd0c910e"
    "sensitive_content" = tostring(null)
    "source" = tostring(null)
  }
}

 
 
If we now remove an item from var.filename and execute terraform apply, TF will only destroy a single resource, the one based on that value and it will not re-create other resources.
 
---

 

Resources: 
 

No comments: