Eventually, I got to the modules in Terraform.
Namely, I had to figure out how to transfer the values of the variables between 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.
Contents
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:
[simterm]
$ mkdir modules_example $ cd modules_example/
[/simterm]
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" }
Run terraform init
to pull up the necessary modules of Terraform itself:
[simterm]
$ 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) ... Terraform has been successfully initialized!
[/simterm]
Then, run terraform plan
to check whether it will work at all, and what exactly it will do:
[simterm]
$ 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.
[/simterm]
If it looks OK, run terraform apply
:
[simterm]
$ 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.
[/simterm]
And check the contents of the directory where we created the main.tf
file and launched the commands:
[simterm]
$ 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
[/simterm]
And the file.txt
content:
[simterm]
$ cat file.txt file content
[/simterm]
It works! let’s go further.
Terraform Modules
Next, let’s go to the modules.
Create two directories for two modules:
[simterm]
$ mkdir -p modules/file_1 $ mkdir -p modules/file_2
[/simterm]
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" }
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" }
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" }
Run init
again so that Terraform creates its modules structure:
[simterm]
$ 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!
[/simterm]
Check the .terraform/modules/
:
[simterm]
$ 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" } ] }
[/simterm]
Run plan
:
[simterm]
$ 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.
[/simterm]
And now we can perform the apply
.
In order not to enter “yes” every time, we can use the -auto-approve
argument:
[simterm]
$ 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.
[/simterm]
Check whether the files have appeared:
[simterm]
$ 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
[/simterm]
And their content:
[simterm]
$ cat file_1.txt file_1 content $ cat file_2.txt file_2 content
[/simterm]
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" }
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" }
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" }
Apply the changes:
[simterm]
$ 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.
[/simterm]
And the result check:
[simterm]
$ cat file_1.txt file_1 content from user1
[/simterm]
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 the outputs
. See Output Values documentation.
Update the first module – add the output
with 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") }
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") }
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" }
Run apply
:
[simterm]
$ 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.
[/simterm]
Check the result:
[simterm]
$ cat concat_file.txt file_1 content from user1 file_2 content from user2
[/simterm]
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 the default value “module 1 value“:
... variable "module_1_value" { type = string default = "module 1 value" } ...
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 } ...
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 } ...
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 }
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 }
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" }
Now in the file file_1.txt
we have to get the value from the module "file_2"
, and vice versa.
Apply:
[simterm]
$ 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.
[/simterm]
Check the result:
[simterm]
$ 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
[/simterm]
Done.