Custom Terraform Providers for Chileans

Una historia de esfuerzo y superación.

Luciano Adonis
devsChile
7 min readAug 1, 2018

--

Debido a que no encontré una explicación amigable sobre cómo funciona un provider de Terraform, decidí hacer un post en base a lo aprendí del Hackday LATAM.

El desafío de armar un provider

Podría resumir el evento en implementar un provider de la forma más decente posible sin morir en el proceso. Tan simple como eso. Respecto al review del evento en sí:

🍕: Pudo haber estado mejor, pero por primera versión 8/10
🍺: Pepsi y agua mineral 5/10
☀️: Agradable para aprender y romper cosas en el proceso.

Especiales agradecimientos a mi team conformado por Martin Chait y Juan Esteban Colcombet, cuyos esfuerzos nos permitieron cumplir con el objetivo (y no morir en el intento). También al resto de los participantes que me permitieron colarme al evento 🤙🏻

¿Qué es un provider?

Un provider es el componente responsable de implementar uno o más recursos mediante la comunicación con la API de dicho elemento, en nombre de Terraform.

TL;DR: es la cuestión que lee los archivos .tf y hace la magia al final del día.

¿Para qué hacer tu propio provider?

Fama, mujeres, dinero; algunas veces extender las funcionalidades de un provider específico y otras para crear uno en base a las necesidades especificas de una organización.

¿De dónde sale Go?

Me quedo con la definición de Terraform:

Terraform supports a plugin model, and all providers are actually plugins.

Estos plugins son binarios en Go y pese a que se podría utilizar otro lenguaje, la mayoría (por no decir que todos) están en Go, compilarlos es bien entrete.

¿Cómo hace la magia?

La forma en que Terraform nos permite crear y administrar recursos es en base a 4-5 operaciones comunes:

  • Create: crear el recurso.
  • Read: leer el estado del recurso y utilizar esto para actualizar el estado en Terraform State.
  • Update: realizar la actualización del recurso en base al Terraform State actual.
  • Delete: eliminar el recurso.
  • Exists: validar si existe ese recurso.

De esta forma Terraform compara el estado del recurso con el existente y decide qué operaciones debe realizar sobre éste.

Un dato curioso sobre estas operaciones es que la más compleja a opinión personal es la Exist, ya que las validaciones correspondientes le dan el peso a Terraform cuando debe manejar infraestructura y tomar decisiones.

Pero, ¿cómo hace lo que le pido?

Muchas de las operaciones se realizan mediante la comunicación de provider de Terraform con la API del recurso involucrado (Ya saben operaciones CRUD mas bonitas). De esta forma cuando defines un parámetro dentro de un atributo de un recurso específico, Terraform procesa todo esto y hace que se realice cada operación en base a distintas funciones.

Dependiendo de la complejidad de las operaciones, una buena opción es optar por utilizar algún Package que facilite la comunicación en Go. En vez de realizar operaciones a la fuerza. También esto ayuda a evitar tener que reinventar la rueda para evitar tener que hacer operaciones simples más complejas de lo que deberían ser.

Dime algo que no sepa, cerebrito.

La velocidad media de una golondrina europea es de 11 metros por segundo

Nos tocó Bitbucket como provider a implementar (con los requisitos ya mencionados) y cuyo objetivo final era aprender y divertirse en un día solo para Terraform.

“Pero si ya existe un provider para Bitbucket” -Anon

Sí, pero no hecho en casa con amor, contra el tiempo y sin copiar.

Okay, ¿a qué queríamos llegar al armar este provider? A uno que nos permitiese crear un repositorio en uno de nuestros proyectos ya existentes en Bitbucket. De forma que pudiésemos realizarlo con las siguientes definiciones en un main.tf cualquiera.

“¿Qué le ocurrió a la definición de las credenciales?” -Anon

Okay, en retrospectiva, para cubrir los objetivos (de una u otra forma) con el tiempo que se nos dió, algo debía sacrificarse por el bien de la funcionalidad.

Volviendo al tema:

¿Dónde le digo al provider que haga las cosas como yo quiero?

Para llegar a eso, primero debemos familiarizarnos con el siguiente panorama:

Adivinen quién no completo el curso de diseño gráfico en Udemy.

El amado provider con el que trabajamos en nuestro día a día es simplemente el resultado de compilar los siguientes archivos:

  • main.go
  • provider.go
  • resource_do_stuff.go
  • resource_do_other_stuff.go
  • resource_do_something_nice.go
  • config.go

y muchos colores 🌈

MAIN

Go requiere de un main.go el cual es el ejecutable por defecto al compilarlo. Este consume la librería (o biblioteca, como sea) Plugin de Terraform, que se encarga de realizar la comunicación con el Terraform Core y el plugin.

PROVIDER

El provider.go le entrega la metadata al Plugin y de esta forma Terraform sabe que puede hacer. La metadata que este le entrega son las propiedades asociadas a un recurso, al igual que las funciones asociadas a este para implementarlo.

En este ejemplo se está definiendo el recurso “bitbucket_repository” y la función asociada a este. En una situación menos experimental, en este punto se agregaría la configuración inicial para habilitar la comunicación con los distintos back-ends y permitirle al usuario definir sus credenciales como variables de entorno. Variando según el tipo de recurso al cual quieras acceder.

TL;DR: en este archivo se define lo que puede hacer y cómo configurar las credenciales específicas para pegarle a la API del recurso.

y, ¿dónde esta la función? Pues en el…

✨ RESOURCE

Por convención, los provider de Terraform utilizan cada recurso en un archivo individual (sí, por eso escribí varios resources al comienzo). Estos son llamados desde sus definiciones individuales en el provider.go

En este se define el esquema para los atributos que se esperan recibir, en este se incluye todas las funciones individuales para cada operación CRUD.

Por temas de presupuesto, no alcanzó en una pura imagen la definición del resource de ejemplo.

Hay que admitir que no es la forma mas bonita de llamar la función pero para un primer acercamiento ¿nos vamos a poner exigentes? No.

“Hey y ¿para qué el log.Printf?” -Anon

Me pregunto lo mismo, lo ideal era que mostrara por consola cuando se estuviesen haciendo cosas bonitas, pero por cosas de la vida no funciona así o al menos aún no logro hacerlo.

Como lifehack, se puede realizar lo siguiente:

Al habilitar DEBUG, mostrará todas las cosas que pasan por debajo, permitiendo hacer un troubleshooting más amigable; lo que sí ocurre con eso es que la cantidad de lineas que te botará por cada cosa que hagas son considerables ypor eso se le especifica un archivo en la ruta que te acomode, para revisar el log de forma mas amigable.

Oh en que estaba…, ah sí, la función que llama a la función que es llamada por otra función, pero, ¿dónde esta esa función? Pues en:

✨ CONFIG

Ignoremos el que las credenciales van de lleno ahí y sigamos con esto, falta menos.

Uno de los elementos no tan bien explicados responderá muchas de tus dudas existenciales. En este archivo se define cómo se establece la comunicación con la API del recurso. Esto se puede hacer mediante:

Me gusta el logo de Gorequest.

A) El import de un Package que permita pegarle a la API de forma amigable y elegante.

B) Pegarle directamente con HTTP Request.

Adivina cuál se usó para esta prueba….

¿Cuándo compilas?

Pues ahora, debido a que se tiene claro lo que se quiere hacer (crear un repositorio dentro de Bitbucket) y cómo se logrará esto mediante las funciones en Go que realizarán el POST y el DELETE en nombre de Terraform, mas la otra que llama a esa función que le pide a otra función que llame a la otra que finalmente llamará a todas las anteriores.

Para compilar basta utilizar el siguiente comando:

Se debe tener 👀 con respecto al <NAME> ya que en base a este Terraform detectará que es el Plugin correcto. ¿Cómo? Mediante un sistema de autodiscover que busca localmente los plugins antes de salir a buscarlos al sitio oficial de Terraform.

Para este ejemplo se están definiendo los recursos como “bitbucket_algo”, el primer elemento, vendría siendo el nombre del provider, por lo cual, nuestro provider debe llamarse:

Este generará un binario el cual será utilizado por Terraform al ejecutar terraform init en el repositorio.

…y así chicos, es como conocí a su Provider…

Para los que quieran mejores fuentes de información o simplemente para los que quedaron aún mas perdidos:

Parte del post Terraform GSuite Provider

Cualquier otra duda o comentario sobre qué mejorar, luciano.adonisv@gmail.com.

--

--