Project example#

Let’s go into an example of how a module works. For this example, the code and directory structure will be listed, as you need to have a nested folder for a module.

Folder structure#

Folder structure

Code#

This code requires the following environment variables to execute:
access_key_id
Not Specified...
secret_access_key
Not Specified...
/
sqs-with-backoff
main.tf
Terraform module code example

We have written quite a lot of code. Let’s dive into it piece by piece and explain what it is all doing.

  • In a module, you can take arguments.

    This allows you to give the user a chance to specify things about this instance of a module.

  • The module that we have written creates two AWS SQS queues.

    One of the queues is a dead letter queue of the other.

  • For our module, we allow the user to specify the name of the queue.

    We do this by defining the variable queue_name.

variables.tf#

Variables have a special meaning when used with a module; they become the input values for your module. Note that inside a module, Terraform does not care what the file names are as long as they end in .tf.

However, there is a convention where variables go in a file called variables.tf, so we will stick with that. As the queue_name variable does not have a default, a value must be given when the module is used. Variables in modules act in the same way as they do at the top level (which we learned in the Variables chapter). If you do not give a default value, you have to provide a value for the variable when using the module.

As queue_name does not have a default, it is a required parameter that must be set when using this module. The other two variables we have defined, max_receive_count and visibility_timeout, have defaults so they do not have to be given values when using the module.

main.tf#

Next, let’s look at the main.tf file inside the module. You can see that we use the passed-in variables by using the var.<variable_name> syntax.

The other interesting thing about the code is that we set the redrive_policy of the first queue to have a dead letter queue of the second queue.

We do this by using the interpolation syntax we learned earlier. This allows Terraform to dynamically put the arn from the second queue in the redrive policy when it creates the first queue.

output.tf#

The last file that makes up our module is output.tf. This is where our outputs (or return values) from the module are specified. It is optional to return values out of a module, but most modules do return values to make them easier to use. Outputs are defined using the output blocks that we learned back in the Outputs chapter. Returned values in a module can be used in the main Terraform project.

Top level main.tf#

If we look at the top-level main.tf file, we can see how we create an instance of a module:

Top level main.tf module block

Creating instance of a module#

To create an instance of a module, we start with the keyword module. This is followed by the identifier you want to refer to that instance of the module. You then surround the module block in { and }. Inside the module block, every module has a property called source. The source property is where the code is for the module.

You can see that we are using the local path ./sqs-with-backoff. This tells Terraform that it can find the code for this module in a local folder with that name. We then give a value of work-queue to the queue_name property.

Note that we are not specifying the max_receive_count or visibility_timeout. We do not need to, as they both have default values. If we wanted to, we could provide values for them, which would be used instead of the defaults.

Referencing the value returned by module#

At the bottom of the main.tf, we output the names of the two queues created in the module. This works because we referenced the values returned by the module. To reference a value returned by a module, you use the following syntax: module.<module_identifier>.<output_name>. So, to reference the value of the main queue, you would use module.work_queue.queue_name. The keyword module is constant.

Running the project#

Clicking the run button will run terraform init and then terraform apply. When prompted, say yes if you want to run it in the project. You will see that two AWS SQS queues are created. One is a dead letter queue of the other and the queue names are printed to the console when Terraform finishes running.

Modules’ real power#

The real power of modules is that they allow you to place logic in a single place and then reuse it across your Terraform project. You can have multiple instances of a module in a single Terraform project. Add the following code to the top-level main.tf in your project:

Adding module block to top-level main.tf file

Above, we define the second instance of the same module. If you run the project now, you will see that Terraform creates two more queues, one being a dead letter of the second.

This is a good way to write code, as it allows us to change how our queues are constructed only once and apply that change to the whole of your project by running terraform apply.

For example, try changing the max_message_size property on one of the queues inside the module and run Terraform again. You will see that both queues are updated.

If you had written this code with all of the SQS resources in the top-level Terraform project, you would have had four Terraform resources defined (one for each queue). If you wanted to make a change, you would have had to update each resource with that change. This approach would quickly become unmanageable if you had tens or even hundreds of instances of your module.

Define structure using modules#

Using modules also allows you to define a structure for how items should look. In our example, we prefixed our queue names with awesome_co-. This is a great technique if you want to name all of your queues in a certain way. You could optionally take a variable that is the name of the environment. The module will prefix the name of the environment onto the queue name so the queue names will not clash.

Modules Introduction
Returning a Complex Type From a Module
Mark as Completed
Report an Issue