Bloques dinámicos en Terraform 0.12.x
Hace un tiempo escribí sobre cómo hacer bloques dinámicos en Terraform 0.11.x, que si bien resolvía el problema generaba otros debido a que era no era una solución oficial.
En esencia lo que hacía era definir los bloques de forma dinámica usando una asiganción a una lista de maps. Es decir, en vez de hacer esto:
resource "aws_codebuild_project" "codebuild" {
[...]
artifacts{
type = "${var.codebuild_artifacts_type}"
location = "${var.codebuild_artifacts_location}"
name = "${var.codebuild_artifacts_name}"
namespace_type = "${var.codebuild_artifacts_namespace_type}"
packaging = "${var.codebuild_artifacts_packaging}"
path = "${var.codebuild_artifacts_path}"
}
}
Se sustituía por:
resource "aws_codebuild_project" "codebuild" {
[...]
artifacts = ["${local.codebuild_artifacts}"]
}
Donde local.codebuild_artifacts
se construida a partir de una lista de maps, que dependiendo del tipo definido en la variable codebuild_artifacts_type
creaba un bloque diferente:
locals {
codebuild_artifacts_def = {
"S3" = [{
type = "${var.codebuild_artifacts_type}"
location = "${var.codebuild_artifacts_location}"
name = "${var.codebuild_artifacts_name}"
namespace_type = "${var.codebuild_artifacts_namespace_type}"
packaging = "${var.codebuild_artifacts_packaging}"
path = "${var.codebuild_artifacts_path}"
encryption_disabled = var.codebuild_artifacts_encryption_disabled
}]
"CODEPIPELINE" = [{
type = "${var.codebuild_artifacts_type}"
location = "${var.codebuild_artifacts_location}"
name = "${var.codebuild_artifacts_name}"
namespace_type = "${var.codebuild_artifacts_namespace_type}"
packaging = "${var.codebuild_artifacts_packaging}"
path = "${var.codebuild_artifacts_path}"
encryption_disabled = var.codebuild_artifacts_encryption_disabled
}]
"NO_ARTIFACTS" = [{
type = "${var.codebuild_artifacts_type}"
}]
}
# Retuned map
codebuild_artifacts = "${local.codebuild_artifacts_def[var.codebuild_artifacts_type]}"
}
Bloques dinámicos en Terraform 0.12.x
Terraform 0.12.x propone el uso de bloques dinámicos para solucionar este y otros casos relacionados a bloques. Para esto hay que usar dynamic
y for-each
. Por ejemplo, para la definición de arriba tendriamos lo siguiente:
dynamic "artifacts" {
for_each = [local.codebuild_artifacts]
content {
type = artifacts.value.type
encryption_disabled = lookup(artifacts.value, "encryption_disabled", null)
location = lookup(artifacts.value, "location", null)
name = lookup(artifacts.value, "name", null)
namespace_type = lookup(artifacts.value, "namespace_type", null)
packaging = lookup(artifacts.value, "packaging", null)
path = lookup(artifacts.value, "path", null)
}
}
Y la definición de local.codebuild_artifacts
se simplifica enormemente:
locals {
codebuild_artifacts = {
type = var.codebuild_artifacts_type
location = var.codebuild_artifacts_location
name = var.codebuild_artifacts_name
namespace_type = var.codebuild_artifacts_namespace_type
packaging = var.codebuild_artifacts_packaging
path = var.codebuild_artifacts_path
encryption_disabled = var.codebuild_artifacts_encryption_disabled
}
}
¿Dónde está la magia?
Primero, en el tipo de datos null
, el cual le dice a Terraform que ignore el atributo si la función lookup
no encuentra el índice en el map.
Veamos las primeras línea de la definición del bloque content
, que es lo que se subsituye en el bloque artifacts
en cada iteración:
content {
type = artifacts.value.type
encryption_disabled = lookup(artifacts.value, "encryption_disabled", null)
location = lookup(artifacts.value, "location", null)
...
Acá se puede apreciar que:
- El atributo
type
siempre se define acorde al valor deartifacts.value.type
. Es decir, no se toma del map. - El atributo
encryption_disabled
se asigna si se consigue en el map algo comoartifacts.value['encryption_disabled']
. Si este ínidce no existe se le asiganull
y por ende no será tomado en cuenta pot Terraform. - Y es igual para el resto de los atributos, como por ejemplo
location
.
Esto permite tener definiciones de bloques dinámicas, pero teniendo en cuenta que se deben definir ciertos valores en conjunto. Por ejemplo, para S3
se debe definir las variables:
var.codebuild_artifacts_type
var.codebuild_artifacts_location
var.codebuild_artifacts_name
var.codebuild_artifacts_namespace_type
var.codebuild_artifacts_packaging
var.codebuild_artifacts_path
var.codebuild_artifacts_encryption_disabled
Mientras que para NO_ARTIFACTS
basta con:
var.codebuild_artifacts_type
Por otro lado, la magia se completa por el hecho de que no se está usando la asingación para el bloque artifacts
sino por la definición del bloque. Es decir, en vez de usar esta asignación:
artifacts = ["${local.codebuild_artifacts}"]
Se usa la definición de bloque dinámico.
dynamic "artifacts" {
for_each = [local.codebuild_artifacts]
content {
...
}
Leave a Comment