Skip to content

Cours - Boucles et expressions conditionnelles dans un langage déclaratif comme Terraform

Le fait que HCL soit un langage destiné à une interprétation permet d’intégrer des appels à des boucles afin de générer par exemple plusieurs instances d’un même objet.

Ce n’est pas courant parmi les langages de configuation qui sont le plus souvent statiques, cf. XML, JSON ou YAML .

Terraform propose plusieurs constructions de bouclage, chacune destinée à être utilisée dans un scénario légèrement différent.

  • Le meta paramètre count : boucler sur les ressources et les modules
  • L’expression for_each : boucler sur les ressources, les blocs en ligne dans une ressource et les modules
  • L’expression for : boucler sur des listes et des maps
  • La directive de chaînes de caractère for : boucler sur les listes et les maps dans une chaîne

Ce méta-argument count peut provisionner dynamiquement plusieurs instances d’un Ressource.

Il prend pour paramètre un nombre entier qui détermine le nombre d’objets désirés.

variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
resource "aws_iam_user" "example" {
count = length(var.user_names)
name = var.user_names[count.index]
}

La syntaxe count.index permet d’accéder à l’entier correspondant à l’itération courante, de {0...N}.

Pour accéder à une instance d’une ressource créée avec count, utilisez la notation crochet [], car il produit logiquement une liste de ressources.

aws_iam_user.example[0]

Il existe plusieurs limitations qui limitent l’usage de count.

  1. Il n’est utilisable que sur des blocks de ressources, et non sur des blocs intérieurs.

  2. Il peut introduire des erreurs en raison de l’index d’itération.

Q: Que se passe-t-il si j’enlève l’utilisateur ayant l’index 1 de la liste ?
R: La valeur de aws_iam_user.example[1] n’est plus la même et Terraform va replanifier en fonction.


Cette expression comparable à count itére sur des listes et des maps pour créer plusieurs copies d’une ressource.

On peut l’utiliser comme count en tant que paramètre de la ressource, en lui fournissant une structure complexe à parcourir.

variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
resource "aws_iam_user" "example" {
for_each = toset(var.user_names)
name = each.value
}

Quelques remarques importantes

  • On utilise la fonction toset pour convertir le type list en type set
  • On utilise la syntaxe each.value pour accéder à la valeur courante du pointeur dans la structure.
  • On peut aussi utiliser each.key pour utiliser la clef d’une map
  • Terraform utilise la chaîne value d’un set ou la key d’une map comme index du conteneur des ressources produites
aws_iam_user.example["neo"]
aws_iam_user.example["trinity"]
aws_iam_user.example["morpheus"]

L’intérêt de for_each est qu’il s’agit d’une expression qui peut être utilisée plus généralement.

Au contraire de count on peut l’utiliser dans toutes sortes de blocs à condition d’utiliser la directive dynamic.

Ici on l’utilise pour générer plusieurs tags définis dans une variable.

variable "
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnets.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
min_size = var.min_size
max_size = var.max_size
tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
dynamic "tag" {
for_each = var.custom_tags
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}

La structure complexe parcourue par une expression for_each doit être connue à l’avance.

On ne peut donc pas utiliser une structure construire sur la base d’informations d’une ressource connues après son obtention.


L’expression for est utilisée pour parcourir et traiter une structure complexe.

Son usage est assez proche de celui des compréhensions de liste en python.

Elle produit des listes et des maps

> [for k,v in [1,3,5] : "${k}:${v}"]
[
"0:1",
"1:3",
"2:5",
]
> {for k,v in ["apple","kiwi","bananas"] : v => k}
{
"apple" = 0
"bananas" = 2
"kiwi" = 1
}

On peut également filtrer les résultats avec un if et utilliser des fonctions.

Terminal window
> [for k,v in ["aba","aca","ada","afa","aga"] : upper("${k}:${v}") if k % 2 == 0]
[
"0:ABA",
"2:ADA",
"4:AGA",
]

La directive de chaînes de caractère for

Section titled “La directive de chaînes de caractère for”

Elle est utile pour créer des contenus de chaînes à partir de structures complexes.

> "%{ for k,v in ["apple","kiwi","bananas"] }${k}:${v}, %{ endfor }"
"0:apple, 1:kiwi, 2:bananas, "

En combinant les conditionals et count on peut désactiver certaines ressources.

resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
count = var.enable_autoscaling ? 1 : 0
scheduled_action_name = "${var.cluster_name}-scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
autoscaling_group_name = aws_autoscaling_group.example.name
}

On peut aussi faire des conditions inverses

resource "aws_iam_user_policy_attachment" "neo_cloudwatch_full_access" {
count = var.give_user_cloudwatch_full_access ? 1 : 0
user = aws_iam_user.the_user.name
policy_arn = aws_iam_policy.cloudwatch_full_access.arn
}
resource "aws_iam_user_policy_attachment" "neo_cloudwatch_read_only" {
count = var.give_user_cloudwatch_full_access ? 0 : 1
user = aws_iam_user.the_user.name
policy_arn = aws_iam_policy.cloudwatch_read_only.arn
}

On peut transformer la structure complexe à parcourir avant de la passer à for_each.

dynamic "tag" {
for_each = {
for key, value in var.custom_tags:
key => upper(value)
if key != "Name"
}
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}

Utiliser des conditions pour la directive de chaîne for

Section titled “Utiliser des conditions pour la directive de chaîne for”

Dans l’exemple précédent, on avait le problème classique de la chaîne terminant par une virgule.

## console
> "%{ for k,v in ["apple","kiwi","bananas"] }${k}:${v}%{ if k < length( ["apple","kiwi","bananas"]) - 1}, %{ endif}%{ endfor }"
"0:apple, 1:kiwi, 2:bananas"