Terraform custom provider, c’est bien mais comment on le configure ?
Dans le précédent article intitulé « Terraform custom provider » que vous pouvez retrouver ici : https://ekite.info/2024/04/05/terraform-custom-provider/ vous avez pu apprendre et créer votre propre provider personnalisé pas à pas. Cependant, ne manquait-il pas quelque chose ? Faire une requête HTTP avec une URL en « dur » n’est franchement pas très utile et sécurisé…
Dans ce second article, nous vous présenterons comment configurer correctement votre provider afin de lui passer tous les paramètres que vous souhaitez. Que ce soit pour la configuration de variables d’environnement telles qu’une URL pouvant être dupliquée sur des environnements différents selon l’utilisation que l’on fait de notre provider, ou alors des secrets qui ne peuvent pas, par définition, être mis en clair directement dans un repository Git.
Vous trouverez les évolutions dans la pull request liée à cet article.
Cependant, nous vous proposons de réaliser l’exercice vous même comme dans le premier article où nous avons appris à créer notre premier custom provider.. Alors codons ensemble !
Prérequis
Avoir réalisé la première partie Terraform custom provider
Pas à pas
Pour commencer, vous avez trois choix possibles, récupérer le projet depuis Git, reprendre à la fin du guide précédent ou récupérer la branche de la pull request sur le précédent lien.
Afin d’avoir un exemple concret, nous avons apporté une petite modification à l’API pour qu’elle n’autorise que les requêtes contenant un certain header avec une valeur précise, qui peut correspondre à une liste de plusieurs clés d’API valides.
La mise en place de la configuration de notre provider se passera uniquement dans le fichier provider.go, et se résumera à deux éléments. D’abord un schéma de définition de notre provider, qui corresponds aux paramètres possibles de notre provider, dans notre cas, une URL et un token d’accès à une API.
func Provider() *schema.Provider {
return &schema.Provider{
Schema: map[string]*schema.Schema{
"api_token": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("API_TOKEN", nil),
},
"api_url": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("API_URL", nil),
},
},
ResourcesMap: map[string]*schema.Resource{
"ekite_cour": dataSourceCour(),
},
DataSourcesMap: map[string]*schema.Resource{
"ekite_courses": dataSourceCourses(),
},
ConfigureContextFunc: configure,
}
}Ensuite il nous faudra une méthode permettant de configurer notre provider. Si vous êtes observateur, vous verrez l’appel à cette méthode dans l’extrait précédent.
type Config struct {
api_token string
api_url string
}
func configure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
var diags diag.Diagnostics
config := Config{}
// On essaie de récupérer la valeur passée à notre provider, s'il y arrive il le cast en string, sinon on renvoie un message d'erreur
if v, ok := d.GetOk("api_token"); ok {
config.api_token = v.(string)
} else {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "api_token is required",
})
return nil, diags
}
if v, ok := d.GetOk("api_url"); ok {
config.api_url = v.(string)
} else {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "api_url is required",
})
return nil, diags
}
return &config, diags
}Cette méthode nous permettra d’accéder à notre configuration dans n’importe quelle méthode via l’interface du provider. On observe d’abord une petite structure qui représente notre configuration, puis, le plus intéressant, le contenu de notre méthode. Nous voyons qu’elle aussi à un paramètre « d », qui est la variable définie par le schéma que nous avons vu plus haut. Via cette variable nous pourrons interagir avec les valeurs passées par ceux qui utilisent notre provider et les orienter en cas d’erreur. Dans notre exemple nous allons juste vérifier la présence d’une valeur mais on peut imaginer faire des requêtes pour vérifier les paramètres ou juste les comparer à des listes de valeurs constantes.
Bien, nous avons notre configuration, il est maintenant temps de l’utiliser pour nos ressources existantes. Au final, nous avons besoin de ne modifier que les requêtes à notre API pour y ajouter un header, et utiliser l’URL d’API passée en configuration.
Retournons dans notre fichier data_source_cour.go, et modifions notre CRUD. Pour l’exemple je mettrai uniquement la méthode read, mais vous pouvez retrouver le code complet dans le repo.
func resourceCourRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var diags diag.Diagnostics
client := &http.Client{Timeout: 10 * time.Second}
config := m.(*Config)
url := config.api_url + "/cours/" + d.Id()
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return diag.FromErr(err)
}
req.Header.Add("tf-api-key", config.api_token)
r, err := client.Do(req)
if err != nil {
return diag.FromErr(err)
}
defer r.Body.Close()
cour := &Cour{}
err = json.NewDecoder(r.Body).Decode(&cour)
if err != nil {
return diag.FromErr(err)
}
d.Set("name", cour.Name)
d.Set("time", cour.Time)
d.Set("summary", cour.Summary)
return diags
}Donc dans cette méthode on retrouve l’interface de notre provider, via la variable « m », en paramètre de notre méthode. Remarquez comme il est facile d’interagir avec cette configuration. Il suffit de récupérer dans une variable « config » les informations passées par l’interface (ligne 4). Il ne nous reste plus qu’à l’utiliser où bon nous semble. Dans la ligne qui suit nous créons notre URL avec la propriété « api_url » et ligne 11 nous créons le header et lui attribuons la valeur passée avec la propriété « api_token ». Il ne nous reste plus qu’à tester tout ça !
Vous connaissez la chanson, on change la version de notre provider dans notre Makefile, et on lance un « make install ». Maintenant, rendez-vous dans le main.tf pour y apporter les modifications nécessaire. Une fois cela fait on lance notre commande « tf init –upgrade »
terraform {
required_providers {
courses = {
version = "0.5.8"
source = "hashicorp.com/ekite/courses"
}
}
}
provider "courses" {
api_url = "http://localhost:4000"
api_token = "1234-5678-910-abCd"
}
resource "ekite_cour" "example" {
provider = courses
name = "test from tf update"
time = 60
summary = "summary from tf"
}
Vous pouvez maintenant jouer avec ces paramètres pour observer les différents comportements.
Quand on ne remplit pas les paramètres :
Quand on set de mauvais paramètres :

Et bien sùr avec les bons paramètres, un résultat en success
Conclusion
À la fin de cette suite de deux articles, vous êtes maintenant capable de réaliser vos propres custom providers Terraform, ainsi que la configuration associée. La prochaine étape sera de voir comment nous pouvons mettre en place une intégration continue autour du développement de ce provider et comment le mettre facilement à disposition d’utilisateurs.
Cet exemple simple nous a permis de voir comment réaliser et configurer un provider custom. Il vous a permis de voir toute la puissance de Terraform dont la customization peut aller jusqu’à l’extrême limite de votre imagination et de vos besoins… Alors allez-y, codez !