Terraform 101 - Part 3/3: Modules, Built-in Functions, Type Constraints, and Dynamic Blocks | By Ali KHYAR
Published: 2022-10-08 02:32:09

Part 1/3: History, Workflow, and Resource Addressing: [link here](https://dev.to/alikhyar/terraform-101-part-13-history-workflow-and-resource-addressing-by-ali-khyar-4m23)
Part 2/3: State, Variables, Outputs, and provision: [link here](https://dev.to/alikhyar/terraform-101-part-23-state-variables-outputs-and-provisioners-by-ali-khyar-lno)

---

## Modules:

simply, and without complexity, a module is a container of many resources that are used together to help avoid reinventing the wheel. Modules can take inputs (optional) and returns outputs(optional).
One module you interacted with is the root module which embodies the code files from the main working directory. When calling modules from another one, the called modules are considered children modules.
Modules can be downloaded and be called from:

1. Terraform public registry: which contains collections of publicly available modules, that get downloaded (when referencing them) into a hidden folder on your local system.
2. Private registry: you probably will go with this for closed source code or security reasons.
3. Local system: when you have modules folders saved on your local system, either in the configuration code folder or elsewhere, and then reference them using an absolute or relative path.

Let's look at the snippet below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c4nnu9jjg5xsoe2hog92.png)


Defining modules requires using a reserved keyword which is module followed by the name of that module, in the example above is vpc_module, the main two parameters that should be inside every module are:

- source: path to module folder.
- version: a best practice to always keep track of the module's version, so you can avoid any unwanted side effects when deploying/redeploying the resource.

Other allowed parameters in modules are:

- **built-in functions** like max, count, tolist, for_each…. which we will discuss later.
- **providers:** which bind the module to a certain provider.
- **depends_on: **setup dependencies.

As mentioned before, modules can optionally take inputs and return outputs, which are defined in the modules output block, and can be referenced like in the snippet below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a560fkfav1jvh7dby4lm.png)


Looking at the snippet above, you should know that an output named **subnet_id** is defined inside **vpc-module** module:
When referencing module outputs we always start with module keyword.
In the already seen snippet:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/arpnimytwbxs1x5nmat5.png)


we have **region** which is considered as input for this module, it is arbitrarily named because we only define it in module resource to call it later in the module's code using the following syntax: var.region

## Built-In Functions:

Bilt-in functions are expressions that allow you to get a value from somewhere, transform it, evaluate it or convert it. Users cannot define custom functions but the list of the already defined functions is extensive.
Calling built-in functions in Terraform is like calling functions in any programming language: **funcName(arg1, arg2, … )**, let's take a look at the **join** function which produces a string by concatenating together all elements of a given list of strings with the given delimiter:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tl7xvh4o2m52ntmob7os.png)


The delimiter in the above snippet is a hyphen - and the element between brackets are strings that we will concatenate, which will result in the string **my-project-name-preprod** .
Terraform happily provides a console to test things such as built-in functions and expressions, to access it use the command:`terraform console
`

## Type Constraints:

So far, we have seen primitive types like string, number, and boolean, which controls the type of given variable values.
Another type of variable is the Complex type, which is created by combining multiple types, example of complex types are list, tuple, map, and object. The complex type itself can be divided into two types;

- Collection: multiple values of one primitive type grouped together against a variable, example:
`list(type)
map(type)
set(type)`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/anzyxwy28eq8fuhbtvii.png)


- Structural: multiple values of different primitive types grouped together.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8rspkvbfbq57yeg5ybkx.png)


Another constraint type is the `any` constraint which serves as a placeholder for a primitive type not decided yet.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xihzise4utbtpczk2f7.png)


terraform does its best to know which primitive type to assign to any, in the example above it will assign the string type to it.

## Dynamic Blocks:

Dynamic blocks allow the construction of repeatable nested configuration blocks inside the following Terraform blocks: resource, data, provisioner, and provider.
imagine the following scenario, where you need to create a security group that contains many rules:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pfd3pi6xa9agnoj0fp3i.png)


as the ingress rules add up, the security group will be hard to manage, and the code doesn't look beautiful as well, a way to clean the above code is by using the dynamic blocks. First, we can extract the data from ingress blocks into one variable that looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x7rowsl45hon7vomk5al.png)


then by using the following snippet:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/51rrm8gnjhdjlj9h6dce.png)


we tell Terraform using the **dynamic** keyword what block we want to replicate, which is in this case **ingress**. then we assign our variable to loop through; and for **content**, Terraform implicitly provides an **ingress** object which we access its values with the **value** keyword. The object name matches the dynamic argument **ingress**.

---

## Conclusion:

Hope this blog gave you an idea of how modules, built-in functions, type constraints, and dynamic blocks work. In the next article, we're going to look at some hacks and tricks that can be used in Terraform.