EthicsComputerCourses
Join training +91 90532 10052

HCL Syntax: Blocks, Expressions & Functions

Terraform & Ansible · Lesson 5 of 29 Intermediate 6 min read Updated

By the end of this lesson you will be able to read any Terraform file and know which parts are blocks, arguments and expressions. You will also try types, conditions, for expressions and built-in functions in terraform console, a safe playground that changes nothing.

Blocks and arguments

HCL, the HashiCorp Configuration Language, has only two main building pieces.

A block is a container with a type, zero or more labels, and a body in curly braces. An argument is a name = value line inside a block.

HCL (Terraform)
resource "local_file" "notice" {    # block type, then two labels
  filename = "notice.txt"           # argument
  content  = "Exam on Monday"       # argument

  lifecycle {                       # a nested block, no labels
    prevent_destroy = false
  }
}

Think of a block as a form at a bank counter. The block type is the form name ("Account Opening"), the labels fill the top boxes, and the arguments are the fields inside.

A few rules to remember:

  • Names (called identifiers) may contain letters, digits, underscores and hyphens, and must not start with a digit.
  • Comments start with # or // for one line, or sit between /* and */ for many lines. # is the usual style.
  • The order of blocks does not matter. Terraform works out the order from references.
  • Run terraform fmt to line up the = signs and fix indentation. Never argue about spacing.

Types of values

Every value in HCL has a type:

TypeExampleMeaning
string"Kochi"Text in double quotes
number42, 3.5Whole or decimal numbers
booltrue, falseYes or no
list / tuple["pune", "agra"]Ordered values, counted from 0
map / object{ city = "Pune", pin = 411001 }Named values
settoset(["a", "b"])Unique values with no order
nullnull"No value"; the argument is treated as not set

Lists use square brackets; maps and objects use curly braces. In a map you can separate items with new lines or commas.

Strings, templates and heredocs

Inside double quotes, ${ ... } inserts the value of an expression. This is called interpolation:

HCL (Terraform)
content = "Hello ${var.student_name}, your roll number is ${var.roll_no}."

For text with many lines, use a heredoc. <<-EOT starts it and a line with only EOT ends it. The - lets you indent the text neatly, and Terraform removes the common indentation:

HCL (Terraform)
content = <<-EOT
  [server]
  city = Lucknow
  port = 8080
EOT

EOT is only a convention; any word works as long as the start and end match.

References

An expression can refer to other values by name. You will use these forms constantly:

  • var.region: an input variable.
  • local.common_tags: a local value.
  • local_file.welcome.filename: an attribute of a resource (type, name, attribute).
  • data.http.ip.response_body: an attribute of a data source.
  • module.network.vpc_id: an output of a module.
  • path.module: the folder of the current file.

When one resource refers to another, Terraform learns that it must create the first one before the second. This is how it builds its dependency graph, the order of work, without you writing any steps.

Try expressions in terraform console

terraform console opens an interactive prompt where you can test expressions. Run it in any folder that has been through terraform init. Nothing is created or changed.

Step 1: start the console and try simple functions.

Terminal
terraform console
> upper("rohtak")
> length(["pune", "agra", "kochi"])
> join(", ", ["Pune", "Agra"])
> 75 >= 33 ? "pass" : "fail"
Output
"ROHTAK"
3
"Pune, Agra"
"pass"

Strings print in quotes and numbers print plainly. The last line is a conditional expression: condition ? value_if_true : value_if_false. Here a mark of 75 passes the cut-off of 33.

Step 2: try collections and a for expression.

Terminal
> split(",", "Pune,Agra")
> [for c in ["pune", "agra"] : upper(c)]
> {for c in ["pune", "agra"] : c => length(c)}
> cidrsubnet("10.0.0.0/16", 8, 2)
Output
tolist([
  "Pune",
  "Agra",
])
[
  "PUNE",
  "AGRA",
]
{
  "agra" = 4
  "pune" = 4
}
"10.0.2.0/24"

Notice what you see:

  • split returns a list, which the console shows wrapped in tolist(...).
  • A for expression in square brackets builds a list from another collection, like a teacher reading out each name and writing it in capitals.
  • A for expression in curly braces with => builds a map. Map keys are shown in alphabetical order.
  • cidrsubnet cuts a network range into smaller ones. You will use it for cloud subnets.

Type exit (or press Ctrl+D) to leave the console.

Useful built-in functions

Terraform has many built-in functions. You cannot write your own in plain HCL, but newer providers can offer extra ones. These cover most daily work:

FunctionWhat it doesExample result
lower, upperChange caselower("PUNE") is "pune"
join, splitList to string and backjoin("-", ["a", "b"]) is "a-b"
lengthCount items or characterslength("kochi") is 5
mergeCombine mapsUsed for tags
lookupRead a map key with a defaultlookup(m, "env", "dev")
coalesceFirst value that is not null or emptyFallback values
file, templatefileRead a file, or fill a template fileStart-up scripts, configs
tryFirst expression that does not errorOptional nested values
toset, tolist, tomapConvert typesPreparing for_each input

A splat expression is a short form of a for expression. aws_instance.web[*].public_ip means "the public_ip of every instance in the list".

Common mistakes

  • Single quotes. HCL strings need double quotes. 'Pune' is an error.
  • Commas between arguments. Arguments in a block are on separate lines with no commas.
  • Mixing up lists and maps. ["a", "b"] has no keys; { a = 1 } has no order.
  • Forgetting ${ } inside text. "Hello var.name" prints the words literally; write "Hello ${var.name}".

Key points

  • HCL is made of blocks (type, labels, body) and arguments (name = value).
  • Values have types: string, number, bool, list, map, object, set and null.
  • ${ } interpolates values into strings; <<-EOT heredocs hold many lines.
  • References such as var.x and local_file.a.filename also create the dependency order.
  • Use conditionals, for expressions, splats and built-in functions instead of copying code.
  • terraform console is a safe place to test any expression.

Exercise

In terraform console, write one expression that takes the list ["rohan", "isha", "farhan"] and builds a map from each name to a greeting such as "Hello ROHAN". Then write a conditional that returns "senior" when length(["rohan", "isha", "farhan"]) is more than 2, and "junior" otherwise.

Show solution
Terminal
> {for n in ["rohan", "isha", "farhan"] : n => "Hello ${upper(n)}"}
> length(["rohan", "isha", "farhan"]) > 2 ? "senior" : "junior"
Output
{
  "farhan" = "Hello FARHAN"
  "isha" = "Hello ISHA"
  "rohan" = "Hello ROHAN"
}
"senior"

The curly-brace for expression with => builds a map, interpolation inserts the upper-case name, and the conditional checks the list length.

Quick Quiz

1. Which line is valid HCL inside a resource block?

2. What does [for c in var.cities : upper(c)] produce?

3. How does Terraform decide the order in which to create resources?

Learn Terraform & Ansible with a mentor

Free lessons get you started. Our Cloud & DevOps training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call