Skip to main content

HCL syntax


Chapter objectives

  • Understand the HCL syntax
  • Master the data types
  • Use expressions and functions
  • Write clean HCL code

1 - Introduction to HCL

What is HCL?

HCL (HashiCorp Configuration Language) is a declarative language designed to be:

  • Human-readable
  • Easily editable
  • JSON-compatible

Basic structure

# Commentaire sur une ligne

/* Commentaire
sur plusieurs
lignes */

# Bloc avec arguments
type "label" "name" {
argument1 = "valeur"
argument2 = 123

# Bloc imbriqué
nested_block {
nested_arg = true
}
}

2 - Types of blocks

Resource

# Crée une ressource dans le cloud
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"

tags = {
Name = "WebServer"
}
}

Variable

# Déclare une variable d'entrée
variable "instance_type" {
description = "Type d'instance EC2"
type = string
default = "t2.micro"
}

Output

# Exporte une valeur
output "instance_ip" {
description = "IP publique de l'instance"
value = aws_instance.web.public_ip
}

Provider

# Configure un provider
provider "aws" {
region = "eu-west-1"
}

Locals

# Définit des valeurs locales
locals {
environment = "production"
common_tags = {
Environment = local.environment
Project = "MyProject"
}
}

3 - Data types

Primitive types

# String
name = "hello"
message = "Hello, ${var.name}!"

# Number
count = 42
price = 19.99

# Bool
enabled = true
disabled = false

Complex types

# List (ordered collection)
availability_zones = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]

# Map (key-value pairs)
tags = {
Name = "WebServer"
Environment = "Production"
}

# Set (unique values, no order)
unique_ports = toset([80, 443, 8080])

# Object (structured type)
server_config = {
name = "web-01"
cpu = 4
memory = 8
}

# Tuple (fixed-length list with specific types)
mixed = ["hello", 42, true]

Type declaration

variable "string_var" {
type = string
}

variable "number_var" {
type = number
}

variable "bool_var" {
type = bool
}

variable "list_var" {
type = list(string)
}

variable "map_var" {
type = map(string)
}

variable "object_var" {
type = object({
name = string
age = number
enabled = bool
})
}

variable "any_var" {
type = any
}

4 - References and expressions

Reference resources

# Référence simple
resource "aws_instance" "web" {
ami = "ami-12345678"
instance_type = "t2.micro"
subnet_id = aws_subnet.main.id # Référence
}

resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
}

Types of references

# Ressource
aws_instance.web.id
aws_instance.web.public_ip

# Variable
var.instance_type
var.environment

# Local
local.common_tags
local.environment

# Data source
data.aws_ami.ubuntu.id

# Module output
module.vpc.vpc_id

# Provider
provider.aws

Interpolation

# Interpolation de variables
name = "server-${var.environment}-${count.index}"

# Interpolation conditionnelle
instance_type = var.environment == "production" ? "t2.large" : "t2.micro"

# Interpolation avec fonctions
bucket_name = lower("MyBucket-${random_id.bucket.hex}")

5 - Operators

Arithmetic

# Addition, soustraction, multiplication, division
total = 10 + 5 # 15
diff = 10 - 5 # 5
prod = 10 * 5 # 50
quot = 10 / 5 # 2
mod = 10 % 3 # 1 (modulo)
neg = -10 # -10

Comparison

# Égalité et inégalité
equal = var.env == "prod"
not_equal = var.env != "dev"

# Comparaison numérique
greater = 10 > 5
less = 5 < 10
gte = 10 >= 10
lte = 5 <= 10

Logical

# AND, OR, NOT
both = var.enabled && var.ready
either = var.primary || var.secondary
negate = !var.disabled

6 - Conditional expressions

Ternary

# condition ? true_value : false_value
instance_type = var.environment == "production" ? "t2.large" : "t2.micro"

# Imbriqué
size = var.env == "prod" ? "large" : (var.env == "staging" ? "medium" : "small")

With count

resource "aws_instance" "web" {
count = var.create_instance ? 1 : 0

ami = "ami-12345678"
instance_type = "t2.micro"
}

With for_each

resource "aws_instance" "web" {
for_each = var.create_instances ? var.instances : {}

ami = each.value.ami
instance_type = each.value.type
}

7 - Loops and iterations

for expression

# Transformer une liste
upper_names = [for name in var.names : upper(name)]
# ["alice", "bob"] -> ["ALICE", "BOB"]

# Filtrer une liste
adults = [for p in var.people : p.name if p.age >= 18]

# Créer une map depuis une liste
name_map = {for s in var.servers : s.id => s.name}
# [{id="1", name="web"}] -> {"1" = "web"}

# Transformer une map
upper_tags = {for k, v in var.tags : k => upper(v)}

count

resource "aws_instance" "web" {
count = 3

ami = "ami-12345678"
instance_type = "t2.micro"

tags = {
Name = "web-${count.index}" # web-0, web-1, web-2
}
}

# Accéder aux instances
output "instance_ids" {
value = aws_instance.web[*].id
}

for_each

# Avec un set
resource "aws_iam_user" "users" {
for_each = toset(["alice", "bob", "charlie"])

name = each.value
}

# Avec une map
resource "aws_instance" "servers" {
for_each = {
web = { type = "t2.micro", ami = "ami-web" }
api = { type = "t2.small", ami = "ami-api" }
db = { type = "t2.medium", ami = "ami-db" }
}

ami = each.value.ami
instance_type = each.value.type

tags = {
Name = each.key
}
}

8 - Built-in functions

String functions

# Manipulation de strings
lower("HELLO") # "hello"
upper("hello") # "HELLO"
title("hello world") # "Hello World"
trim(" hello ") # "hello"
trimprefix("helloworld", "hello") # "world"
trimsuffix("helloworld", "world") # "hello"
replace("hello", "l", "x") # "hexxo"
split(",", "a,b,c") # ["a", "b", "c"]
join("-", ["a", "b", "c"]) # "a-b-c"
format("Hello, %s!", "World") # "Hello, World!"
substr("hello", 0, 3) # "hel"

Numeric functions

abs(-5)           # 5
ceil(4.2) # 5
floor(4.8) # 4
max(1, 5, 3) # 5
min(1, 5, 3) # 1
pow(2, 3) # 8
signum(-5) # -1

Collection functions

# Listes
length([1, 2, 3]) # 3
element(["a", "b", "c"], 1) # "b"
index(["a", "b", "c"], "b") # 1
contains(["a", "b"], "a") # true
concat([1, 2], [3, 4]) # [1, 2, 3, 4]
flatten([[1, 2], [3, 4]]) # [1, 2, 3, 4]
distinct([1, 1, 2, 2, 3]) # [1, 2, 3]
reverse([1, 2, 3]) # [3, 2, 1]
sort(["c", "a", "b"]) # ["a", "b", "c"]
slice([1, 2, 3, 4], 1, 3) # [2, 3]
range(1, 5) # [1, 2, 3, 4]

# Maps
keys({a = 1, b = 2}) # ["a", "b"]
values({a = 1, b = 2}) # [1, 2]
lookup({a = 1}, "a", 0) # 1
lookup({a = 1}, "b", 0) # 0
merge({a = 1}, {b = 2}) # {a = 1, b = 2}

File functions

file("script.sh")                 # Contenu du fichier
fileexists("config.yaml") # true/false
filebase64("image.png") # Base64 du fichier
templatefile("template.tpl", {
name = "World"
}) # Fichier rendu

Type functions

tostring(123)                     # "123"
tonumber("123") # 123
tobool("true") # true
tolist(toset(["a", "b"])) # ["a", "b"]
tomap({a = 1}) # {a = 1}
toset([1, 1, 2]) # toset([1, 2])
try(var.value, "default") # var.value ou "default"
can(var.value.nested) # true si accessible

9 - Dynamic blocks

Syntax

resource "aws_security_group" "example" {
name = "example"

dynamic "ingress" {
for_each = var.ingress_rules
content {
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
}

Associated variable

variable "ingress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_blocks = list(string)
}))

default = [
{
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
},
{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
]
}

10 - HCL best practices

Formatting

# Formater automatiquement
terraform fmt

# Vérifier le formatage
terraform fmt -check

Naming

# ✅ Bon - snake_case
resource "aws_instance" "web_server" {}
variable "instance_count" {}

# ❌ Mauvais
resource "aws_instance" "WebServer" {}
variable "instanceCount" {}

Organization

# ✅ Un fichier par type
# variables.tf - toutes les variables
# outputs.tf - tous les outputs
# main.tf - ressources principales

# ✅ Grouper les ressources liées
# networking.tf - VPC, subnets, routes
# compute.tf - EC2, ASG
# database.tf - RDS, ElastiCache

Summary

Key points
  • HCL is declarative and readable
  • Use the appropriate types for validation
  • for expressions enable powerful transformations
  • count and for_each to create multiple resources
  • Always format with terraform fmt

Practical exercises

  1. Create variables of different types
  2. Use a for expression to transform a list
  3. Create resources with for_each
  4. Explore the string functions

← Installation | Providers →