1-03 Concepts de base de Terraform : le langage HCL
Objectifs
- Savoir utiliser le langage utilisé par Terraform
Chlorure d'hydrogène (HCl)
Le chlorure d’hydrogène, de symbole chimique HCl, est un corps composé de chlore et d'hydrogène, incolore, toxique et hautement corrosif. Dans les conditions ambiantes de température et de pression, c'est un gaz qui forme des fumées blanches au contact de l'humidité. Ces fumées sont constituées d'acide chlorhydrique, solution ionique de chlorure d'hydrogène dans l'eau.
HashiCorp Configuration Language (HCL)
Documentation :
- Générale : https://github.com/hashicorp/hcl
- Specs : https://github.com/hashicorp/hcl/blob/main/hclsyntax/spec.md
- Specs agnostiques : https://github.com/hashicorp/hcl/blob/main/spec.md
- Terraform : https://developer.hashicorp.com/terraform/language/
Un langage intermédiaire entre représentation et exécution
- Langage inventé par HashiCorp pour remplacer les langages de configuration plus verbeux comme JSON et XML.
- Équilibre entre la lisibilité humaine et machine
- Influencé par des tentatives antérieures sur le terrain, telles que la configuration libucl et Nginx.
- Compatible avec JSON, ce qui signifie que HCL peut être converti 1:1 en JSON et vice versa.
- Pensé pour être utilisé par des langages typés, en l'occurence Go
Exercice : Transcrire un json => HCL
{
"name": "Jason Ray",
"profession": "Software Engineer",
"age": 31,
"address": {
"city": "New York",
"postalCode": 64780,
"Country": "USA"
},
"languages": ["Java", "Node.js", "JavaScript", "JSON"],
"socialProfiles": [
{
"name": "Twitter",
"link": "https://twitter.com"
},
{
"name": "Facebook",
"link": "https://www.facebook.com"
}
]
}
## We can use comments !
"name" = "Jason Ray"
"profession" = "Software Engineer"
"age" = 31
"address" = {
"city" = "New York"
"postalCode" = 64780
"Country" = "USA"
}
"languages" = ["Java", "Node.js", "JavaScript", "JSON"]
"socialProfiles" = {
"name" = "Twitter"
"link" = "https://twitter.com"
}
"socialProfiles" = {
"name" = "Facebook"
"link" = "https://www.facebook.com"
}
https://www.convertsimple.com/convert-json-to-hcl/
Les composants du langage Terraform
Les attributs
Paires nom/valeur associée à un corps.
Les noms d'attributs sont uniques dans un corps donné.
Les valeurs d'attribut sont fournies sous forme d'expressions.
$ cat terraform.tfvars
htoken = "ht8ZrixBqIdsuaQn"
Les blocs
Structure imbriquée qui a un nom de type, zéro ou plusieurs étiquettes de chaîne (par exemple, des identifiants) et un corps imbriqué.
En général leur contenu est défini par un schéma préalable qui contient :
- un schéma des attributs
- un schéma du bloc d'en-tête (header)
Ensemble, les éléments structurels créent une structure de données hiérarchique, avec des attributs destinés à représenter les propriétés directes d'un objet particulier dans l'application appelante, et des blocs destinés à représenter des objets enfants d'un objet particulier.
$ cat main.tf
variable "base_cidr_block" {
description = "A /16 CIDR range definition, such as 10.1.0.0/16, that the VPC will use"
type = string
default = "10.1.0.0/16"
}
resource "aws_vpc" "main" {
cidr_block = var.base_cidr_block
}
Les structures résultantes sont multi dimensionnelles
dictionary "level_1" "level_2" {
var = "value"
}
En JSON :
{
"dictionary":
{
"level_1":
{
"level_2":
{
"var": "value"
}
}
}
}
Le corps
Conteneur représentant un ensemble de zéro ou plusieurs attributs et un ensemble de zéro ou plusieurs blocs.
Les attributs des schémas sont typés, et le corps a un type "name" par exemple.
L'autre mode est un mode dynamique / sans schéma.
$ cat variables.tf
## Copyright (c) HashiCorp, Inc.
## SPDX-License-Identifier: MPL-2.0
variable "region" {
description = "AWS region"
type = string
default = "us-east-2"
}
Le fichier de configuration
Généralement produit en lisant un fichier sur le disque et en l'analysant comme une syntaxe particulière.
Dans terraform, le fichier de configuration est souvent le résultat d'une concaténation de plusieurs fichiers.
C'est là une des forces du langage qui autorise
- une composition des fichiers
- une surcharge potentielle
Les expressions
Les valeurs d'attribut sont représentées par des expressions.
Selon la syntaxe concrète utilisée, une expression peut n'être qu'une valeur littérale ou elle peut décrire un calcul en termes de valeurs littérales, de variables et de fonctions.
## Basic types
my_absent = null
my_bool = true
my_int = 1024
my_number = 3.1415926535897932384626433832795028841971693993751058
## Strings and templates
my_str = "Something blue"
my_heredoc = <<-EOT
A multiline
string
with indents
EOT
some_string = "My int is ${my_int}"
## Lists and maps
my_list = ["blue", "green"]
some_color = my_list[0]
my_map = {type = "apple", color = "red"}
some_type = my_map["type"]
On peut effectuer des opérations de transtypages sur les variables (ex 0 => "0").
Le langage étant typé, il existe toute une complexité de situations qui sont rares dans l'usage courant de Terraform.
Les références
L'usage de références est courant dans Terraform.
L'usage de référence est ce qu'on a vu plus haut, le référencement d'un objet configuré dans le scope d'exécution.
$ cat main.tf
variable "base_cidr_block" {
description = "A /16 CIDR range definition, such as 10.1.0.0/16, that the VPC will use"
type = string
default = "10.1.0.0/16"
}
resource "aws_vpc" "main" {
cidr_block = var.base_cidr_block
}
Les fonctions
L'usage de fonctions rapprche HCL des langages de templating comme jinja2.
Il existe des dizaines de fonctions dans Terraform
$ terraform console
> keys({a=1, c=2, d=3})
[
"a",
"c",
"d",
]
Elles permettent de filtrer, transformer, augmenter, qualifier, et toutes autres manipulations utiles des données gérées dans le fichier de configuration.
Les opérateurs et les conditions
HCL utilise des les opérateurs pour les
- opérations arithmétiques
{ a = (1+2) }
- opérations de comparaisons
1 >= 2
- opérations logiques
true == false || 1 == 1
HCL utilise aussi la structure ternaire conditionnelle
> { a = true ? 1 : 2 }
Les boucles
Une expression for
permet d'itérer sur une structure pour générer une nouvelle structure.
> [for s in ["foo","bar"] : upper(s)]
> [for k,v in {apple:"red",kiwi:"green"} : "${k} + ${v}"]
> {for k,v in {apple:"red",kiwi:"green"} : v => k}
> {for k,v in {apple:"fruit",kiwi:"fruit"} : v => k... }
Les expressions de type splat simplifient la syntaxe d'accès à des contenus de listes
> [{"count": 32},{"count": 64}][*].count
Un exemple réaliste
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 1.0.4"
}
}
}
variable "aws_region" {}
variable "base_cidr_block" {
description = "A /16 CIDR range definition, such as 10.1.0.0/16, that the VPC will use"
type = string
default = "10.1.0.0/16"
}
variable "availability_zones" {
description = "A list of availability zones in which to create subnets"
type = list(string)
default = ["us-east-2","eu-west-3"]
}
provider "aws" {
region = var.aws_region
}
resource "aws_vpc" "main" {
# Referencing the base_cidr_block variable allows the network address
# to be changed without modifying the configuration.
cidr_block = var.base_cidr_block
}
resource "aws_subnet" "az" {
# Create one subnet for each given availability zone.
count = length(var.availability_zones)
# For each subnet, use one of the specified availability zones.
availability_zone = var.availability_zones[count.index]
# By referencing the aws_vpc.main object, Terraform knows that the subnet
# must be created only after the VPC is created.
vpc_id = aws_vpc.main.id
# Built-in functions and operators can be used for simple transformations of
# values, such as computing a subnet address. Here we create a /20 prefix for
# each subnet, using consecutive addresses for each availability zone,
# such as 10.1.16.0/20 .
cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 4, count.index+1)
}
On peut agir dans le terminal avec cette recette
$ cd TH-01-HCL
$ terraform init
$ terraform console
> var.base_cidr_block
"10.1.0.0/16"
Rappel des objectifs
- Savoir utiliser le langage utilisé par Terraform