Créer un provider Terraform (Partie 1)

Joseph Ligier
8 min readJan 11, 2024

Dans cette série d’article, nous allons voir comment créer un provider Terraform et le publier sur la registry HashiCorp et opentofu.

Nous avons déjà parlé de terraform et de son utilisation avec azure. Mais nous n’avons pas encore parlé de providers terraform.

J’ai créé récemment mon premier provider :

Alors je partage mon expérience.

Qu’est-ce qu’un provider Terraform ?

C’est un plugin Terraform qui va permettre de communiquer avec une API. Les providers les plus utilisés sont évidemment aws (il y a aussi à ne pas négliger awscc), azurerm (pour le cloud Azure), google (pour le cloud GCP). Mais il y en a plein d’autres :

  • Kubernetes : pour gérer les ressources kubernetes
  • Helm : pour gérer les ressources helm
  • Github : pour gérer les ressources Github
  • Et donc le mien : pour gérer les ressources Cilium
L’interaction entre terraform et l’API

Nous allons voir maintenant comment le développer.

Pré-requis

  • Il faut connaître un minimum le langage Go et Terraform
  • Il faut avoir installé : Go, Makefile, Terraform (ou Opentofu)
  • Il faut un compte Github

Réutiliser le template

La première chose à faire est de réutiliser le repos git de HashiCorp :

Use this template > Create a new repository

Notez bien qu’il ne faut pas le forker.

On choisit son owner (littlejo pour moi par exemple). Le nom du repo doit être de la forme terraform-provider-$projet

Par exemple, j’ai choisi comme projet la gestion de cilium, j’ai donc mis terraform-provider-cilium.

Create repository, on génère alors le repo git :

On a alors :

Il “suffit” alors de modifier de modifier ce repo pour créer le provider.

Tester le provider en local

Avant de le releaser un truc qui ne fonctionne pas (et paraître ridicule :D), je vous conseille de tester sur votre machine. Pour se faire, il faut mettre le binaire généré dans le répertoire :

~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH}
  • ~ : le mettre dans son home directory (pas à la racine de son projet terraform)
  • HOSTNAME : terraform.local
  • NAMESPACE : local
  • NAME : le nom du projet (blog pour moi)
  • VERSION : la version du provider (on commence en étant modeste)
  • OS_ARCH : la version du noyau (darwin_amd64: MacOSX, darwin_arm64: MacOSX version arm, linux_amd64 : Linux, …)

Je vous conseille de modifier le Makefile (enfin le GNUmakefile) :

HOSTNAME=terraform.local
NAMESPACE=local
NAME=blog
BINARY=terraform-provider-${NAME}
VERSION=0.0.1
OS_ARCH=linux_amd64

default: install

build:
go build -o ${BINARY}

install: build
mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH}
mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH}

On génère alors le provider :

go build -o terraform-provider-blog
go: downloading github.com/hashicorp/terraform-plugin-framework v1.4.2
go: downloading github.com/hashicorp/terraform-plugin-log v0.9.0
go: downloading github.com/hashicorp/terraform-plugin-go v0.20.0
go: downloading github.com/hashicorp/go-hclog v1.5.0
go: downloading github.com/vmihailenco/msgpack/v5 v5.4.1
go: downloading github.com/vmihailenco/msgpack v4.0.4+incompatible
go: downloading github.com/hashicorp/go-plugin v1.6.0
go: downloading github.com/mitchellh/go-testing-interface v1.14.1
go: downloading google.golang.org/grpc v1.60.0
go: downloading github.com/fatih/color v1.13.0
go: downloading github.com/mattn/go-isatty v0.0.16
go: downloading github.com/vmihailenco/tagparser/v2 v2.0.0
go: downloading github.com/hashicorp/go-uuid v1.0.3
go: downloading github.com/hashicorp/terraform-registry-address v0.2.3
go: downloading google.golang.org/protobuf v1.31.0
go: downloading github.com/golang/protobuf v1.5.3
go: downloading github.com/hashicorp/yamux v0.1.1
go: downloading github.com/oklog/run v1.0.0
go: downloading github.com/mattn/go-colorable v0.1.13
go: downloading golang.org/x/sys v0.15.0
go: downloading github.com/hashicorp/terraform-svchost v0.1.1
go: downloading golang.org/x/net v0.17.0
go: downloading google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97
go: downloading golang.org/x/text v0.14.0
mkdir -p ~/.terraform.d/plugins/terraform.local/local/blog/0.0.1/linux_amd64
mv terraform-provider-blog ~/.terraform.d/plugins/terraform.local/local/blog/0.0.1/linux_amd64

La première fois cela va prendre un peu de temps (probablement pour télécharger les libs). Mais ensuite ça sera super rapide !

Maintenant que la compilation fonctionne on va faire une petite modification. Dans le répertoire internal/provider, on va modifier le fichier provider.go, ligne 33 :

func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "scaffolding"
resp.Version = p.version
}

C’est ici qu’on va décider quel nom de provider que l’on veut. Pour blog, ça sera donc :

func (p *ScaffoldingProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
resp.TypeName = "blog"
resp.Version = p.version
}

On va regénérer le provider :

make
go build -o terraform-provider-blog
mkdir -p ~/.terraform.d/plugins/terraform.local/local/blog/0.0.1/linux_amd64
mv terraform-provider-blog ~/.terraform.d/plugins/terraform.local/local/blog/0.0.1/linux_amd64

On va créer ensuite un répertoire où on va tester Terraform :

mkdir terraform-blog-test

Dans ce répertoire on va rajouter un fichier versions.tf :

terraform {
required_providers {
blog = {
source = "terraform.local/local/blog"
version = "0.0.1"
}
}
}

provider "blog" {}

On initialise le provider :

terraform init

Initializing the backend...

Initializing provider plugins...
- Finding terraform.local/local/blog versions matching "0.0.1"...
- Installing terraform.local/local/blog v0.0.1...
- Installed terraform.local/local/blog v0.0.1 (unauthenticated)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.


│ Warning: Incomplete lock file information for providers

│ Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers:
│ - terraform.local/local/blog

│ The current .terraform.lock.hcl file only includes checksums for linux_amd64, so Terraform running on another platform will fail to install these providers.

│ To calculate additional checksums for another platform, run:
│ terraform providers lock -platform=linux_amd64
│ (where linux_amd64 is the platform to generate)


Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

On va créer un fichier main.tf :

resource "blog_example" "example" {
configurable_attribute = "some-value"
}

On va l’appliquer :

terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# blog_example.example will be created
+ resource "blog_example" "example" {
+ configurable_attribute = "some-value"
+ defaulted = "example value when not configured"
+ id = (known after apply)
}

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

blog_example.example: Creating...
blog_example.example: Creation complete after 0s [id=example-id]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

On a donc créé une ressource blog_example.

Pour modifier ce nom de ressource, on va retourner au repo du provider, le fichier internal/provider/example_resource.go, ligne 41:

func (r *ExampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_example"
}

Si on veut juste une ressource item :

func (r *ExampleResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_item"
}

On recompile :

make
go build -o terraform-provider-blog
mkdir -p ~/.terraform.d/plugins/terraform.local/local/blog/0.0.1/linux_amd64
mv terraform-provider-blog ~/.terraform.d/plugins/terraform.local/local/blog/0.0.1/linux_amd64

On modifie le fichier main.tf dans le répertoire terraform-blog-test :

resource "blog_item" "example" {
configurable_attribute = "some-value"
}

On initialise :

terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of terraform.local/local/blog from the dependency lock file
- Installing terraform.local/local/blog v0.0.1...

│ Error: Failed to install provider

│ Error while installing terraform.local/local/blog v0.0.1: the local package for terraform.local/local/blog 0.0.1 doesn't match any of the checksums previously recorded in the dependency lock file (this might be because the available checksums are for packages
│ targeting different platforms); for more information: https://www.terraform.io/language/provider-checksum-verification

Patatras ! ça ne marche plus.

Terraform détecte que le binaire du provider n’ait plus le même par rapport à la dernière fois. On peut tester “terraform init -upgrade” mais ça ne marchera pas mieux. La seule solution est de supprimer le fichier .terraform.lock.hcl :

rm .terraform.lock.hcl
terraform init

Initializing the backend...

Initializing provider plugins...
- Finding terraform.local/local/blog versions matching "0.0.1"...
- Installing terraform.local/local/blog v0.0.1...
- Installed terraform.local/local/blog v0.0.1 (unauthenticated)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.


│ Warning: Incomplete lock file information for providers

│ Due to your customized provider installation methods, Terraform was forced to calculate lock file checksums locally for the following providers:
│ - terraform.local/local/blog

│ The current .terraform.lock.hcl file only includes checksums for linux_amd64, so Terraform running on another platform will fail to install these providers.

│ To calculate additional checksums for another platform, run:
│ terraform providers lock -platform=linux_amd64
│ (where linux_amd64 is the platform to generate)


Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

On applique :

terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

Terraform planned the following actions, but then encountered a problem:

# blog_item.example will be created
+ resource "blog_item" "example" {
+ configurable_attribute = "some-value"
+ defaulted = "example value when not configured"
+ id = (known after apply)
}

Plan: 1 to add, 0 to change, 0 to destroy.

│ Error: no schema available for blog_example.example while reading state; this is a bug in Terraform and should be reported


Ça ne fonctionne pas non plus. En fait comme on a changé le nom de la ressource Terraform, Terraform le détecte via le fichier tfstate. Il suffit donc de supprimer le tfstate :

rm terraform.tfstate
terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create

Terraform will perform the following actions:

# blog_item.example will be created
+ resource "blog_item" "example" {
+ configurable_attribute = "some-value"
+ defaulted = "example value when not configured"
+ id = (known after apply)
}

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value:yes

blog_item.example: Creating...
blog_item.example: Creation complete after 0s [id=example-id]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Ça peut être intéressant de faire un Makefile dans le répertoire terraform de test qui supprime les deux fichiers et qui fait un init.

Voilà ce qu’on peut dire en introduction à la création d’un provider Terraform. Pour aller plus loin, je vous recommande la documentation :

Dans la prochaine partie, nous verrons un peu plus en détails la modification d’un provider.

--

--