Variables and locals are typed data There are three primitive data types
- string: A Unicode string
- numeric: Used for both integral and non-integral values (434 and 1.34)
- boolean:
trueandfalse - null: a value that represents absence or omission. If an argument of a resource is
null, evaluation of the the argument behaves as though had been completely omitted it.- A variable will use the argument's default value if it has one, or raise an error if the argument is mandatory.
For example, we could have the following locals definitions
locals {
name = "App Server"
port = 8080
private = true
}Type Safety
- Error Checking: Allows type checking during plan and apply phases to catch errors early if an incorrect data type is passed to a parameter expecting a specific type
- Predictable Behavior: Ensures that operations on these types behave as expected such as mathematical operations are meaningful with numeric types
Clarity and Intent
- Clear Intent: Using the appropriate data type makes the intention of the code clearer to anyone reading it
- Simplified Syntax: Numeric and boolean types allow for a more straightforward and concise syntax.
Efficiency
- Optimized Performance: Operations on numeric and boolean types are generally more efficient than their string equivalents.
- Resource Planning: Terraform directives that are conditional or need some form of counting work best with boolean and numeric yptes
- Validation: Stricter validation rules can be enforced on on input variables and configuration parameters when types are explicitly defined
- Terraform Functions: Terraform includes built-in functions that operate specifically on certain types
Terraform haa "heredoc" string literal type which allows multi-line strings to be expressed clearly
A heredoc string consists of:
- An opening sequence consisting of:
- A heredoc marker (<< or <<- — two less-than signs, with an optional hyphen for indented heredocs)
- A delimiter word of your own choosing
- A line break
- The contents of the string, which can span any number of lines
- The delimiter word you chose, alone on its own line (with indentation allowed for indented heredocs)
Example
message = <--MSRT
this is the body of the message
and more stuff
MSRTThese are often useful for multiline directives like start up scripts.
These are similar to the equivalent types in most programming languages
list (or tuple): a sequence of values, like ["alpha", "bext"] indexed starting a 0/
set: a collection of unique values with no specific ordering.
map (or object): a group of values identified by named labels, like {name = "Server", port = 80}
We will explore data types and operations on them in more detail later on.
Declarative languages, like Terraform, normally do not have typical programming constructs like loops and conditional statements
However, there are many scenarios that require the conditional configuration of resources
- For example, creating a module that creates resources only for certain users and not others
- Or creating different sizes of VMs depending on the environment we are deploying into - a small one for "dev", two medium-sized ones for "test" and four large ones for "prod"
Terraform directives allow certain kinds of operations to enable dynamic and conditional configuration
- While these constructs behave in analogous ways to the constructs in programming languages, they do look syntactically different
Terraform has several loop constructs to provide looping functionality in different scenarios
- count parameter: to loop over resources
- for_each expressions: to loop over resources and inline blocks within a functionality
- for expressions: to loop over lists and maps
- for string directive: to loop over lists and maps withing a string
The looping procedural code is implied and generated under the hood by terraform
We specify the number of iterations with the count, which often represents the number of copies of a resource
The following code creates three instances with the names VM-0 , VM-1 and VM-2
It also goes through a list of owners and assigns each owner to a machine. First in the variables.tf file, the list of owners is defined.
variable "server_owners" {
description = "List of server owners"
type = list(string)
}Notice that the data type is a list of strings and is assigned a value in the terraform.tfvars file
ami_type = "ami-080e1f13689e07408"
inst_type = "t2.nano"
server_owners = ["accounting","marketing","R&D"]in the main.tf file, the VM definition has the count directive set to 3. This means that there will be three copies of the VM created. For each iteration the value count.index contains the numeric current value of count
resource "aws_instance" "the_servers" {
count = 3
instance_type = var.inst_type
ami = var.ami_type
tags = {
owner = var.server_owners[count.index]
Name = "VM-${count.index}"
}
}Running this code with terraform apply produces three servers.
aws_instance.the_servers[1]: Creating...
aws_instance.the_servers[0]: Creating...
aws_instance.the_servers[2]: Creating...Because we defined three copies of the server with the Terraform name the_servers, we actually created a list of servers called [the_servers[0], the_servers[1], the_servers[2]]
To see this, we can output tags of the second server.
output "marketin_server_tag" {
value = aws_instance.the_servers[1].tags
} And when we run terraform apply and ask for the tags, we get back a map of the tags.
Outputs:
marketin_server_tag = tomap({
"Name" = "VM-1"
"owner" = "marketing"
})Conceptually, you can think of the the_servers directive being inside a for loop with a loop index count that starts at 0 and does three interations.