Bloques dinámicos en Terraform 0.12.x

2 minute read

Terraform


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:

  1. El atributo type siempre se define acorde al valor de artifacts.value.type. Es decir, no se toma del map.
  2. El atributo encryption_disabled se asigna si se consigue en el map algo como artifacts.value['encryption_disabled']. Si este ínidce no existe se le asiga null y por ende no será tomado en cuenta pot Terraform.
  3. 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 S3se 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 {
      ...
  }

Referencias:

Leave a Comment