DEV Community

Arseny Zinchenko
Arseny Zinchenko

Posted on • Originally published at rtfm.co.ua on

Terraform: modules, Outputs, and Variables

Eventually, I got to the modules in Terraform.

Namely, I had to figure out how to transfer the values ​​of the variables between the two modules.

So in this post, the most basic and simple examples of working with modules and their values ​​&& outputs.

See more in the documentation — Modules.

The Root module

First, let’s create a root module, which simply creates a local file, and where later, we will add the modules.

Create a testing directory:

$ mkdir modules_example
$ cd modules_example/
Enter fullscreen mode Exit fullscreen mode

In this directory add a file main.tf with the resource of the local_file type that will create a file.txt with the "file content" text:

resource "local_file" "file" {
  content = "file content"
  filename = "file.txt"
}
Enter fullscreen mode Exit fullscreen mode

Run terraform init to pull up the necessary modules of Terraform itself:

$ terraform init
Initializing the backend…
Initializing provider plugins…
- Finding latest version of hashicorp/local…
- Installing hashicorp/local v2.2.3…
- Installed hashicorp/local v2.2.3 (signed by HashiCorp)
…
Enter fullscreen mode Exit fullscreen mode

Terraform has been successfully initialized!

Then, run terraform plan to check whether it will work at all, and what exactly it will do:

$ terraform plan
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.file will be created
+ resource “local_file” “file” {
+ content = “file content”
+ directory_permission = “0777”
+ file_permission = “0777”
+ filename = “file.txt”
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
Enter fullscreen mode Exit fullscreen mode

If it looks OK, run the terraform apply:

$ 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.file will be created
+ resource “local_file” “file” {
+ content = “file content”
+ directory_permission = “0777”
+ file_permission = “0777”
+ filename = “file.txt”
+ id = (known after apply)
}
Plan: 1 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.file: Creating…
local_file.file: Creation complete after 0s [id=87758871f598e1a3b4679953589ae2f57a0bb43c]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Enter fullscreen mode Exit fullscreen mode

And check the contents of the directory where we created the main.tf file and launched the commands:

$ ls -l
total 12
-rwxr-xr-x 1 setevoy setevoy 12 Nov 28 13:14 file.txt
-rw-r — r — 1 setevoy setevoy 90 Nov 28 13:09 main.tf
-rw-r — r — 1 setevoy setevoy 854 Nov 28 13:14 terraform.tfstate
Enter fullscreen mode Exit fullscreen mode

And the file.txt content:

$ cat file.txt
file content
Enter fullscreen mode Exit fullscreen mode

It works! let’s go further.

Terraform Modules

Next, let’s go to the modules.

Create two directories for two modules:

$ mkdir -p modules/file_1
$ mkdir -p modules/file_2
Enter fullscreen mode Exit fullscreen mode

In each of them, create their own main.tf files - the modules/file_1/main.tf and modules/file_2/main.tf.

In the modules/file_1/main.tf use the same local_file resource to create a file_1.txt file :

resource "local_file" "file_1" {
  content = "file_1 content"
  filename = "file_1.txt"
}
Enter fullscreen mode Exit fullscreen mode

Similarly in the modules/file_2/main.tf for the file_2.txt:

resource "local_file" "file_2" {
  content = "file_2 content"
  filename = "file_2.txt"
}
Enter fullscreen mode Exit fullscreen mode

Update the root module, that is, modules_example/main.tf - delete the resource "local_file", and instead of it describe the two modules with the paths to the directories of both modules:

module "file_1" {
  source = "./modules/file_1"
}

module "file_2" {
  source = "./modules/file_2"
}
Enter fullscreen mode Exit fullscreen mode

Run the init again so that Terraform creates its modules structure:

$ terraform init
Initializing modules…
- file_1 in modules/file_1
- file_2 in modules/file_2
Initializing the backend…
Initializing provider plugins…
- Reusing previous version of hashicorp/local from the dependency lock file
- Using previously-installed hashicorp/local v2.2.3
Terraform has been successfully initialized!
Enter fullscreen mode Exit fullscreen mode

Check the .terraform/modules/:

$ cat .terraform/modules/modules.json | jq
{
“Modules”: [
{
“Key”: “”,
“Source”: “”,
“Dir”: “.”
},
{
“Key”: “file_1”,
“Source”: “./modules/file_1”,
“Dir”: “modules/file_1”
},
{
“Key”: “file_2”,
“Source”: “./modules/file_2”,
“Dir”: “modules/file_2”
}
]
}
Enter fullscreen mode Exit fullscreen mode

Run plan:

$ terraform plan
local_file.file: Refreshing state… [id=87758871f598e1a3b4679953589ae2f57a0bb43c]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
- destroy
Terraform will perform the following actions:
local_file.file will be destroyed
(because local_file.file is not in configuration)
- resource “local_file” “file” {
- content = “file content” -> null
- directory_permission = “0777” -> null
- file_permission = “0777” -> null
- filename = “file.txt” -> null
- id = “87758871f598e1a3b4679953589ae2f57a0bb43c” -> null
}
module.file_1.local_file.file_1 will be created
+ resource “local_file” “file_1” {
+ content = “file_1 content”
+ directory_permission = “0777”
+ file_permission = “0777”
+ filename = “file_1.txt”
+ id = (known after apply)
}
module.file_2.local_file.file_2 will be created
+ resource “local_file” “file_2” {
+ content = “file_2 content”
+ directory_permission = “0777”
+ file_permission = “0777”
+ filename = “file_2.txt”
+ id = (known after apply)
}
Plan: 2 to add, 0 to change, 1 to destroy.
Enter fullscreen mode Exit fullscreen mode

And now we can perform the apply.

In order not to enter “yes” every time, we can use the -auto-approve argument:

$ terraform apply -auto-approve
local_file.file: Refreshing state… [id=87758871f598e1a3b4679953589ae2f57a0bb43c]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
- destroy
Terraform will perform the following actions:
local_file.file will be destroyed
(because local_file.file is not in configuration)
- resource “local_file” “file” {
- content = “file content” -> null
- directory_permission = “0777” -> null
- file_permission = “0777” -> null
- filename = “file.txt” -> null
- id = “87758871f598e1a3b4679953589ae2f57a0bb43c” -> null
}
module.file_1.local_file.file_1 will be created
+ resource “local_file” “file_1” {
+ content = “file_1 content”
+ directory_permission = “0777”
+ file_permission = “0777”
+ filename = “file_1.txt”
+ id = (known after apply)
}
module.file_2.local_file.file_2 will be created
+ resource “local_file” “file_2” {
+ content = “file_2 content”
+ directory_permission = “0777”
+ file_permission = “0777”
+ filename = “file_2.txt”
+ id = (known after apply)
}
Plan: 2 to add, 0 to change, 1 to destroy.
local_file.file: Destroying… [id=87758871f598e1a3b4679953589ae2f57a0bb43c]
module.file_2.local_file.file_2: Creating…
module.file_1.local_file.file_1: Creating…
local_file.file: Destruction complete after 0s
module.file_2.local_file.file_2: Creation complete after 0s [id=7225b36c22072cd558c23529d0d992c29cb873be]
module.file_1.local_file.file_1: Creation complete after 0s [id=82888a6ec6b05fc219759bd241ca5f0d6cba0e23]
Apply complete! Resources: 2 added, 0 changed, 1 destroyed.
Enter fullscreen mode Exit fullscreen mode

Check whether the files have appeared:

$ ll
total 24
-rwxr-xr-x 1 setevoy setevoy 14 Nov 28 13:21 file_1.txt
-rwxr-xr-x 1 setevoy setevoy 14 Nov 28 13:21 file_2.txt
-rw-r — r — 1 setevoy setevoy 104 Nov 28 13:18 main.tf
drwxr-xr-x 4 setevoy setevoy 4096 Nov 28 13:15 modules
Enter fullscreen mode Exit fullscreen mode

And their content:

$ cat file_1.txt
file_1 content
cat file_2.txt
file_2 content
Enter fullscreen mode Exit fullscreen mode

Works? Move on.

Variables in Terraform modules

There is nothing special here  -  everything is the same as with common Terraform variables. See the documentation  -  Input Variables.

In the modules_example/modules/file_1/main.tf declare a variable user_name:

variable "user_name" {
  type = string
}    

resource "local_file" "file_1" {
  content = "file_1 content from ${var.user_name}"
  filename = "file_1.txt"
}
Enter fullscreen mode Exit fullscreen mode

The same in the second module:

variable "user_name" {
  type = string
}    

resource "local_file" "file_2" {
  content = "file_2 content from ${var.user_name}"
  filename = "file_2.txt"
}
Enter fullscreen mode Exit fullscreen mode

Update the root module — pass the values for the user_name​ variable ​for both modules:

module "file_1" {
  user_name = "user1"
  source = "./modules/file_1"
}

module "file_2" {
  user_name = "user2"
  source = "./modules/file_2"
}
Enter fullscreen mode Exit fullscreen mode

Apply the changes:

$ terraform apply -auto-approve
module.file_1.local_file.file_1: Refreshing state… [id=82888a6ec6b05fc219759bd241ca5f0d6cba0e23]
module.file_2.local_file.file_2: Refreshing state… [id=7225b36c22072cd558c23529d0d992c29cb873be]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
module.file_1.local_file.file_1 must be replaced
-/+ resource “local_file” “file_1” {
~ content = “file_1 content” -> “file_1 content from user1” # forces replacement
~ id = “82888a6ec6b05fc219759bd241ca5f0d6cba0e23” -> (known after apply)
(3 unchanged attributes hidden)
}
module.file_2.local_file.file_2 must be replaced
-/+ resource “local_file” “file_2” {
~ content = “file_2 content” -> “file_2 content from user2” # forces replacement
~ id = “7225b36c22072cd558c23529d0d992c29cb873be” -> (known after apply)
(3 unchanged attributes hidden)
}
…
Apply complete! Resources: 2 added, 0 changed, 2 destroyed.
Enter fullscreen mode Exit fullscreen mode

And the result check:

$ cat file_1.txt
file_1 content from user1
Enter fullscreen mode Exit fullscreen mode

Modules and Output values ​​of the variables

How can we pass the value of the variable from the child module to the root module? Use outputs. See Output Values ​​documentation.

Update the first module -  add the outputwith the name "file_content":

variable "user_name" {
  type = string
}    

resource "local_file" "file_1" {
  content = "file_1 content from ${var.user_name}"
  filename = "file_1.txt"
} 

output "file_content" {
  value = file("file_1.txt")
}
Enter fullscreen mode Exit fullscreen mode

The same in the second:

variable "user_name" {
  type = string
}    

resource "local_file" "file_2" {
  content = "file_2 content from ${var.user_name}"
  filename = "file_2.txt"
} 

output "file_content" {
  value = file("file_2.txt")
}
Enter fullscreen mode Exit fullscreen mode

And then in the root module, use the values ​​obtained using with the module.<MODULE_NAME>.<OUTPUD_NAME> to create a file concat_file.txt :

module "file_1" {
  user_name = "user1"
  source = "./modules/file_1"
}

module "file_2" {
  user_name = "user2"
  source = "./modules/file_2"
}

resource "local_file" "concat_file" {
  content = "${module.file_1.file_content}\n${module.file_2.file_content}\n"
  filename = "concat_file.txt"
}
Enter fullscreen mode Exit fullscreen mode

Run apply:

$ terraform apply -auto-approve
module.file_2.local_file.file_2: Refreshing state… [id=5771aa8b1046de4f4342d492faee20e3c289365d]
module.file_1.local_file.file_1: Refreshing state… [id=8a3552bfa2e46f311e7b718f8eed71b69bb8115f]
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.concat_file will be created
+ resource “local_file” “concat_file” {
+ content = <<-EOT
file_1 content from user1
file_2 content from user2
EOT
+ directory_permission = “0777”
+ file_permission = “0777”
+ filename = “concat_file.txt”
+ id = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
local_file.concat_file: Creating…
local_file.concat_file: Creation complete after 0s [id=a4e1240fb9cdc96fffc1b0984392f6218422d194]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Enter fullscreen mode Exit fullscreen mode

Check the result:

$ cat concat_file.txt
file_1 content from user1
file_2 content from user2
Enter fullscreen mode Exit fullscreen mode

Transfer variable values ​​between modules

And finally, what we started with  -  how to transfer values ​​from one module to another?

In the file main.tf of the first module, add a variable with the name module_1_value and a default value "module 1 value":

...
variable "module_1_value" {
  type = string
  default = "module 1 value"
}
...
Enter fullscreen mode Exit fullscreen mode

In the same place, create a second variable module_2_value, into which we will then pass the value from the second module:

...
variable "module_2_value" {
  type = string
}
...
Enter fullscreen mode Exit fullscreen mode

Add the output to return the value of the variable module_1_value to the root module for further use in the second module:

...
output "module_1_value" {
  value = var.module_1_value
}
...
Enter fullscreen mode Exit fullscreen mode

The complete file now looks like this:

variable "user_name" {
  type = string
} 

variable "module_1_value" {
  type = string
  default = "module 1 value"
} 

variable "module_2_value" {
  type = string
}

resource "local_file" "file_1" {
  content = "file_1 content from ${var.user_name} with value from file_2: ${var.module_2_value}"
  filename = "file_1.txt"
}

output "file_content" {
  value = file("file_1.txt")
}

output "module_1_value" {
  value = var.module_1_value
}
Enter fullscreen mode Exit fullscreen mode

Here in the resource local_file we will use the value transferred from the second module.

Repeat the same for the module file_2:

variable "user_name" {
  type = string
} 

variable "module_2_value" {
  type = string
  default = "module 2 value"
} 

variable "module_1_value" {
  type = string
}

resource "local_file" "file_2" {
  content = "file_2 content from ${var.user_name} with value from file_1: ${var.module_1_value}"
  filename = "file_2.txt"
}

output "file_content" {
  value = file("file_2.txt")
}

output "module_2_value" {
  value = var.module_2_value
}
Enter fullscreen mode Exit fullscreen mode

Return to the root module and add the transfer of the variable module_2_value to the first module, and the variable module_1_value to the second module:

module "file_1" {
  user_name = "user1"
  module_2_value = module.file_2.module_2_value
  source = "./modules/file_1"
}   

module "file_2" {
  user_name = "user2"
  module_1_value = module.file_1.module_1_value
  source = "./modules/file_2"
} 

resource "local_file" "concat_file" {
  content = "${module.file_1.file_content}\n${module.file_2.file_content}\n"
  filename = "concat_file.txt"
}
Enter fullscreen mode Exit fullscreen mode

Now in the file file_1.txt we have to get the value from the module "file_2", and vice versa.

Apply:

$ terraform apply -auto-approve
module.file_1.local_file.file_1: Refreshing state… [id=d74b0e0b3296ab02e7b4791942d983b175d071b4]
local_file.concat_file: Refreshing state… [id=e0e88293b53693a484db146b15bd2ab3d8f4d250]
module.file_2.local_file.file_2: Refreshing state… [id=12faf027dc96d886c6022b54c878324a21d3d112]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
local_file.concat_file must be replaced
-/+ resource “local_file” “concat_file” {
~ content = <<-EOT # forces replacement
- file_1 content from user1 with value from file_2: variable 2 value
- file_2 content from user2 with value from file_1: variable 1 value
+ file_1 content from user1 with value from file_2: module 2 value
+ file_2 content from user2 with value from file_1: module 1 value
EOT
~ id = “e0e88293b53693a484db146b15bd2ab3d8f4d250” -> (known after apply)
(3 unchanged attributes hidden)
}
Plan: 1 to add, 0 to change, 1 to destroy.
local_file.concat_file: Destroying… [id=e0e88293b53693a484db146b15bd2ab3d8f4d250]
local_file.concat_file: Destruction complete after 0s
local_file.concat_file: Creating…
local_file.concat_file: Creation complete after 0s [id=648854841581b273d26646fff776b33fdf060a19]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Enter fullscreen mode Exit fullscreen mode

Check the result:

$ cat concat_file.txt
file_1 content from user1 with value from file_2: module 2 value
file_2 content from user2 with value from file_1: module 1 value
Enter fullscreen mode Exit fullscreen mode

Done.

Originally published at RTFM: Linux, DevOps, and system administration.


Top comments (0)