azure-verified-modules

Par hashicorp · agent-skills

Exigences et bonnes pratiques d'Azure Verified Modules (AVM) pour le développement de modules Azure Terraform certifiés. À utiliser lors de la création ou de la révision de modules Azure nécessitant une certification AVM.

npx skills add https://github.com/hashicorp/agent-skills --skill azure-verified-modules

Exigences pour Azure Verified Modules (AVM)

Ce guide couvre les exigences obligatoires pour la certification Azure Verified Modules. Ces exigences garantissent la cohérence, la qualité et la maintenabilité sur les modules Terraform Azure.

Références:

Table des matières


Références croisées de modules

Gravité: MUST | Exigence: TFFR1

Lors de la création de modules Resource ou Pattern, les propriétaires de modules MAY faire des références croisées à d'autres modules. Cependant:

  • Les modules MUST être référencés en utilisant la référence du registre HashiCorp Terraform avec une version épinglée
    • Exemple: source = "Azure/xxx/azurerm" avec version = "1.2.3"
  • Les modules MUST NOT utiliser de références git (par ex., git::https://xxx.yyy/xxx.git ou github.com/xxx/yyy)
  • Les modules MUST NOT contenir de références à des modules non-AVM

Exigences du fournisseur Azure

Gravité: MUST | Exigence: TFFR3

Les auteurs MUST utiliser uniquement les fournisseurs Azure suivants:

Fournisseur Version min Version max
azapi >= 2.0 < 3.0
azurerm >= 4.0 < 5.0

Exigences:

  • Les auteurs MAY choisir soit Azurerm, Azapi, ou les deux fournisseurs
  • MUST utiliser le bloc required_providers pour appliquer les versions des fournisseurs
  • SHOULD utiliser l'opérateur de contrainte de version pessimiste (~>)

Exemple:

terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
    azapi = {
      source  = "Azure/azapi"
      version = "~> 2.0"
    }
  }
}

Normes de style de code

Casse snake_case minuscule

Gravité: MUST | Exigence: TFNFR4

MUST utiliser la casse snake_case minuscule pour:

  • Locals
  • Variables
  • Outputs
  • Ressources (noms symboliques)
  • Modules (noms symboliques)

Exemple: snake_casing_example

Ordre des ressources et sources de données

Gravité: SHOULD | Exigence: TFNFR6

  • Les ressources dépendantes SHOULD venir en premier
  • Les ressources avec dépendances SHOULD être définies à proximité les unes des autres

Utilisation de count et for_each

Gravité: MUST | Exigence: TFNFR7

  • Utiliser count pour la création conditionnelle de ressources
  • MUST utiliser map(xxx) ou set(xxx) comme collection for_each de la ressource
  • La clé de la map ou l'élément du set MUST être des littéraux statiques

Exemple:

resource "azurerm_subnet" "pair" {
  for_each             = var.subnet_map  # map(string)
  name                 = "${each.value}-pair"
  resource_group_name  = azurerm_resource_group.example.name
  virtual_network_name = azurerm_virtual_network.example.name
  address_prefixes     = ["10.0.1.0/24"]
}

Ordre interne des blocs ressource et données

Gravité: SHOULD | Exigence: TFNFR8

Ordre au sein des blocs ressource/données:

  1. Méta-arguments (haut):

    • provider
    • count
    • for_each
  2. Arguments/blocs (milieu, alphabétique):

    • Arguments requis
    • Arguments optionnels
    • Blocs imbriqués requis
    • Blocs imbriqués optionnels
  3. Méta-arguments (bas):

    • depends_on
    • lifecycle (avec sous-ordre: create_before_destroy, ignore_changes, prevent_destroy)

Séparez les sections par des lignes vides.

Ordre des blocs de modules

Gravité: SHOULD | Exigence: TFNFR9

Ordre au sein des blocs de modules:

  1. Méta-arguments du haut:

    • source
    • version
    • count
    • for_each
  2. Arguments (alphabétique):

    • Arguments requis
    • Arguments optionnels
  3. Méta-arguments du bas:

    • depends_on
    • providers

Syntaxe de lifecycle ignore_changes

Gravité: MUST | Exigence: TFNFR10

L'attribut ignore_changes MUST NOT être entre guillemets doubles.

Bon:

lifecycle {
  ignore_changes = [tags]
}

Mauvais:

lifecycle {
  ignore_changes = ["tags"]
}

Comparaison Null pour création conditionnelle

Gravité: SHOULD | Exigence: TFNFR11

Pour les paramètres nécessitant une création conditionnelle de ressources, encapsuler avec le type object pour éviter les problèmes "known after apply" pendant la phase de plan.

Recommandé:

variable "security_group" {
  type = object({
    id = string
  })
  default = null
}

Blocs dynamiques pour objets imbriqués optionnels

Gravité: MUST | Exigence: TFNFR12

Les blocs imbriqués sous conditions MUST utiliser ce motif:

dynamic "identity" {
  for_each = <condition> ? [<some_item>] : []

  content {
    # contenu du bloc
  }
}

Valeurs par défaut avec coalesce/try

Gravité: SHOULD | Exigence: TFNFR13

Bon:

coalesce(var.new_network_security_group_name, "${var.subnet_name}-nsg")

Mauvais:

var.new_network_security_group_name == null ? "${var.subnet_name}-nsg" : var.new_network_security_group_name

Déclarations de fournisseur dans les modules

Gravité: MUST | Exigence: TFNFR27

  • provider MUST NOT être déclaré dans les modules (sauf pour configuration_aliases)
  • Les blocs provider dans les modules MUST utiliser uniquement alias
  • Les configurations de fournisseur SHOULD être transmises par les utilisateurs du module

Exigences des variables

Variables non autorisées

Gravité: MUST | Exigence: TFNFR14

Les propriétaires de modules MUST NOT ajouter des variables comme enabled ou module_depends_on pour contrôler l'opération du module entier. Les basculements de fonctionnalités booléennes pour des ressources spécifiques sont acceptables.

Ordre de définition des variables

Gravité: SHOULD | Exigence: TFNFR15

Les variables SHOULD suivre cet ordre:

  1. Tous les champs requis (alphabétique)
  2. Tous les champs optionnels (alphabétique)

Règles de nommage des variables

Gravité: SHOULD | Exigence: TFNFR16

  • Suivre les règles de nommage de HashiCorp
  • Les commutateurs de fonctionnalités SHOULD utiliser des déclarations positives: xxx_enabled au lieu de xxx_disabled

Variables avec descriptions

Gravité: SHOULD | Exigence: TFNFR17

  • description SHOULD décrire avec précision l'objectif du paramètre et le type de données attendu
  • Le public cible est les utilisateurs du module, pas les développeurs
  • Pour les types object, utiliser le format HEREDOC

Variables avec types

Gravité: MUST | Exigence: TFNFR18

  • type MUST être défini pour chaque variable
  • type SHOULD être aussi précis que possible
  • any MAY être utilisé seulement avec des raisons adéquates
  • Utiliser bool au lieu de string/number pour les valeurs vrai/faux
  • Utiliser un object concret au lieu de map(any)

Variables de données sensibles

Gravité: SHOULD | Exigence: TFNFR19

Si le type d'une variable est object et contient des champs sensibles, la variable entière SHOULD être sensitive = true, ou extraire les champs sensibles dans des variables séparées.

Valeurs par défaut non nullables pour les collections

Gravité: SHOULD | Exigence: TFNFR20

Nullable SHOULD être défini à false pour les valeurs de collection (sets, maps, lists) lors de leur utilisation dans les boucles. Pour les valeurs scalaires, null peut avoir une signification sémantique.

Décourager la nullabilité par défaut

Gravité: MUST | Exigence: TFNFR21

nullable = true MUST être évité sauf s'il y a un besoin sémantique spécifique de valeurs null.

Éviter sensitive = false

Gravité: MUST | Exigence: TFNFR22

sensitive = false MUST être évité (c'est la valeur par défaut).

Conditions de valeur par défaut sensible

Gravité: MUST | Exigence: TFNFR23

Une valeur par défaut MUST NOT être définie pour les entrées sensibles (par ex., les mots de passe par défaut).

Gestion des variables obsolètes

Gravité: MUST | Exigence: TFNFR24

  • Déplacer les variables obsolètes vers deprecated_variables.tf
  • Annoter avec DEPRECATED au début de la description
  • Déclarer le nom du remplacement
  • Nettoyer lors des versions majeures

Exigences des outputs

Outputs Terraform supplémentaires

Gravité: SHOULD | Exigence: TFFR2

Les auteurs SHOULD NOT produire des objets ressources entiers car ceux-ci peuvent contenir des données sensibles et le schéma peut changer avec les versions API ou du fournisseur.

Meilleures pratiques:

  • Produire les attributs calculés des ressources en tant qu'outputs discrets (motif de couche anti-corruption)
  • SHOULD NOT produire les valeurs qui sont déjà des entrées (sauf name)
  • Utiliser sensitive = true pour les attributs sensibles
  • Pour les ressources déployées avec for_each, produire les attributs calculés dans une structure map

Exemples:

# Attribut calculé d'une ressource unique
output "foo" {
  description = "MyResource foo attribute"
  value       = azurerm_resource_myresource.foo
}

# Ressources for_each
output "childresource_foos" {
  description = "MyResource children's foo attributes"
  value = {
    for key, value in azurerm_resource_mychildresource : key => value.foo
  }
}

# Output sensible
output "bar" {
  description = "MyResource bar attribute"
  value       = azurerm_resource_myresource.bar
  sensitive   = true
}

Outputs de données sensibles

Gravité: MUST | Exigence: TFNFR29

Les outputs contenant des données confidentielles MUST être déclarées avec sensitive = true.

Gestion des outputs obsolètes

Gravité: MUST | Exigence: TFNFR30

  • Déplacer les outputs obsolètes vers deprecated_outputs.tf
  • Définir les nouveaux outputs dans outputs.tf
  • Nettoyer lors des versions majeures

Normes des valeurs locales

Organisation de locals.tf

Gravité: MAY | Exigence: TFNFR31

  • locals.tf SHOULD contenir uniquement les blocs locals
  • MAY déclarer les blocs locals à côté des ressources pour les scénarios avancés

Arrangement alphabétique des locals

Gravité: MUST | Exigence: TFNFR32

Les expressions dans les blocs locals MUST être arrangées alphabétiquement.

Types locaux précis

Gravité: SHOULD | Exigence: TFNFR33

Utiliser des types précis (par ex., number pour l'âge, pas string).


Exigences de configuration Terraform

Exigences de version Terraform

Gravité: MUST | Exigence: TFNFR25

Exigences de terraform.tf:

  • MUST contenir un seul bloc terraform
  • La première ligne MUST définir required_version
  • MUST inclure une contrainte de version minimale
  • MUST inclure une contrainte de version majeure maximale
  • SHOULD utiliser le format ~> #.# ou >= #.#.#, < #.#.#

Exemple:

terraform {
  required_version = "~> 1.6"
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 4.0"
    }
  }
}

Fournisseurs dans required_providers

Gravité: MUST | Exigence: TFNFR26

  • Le bloc terraform MUST contenir le bloc required_providers
  • Chaque fournisseur MUST spécifier source et version
  • Les fournisseurs SHOULD être triés alphabétiquement
  • Inclure uniquement les fournisseurs directement requis
  • source MUST être au format namespace/name
  • version MUST inclure des contraintes de version majeure minimale et maximale
  • SHOULD utiliser le format ~> #.# ou >= #.#.#, < #.#.#

Exigences de test

Outils de test

Gravité: MUST | Exigence: TFNFR5

Outils de test requis pour AVM:

  • Terraform (terraform validate/fmt/test)
  • terrafmt
  • Checkov
  • tflint (avec ruleset azurerm)
  • Go (optionnel pour les tests personnalisés)

Configuration du fournisseur de test

Gravité: SHOULD | Exigence: TFNFR36

Pour des tests robustes, prevent_deletion_if_contains_resources SHOULD être explicitement défini à false dans les configurations de fournisseur de test.


Exigences de documentation

Génération de documentation de module

Gravité: MUST | Exigence: TFNFR2

  • La documentation MUST être générée automatiquement via Terraform Docs
  • Un fichier .terraform-docs.yml MUST être présent à la racine du module

Changements non rétrocompatibles et gestion des fonctionnalités

Utilisation de basculements de fonctionnalités

Gravité: MUST | Exigence: TFNFR34

Les nouvelles ressources ajoutées dans les versions mineures/patch MUST avoir une variable de basculement pour éviter la création par défaut:

variable "create_route_table" {
  type     = bool
  default  = false
  nullable = false
}

resource "azurerm_route_table" "this" {
  count = var.create_route_table ? 1 : 0
  # ...
}

Examen des changements non rétrocompatibles potentiels

Gravité: MUST | Exigence: TFNFR35

Changements non rétrocompatibles nécessitant une prudence:

Blocs de ressources:

  1. Ajouter une nouvelle ressource sans création conditionnelle
  2. Ajouter des arguments avec des valeurs non-défaut
  3. Ajouter des blocs imbriqués sans dynamic
  4. Renommer des ressources sans blocs moved
  5. Changer count en for_each ou vice-versa

Blocs variable/output:

  1. Supprimer/renommer des variables
  2. Changer le type de variable
  3. Changer les valeurs default de variable
  4. Changer nullable à false
  5. Changer sensitive de false à true
  6. Ajouter des variables sans default
  7. Supprimer des outputs
  8. Changer la value output
  9. Changer la valeur sensitive output

Normes de contribution

Protection des branches du référentiel GitHub

Gravité: MUST | Exigence: TFNFR3

Les propriétaires de modules MUST définir des politiques de protection de branche sur la branche par défaut (généralement main):

  1. Exiger une Pull Request avant la fusion
  2. Exiger l'approbation du push examinable le plus récent
  3. Rejeter les approbations PR obsolètes lorsque de nouveaux commits sont poussés
  4. Exiger un historique linéaire
  5. Empêcher les forces push
  6. Ne pas autoriser les suppressions
  7. Exiger l'examen CODEOWNERS
  8. Aucun contournement des paramètres autorisé
  9. Appliquer pour les administrateurs

Liste de conformité

Utilisez cette liste de contrôle lors du développement ou de l'examen des Azure Verified Modules:

Structure du module

  • [ ] Les références croisées du module utilisent les sources de registre avec des versions épinglées
  • [ ] Les versions des fournisseurs Azure (azurerm/azapi) respectent les exigences AVM
  • [ ] .terraform-docs.yml présent à la racine du module
  • [ ] Fichier CODEOWNERS présent

Style de code

  • [ ] Tous les noms utilisent la casse snake_case minuscule
  • [ ] Les ressources sont ordonnées avec les dépendances en premier
  • [ ] for_each utilise map() ou set() avec des clés statiques
  • [ ] Les blocs ressource/données/module suivent l'ordre interne approprié
  • [ ] ignore_changes n'est pas entre guillemets
  • [ ] Les blocs dynamiques sont utilisés pour les objets imbriqués conditionnels
  • [ ] coalesce() ou try() est utilisé pour les valeurs par défaut

Variables

  • [ ] Aucune variable enabled ou module_depends_on
  • [ ] Variables ordonnées: requis (alphabétique) puis optionnel (alphabétique)
  • [ ] Toutes les variables ont des types précis (éviter any)
  • [ ] Toutes les variables ont des descriptions
  • [ ] Les collections ont nullable = false
  • [ ] Aucune déclaration sensitive = false
  • [ ] Aucune valeur par défaut pour les entrées sensibles
  • [ ] Les variables obsolètes sont déplacées vers deprecated_variables.tf

Outputs

  • [ ] Les outputs utilisent le motif de couche anti-corruption (attributs discrets)
  • [ ] Les outputs sensibles sont marqués sensitive = true
  • [ ] Les outputs obsolètes sont déplacés vers deprecated_outputs.tf

Configuration Terraform

  • [ ] terraform.tf a des contraintes de version (format ~>)
  • [ ] Le bloc required_providers est présent avec tous les fournisseurs
  • [ ] Aucune déclaration provider dans le module (sauf les alias)
  • [ ] Les locals sont arrangés alphabétiquement

Test et qualité

  • [ ] Les outils de test requis sont configurés
  • [ ] Les nouvelles ressources ont des basculements de fonctionnalités
  • [ ] Les changements non rétrocompatibles sont examinés et documentés

Statistiques récapitulatives

  • Exigences fonctionnelles: 3
  • Exigences non fonctionnelles: 34
  • Exigences totales: 37

Par gravité

  • MUST: 21 exigences
  • SHOULD: 14 exigences
  • MAY: 2 exigences

Basé sur: Azure Verified Modules - Terraform Requirements

Skills similaires