Terraform: modules, Outputs, and Variables
Tags: devops, todayilearned, tutorial
Published: 2022-12-12 16:38:21

---
title: Terraform: modules, Outputs, and Variables
published: true
date: 2022-12-11 06:51:42 UTC
tags: devops,todayilearned,tutorial
canonical_url: https://rtfm.co.ua/en/terraform-modules-outputs-and-variables/
---

![](https://cdn-images-1.medium.com/max/1024/1*oqPYm4BTtIUwGlFEkr9RkQ.png)

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](https://developer.hashicorp.com/terraform/language/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/
```

In this directory add a file `main.tf` with the [`resource`](https://developer.hashicorp.com/terraform/language/resources) of the [`local_file`](https://registry.terraform.io/providers/hashicorp/local/latest/docs/data-sources/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`](https://developer.hashicorp.com/terraform/cli/commands/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)
…
```

Terraform has been successfully initialized!

Then, run [`terraform plan`](https://developer.hashicorp.com/terraform/cli/commands/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.
```

If it looks OK, run the [`terraform apply`](https://developer.hashicorp.com/terraform/cli/commands/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.
```

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
```

And the `file.txt` content:

```
$ cat file.txt
file content
```

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
```

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 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!
```

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”
}
]
}
```

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.
```

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.
```

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
```

And their content:

```
$ cat file_1.txt
file_1 content
cat file_2.txt
file_2 content
```

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](https://developer.hashicorp.com/terraform/language/values/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:

```
$ 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.
```

And the result check:

```
$ cat file_1.txt
file_1 content from user1
```

### 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](https://developer.hashicorp.com/terraform/language/values/outputs) ​​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")
}
```

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:

```
$ 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.
```

Check the result:

```
$ cat concat_file.txt
file_1 content from user1
file_2 content from user2
```

### 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"
}
...
```

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:

```
$ 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.
```

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
```

Done.

_Originally published at_ [_RTFM: Linux, DevOps, and system administration_](https://rtfm.co.ua/en/terraform-modules-outputs-and-variables/)_._

* * *