DEV Community

Arseny Zinchenko
Arseny Zinchenko

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

1 1

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.


Heroku

Simplify your DevOps and maximize your time.

Since 2007, Heroku has been the go-to platform for developers as it monitors uptime, performance, and infrastructure concerns, allowing you to focus on writing code.

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay