Terraform: Package Schema para millennials

Luciano Adonis
devsChile
Published in
5 min readNov 20, 2018

Parte 2: la documentación contraataca

Original aquí.

El siguiente es un pequeño resumen de la arquitectura de Terraform y la documentación del Package Schema, el cual nos permite escribir providers de forma simple, en base a las definiciones existentes y operaciones CRUD.

Aprovechando de explicar como se realiza la autenticación, lo cual era una de mis mayores dudas cuando empecé a entender como funcionaba un provider.

Este post forma parte de:

Y, para mayor contexto:

Arquitectura de Terraform

Se divide en dos componentes principales, Terraform Core y Plugins. Estos últimos pueden ser Providers o Provisioners, los cuales son los encargados de comunicarse con la API del proveedor que se quiera manejar.

Terraform Core

Es un binario escrito en Go, el cual es responsable de ejecutar, aplicar y reflejar en el state los cambios generados por el provider.

Dentro de sus responsabilidades está:

  • Leer la configuración y administrar el state diff{} , apply{} y refresh{}.
  • Interpretar las relaciones entre recursos mediante el dependency Graph.
  • Buscar y comunicarse con Plugins.
  • plan y apply.

La comunicación entre este y los Plugins mediante remote procedure call (RPC) el cual viene siendo Terraform Core llamando los recursos del provider.

TL;DR: al Terraform Core no podría importarle menos como pegarle a la API de X proveedor de servicios, ya que ese es problema del provider y siguiendo esa misma lógica puede trabajar con multiples providers.

Provider

Binario en Go, el cual posee conocimiento detallado del provider con el que se quiere trabajar. Estos son los encargados de establecer la comunicación con la API del servicio, lidiar con la autenticación y más.

Dentro de estos se definen los distintos recursos que representan servicios con los cuales se quiere interactuar, para esto se basa en 4 funciones principales y, si te sientes suertudo, una quinta que es Exists.

Dentro de sus responsabilidades están:

  • Manejo de autenticación.
  • Comunicación con la API.
  • Define create(), read(), update() y delete() para cada definición de recurso utilizado por el provider.

En este punto es el Package Schema lo que nos permite definir los distintos recursos de forma simple y efectiva, para que puedan ser utilizados por el Terraform Core al aplicar cambios.

Package Schema

Tomando como ejemplo al terraform provider Datadog v1.3.0, el cual es uno de mis favoritos, explicaré brevemente las definiciones del Package Schema en los archivos que componen la base de un provider:provider.go, config.go y resource_x.go.

Definitivamente le sigo sacando el jugo a esos iconos.

Me salto el main.go porque no hay mucho que profundizar ahí ademas de algunos elementos claves que no importan hasta que ya estás viendo la luz.

provider.go

Es el archivo en el cual se definen los valores para realizar la autenticación y los recursos con sus respectivas funciones asociadas. Ejemplo: provider.go

La definición de este archivo se basa en:

Schema: es utilizado para describir la estructura de un valor. Para esto se mapea el atributo, usualmente con campos como; Type, Required, Description e idealmente DefaultFunc.

"example": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("COMPUTE_EXAMPLE", nil),
},
  • Type: ya saben el tipo de valor esperado; bool, int, string, float o typelist.
  • Required: de tipo bool, para definiciones extra no obligatorias.
  • Description: no sé cómo describir esta definición.
  • EnvDefaultFunc: utilizada para retornar el valor de una variable de entorno determinada.

ConfigureFunc: es utilizada para configurar un provider, utilizando las credenciales especificadas para que le entregue un valor a la interface{} y este valor sirva para configurar las credenciales.

Utilizando el ejemplo del provider de Datadog como punto de referencia. Desde el provider.go se realiza la configuración de la autenticación de la siguiente forma:

ConfigureFunc: providerConfigure,
...
func providerConfigure(d *schema.ResourceData) (interface{}, error) {...}

En esta se define un struct con las credenciales entregadas por las definiciones del schema, las cuales son pasadas a la función Client que se encuentra en el config.go. Este archivo es opcional, en el mejor de los casos si cuentas con un package dedicado a la API del proveedor en Go, puede quedar mucho mas limpio.

Desde la versión v1.4.0 del Datadog provider, ya no se utiliza el archivoconfig.go, debido a que movieron las definiciones al provider.go.

config.go

En este archivo se pueden agregar otras configuraciones que se requieran para la autenticación y no colapsar el provider.go. Esta será utilizada como metadatos por el resto de funciones utilizadas en este provider al comunicarse con la API correspondiente. Ejemplo: config.go

Retomando la configuración de la autenticación:

Dentro de este, se le pasa otro struct para recibir los valores:

type Config struct { 
APIKey string
APPKey string
}

Se utiliza como receiver *Config con el valor de c para la función Client() , retornando un valor de tipo *datadog.Client

func (c *Config) Client() *datadog.Client {client := datadog.NewClient(c.APIKey, c.APPKey)
log.Printf("[INFO] Datadog Client configured ")
return client
}

Y este metadato puede ser utilizado por otros recursos, lo cual nos lleva a las definiciones de estos.

resource_x.go

Estos archivos sirven para definir los recursos a implementar en base a las operaciones CRUD. Ejemplo: resource_datadog_monitor.go

Resource: se utiliza para la configuración de un recurso. Dentro de este se especifican las operaciones CRUD:

Cada una de estas operaciones posee una función asociada, la cual procesa los datos entregados al proveedor a través del siguiente Schema:

Schema map[string]*Schema

Al igual que en el provider, este nos permite definir la información configurable que se utilizará para la configuración de los recursos.

Además incluye otro conjunto de funciones 100tyfikas, que no abordaré.

Retomando el ejemplo de Datadog:

Dentro de las operaciones CRUD de los recursos, se vería algo así:

client := meta.(*datadog.Client)

Esto al ser anexado a las solicitudes como client, lo que representa es datadog.NewClient(c.APIKey,c.APPKey)permitiendo establecer comunicación en base a la API de Datadog en Go.

Resumiendo

Lee la documentación y estarás bien.
  • Para lograr que tu Custom Provider haga lo que necesites y mucho más, debes comprender el funcionamiento y las definiciones disponibles en el Package Schema.
  • Mientras que para la autenticación que utilizará cada una de las funciones, ejecutadas por Terraform, es disponibilizada mediante la metadata a través de la interface a cada operación CRUD. Lo cual suena coherente después de unas horas.

Ah, si tienes una API en Go, será una experiencia más agradable. 🔮

--

--