← Todos los posts

Terraform y Microsoft Fabric en la práctica: un medallion en un solo apply

Demo paso a paso con vídeo: una capacity, 9 workspaces, 36 grupos de Entra ID y sus roles en Microsoft Fabric con Terraform. Plan, apply, drift y destroy.

DevOpsFabricArquitectura

En el artículo anterior vimos la teoría: qué es Infrastructure as Code, cómo se escribe un recurso, qué es el estado y por qué hay que leer el plan antes de cada apply.

Hoy vamos con la demo. El encargo: el medallion de un proyecto que se llama «ventas». Tres capas (bronze, silver y gold) por tres entornos (dev, test y prod), cada workspace con sus cuatro grupos de seguridad y sus cuatro roles. Y la capacity debajo, que tampoco existe todavía.

A mano serían nueve workspaces, 36 grupos y 36 asignaciones de roles. Con Terraform es un apply.

Este es el vídeo de la demo que hicimos Alfonso Ming y yo en la NetCoreConf.

El vídeo no tiene sonido: te explico cada paso aquí debajo.

El código está en github.com/marlizana/FabricAsCode.


Paso 0: no hay nada

Antes de empezar, comprobamos que el punto de partida está vacío:

  • Fabric: filtras la lista de workspaces por «ventas». No sale ninguno.
  • Azure: en Fabric capacities no hay ninguna capacity del proyecto.
  • Entra ID: buscas los grupos sg-fbc-ventas. Ninguno.

Paso 1: el código de Terraform, de arriba abajo

El repo tiene esta pinta:

FabricAsCode/
├── versions.tf          # providers y versiones
├── main.tf              # conecta las piezas
├── variables.tf
├── outputs.tf
├── environments/        # un .tfvars por escenario
├── modules/
│   ├── capacity/
│   ├── workspace-with-roles/
│   └── templates/medallion-cicd/
└── scripts/demo.ps1     # atajos para la demo

versions.tf: los providers

terraform {
  required_providers {
    azurerm = { source = "hashicorp/azurerm", version = "~> 4.0" }
    azuread = { source = "hashicorp/azuread", version = "~> 3.0" }
    fabric  = { source = "microsoft/fabric",  version = "~> 1.14" }
  }
}

provider "azurerm" { features {} }
provider "azuread" {}
provider "fabric" {}

azurerm crea la capacity, azuread los grupos y fabric los workspaces y los roles. Los bloques provider van vacíos a propósito: las credenciales del service principal llegan por variables de entorno.

environments/ventas.tfvars: el encargo

capacity_name = "fbcventas"
capacity_sku  = "F2"
region        = "spaincentral"

template     = "medallion-cicd"
project_name = "ventas"
layers       = ["bronze", "silver", "gold"]
environments = ["dev", "test", "prod"]

admin_group_members = [
  "mar@contoso.onmicrosoft.com",
  "alfonso@contoso.onmicrosoft.com",
]

Aquí está todo lo que cambia de un proyecto a otro: el tamaño de la capacity, la región, las capas, los entornos y quién entra en los grupos Admin. Para un proyecto nuevo se copia este fichero y el código no se toca.

main.tf: une las piezas

Llama al módulo de la capacity y al template medallion-cicd, y le pasa al template el ID de la capacity.

modules/capacity/main.tf: la capacity de Fabric

Un resource group y un azurerm_fabric_capacity con SKU F2. Y un detalle que me costó un plan fallido:

# El ID de Azure no es el ID de Fabric: esta data source hace de puente.
data "fabric_capacity" "this" {
  display_name = azurerm_fabric_capacity.this.name
}

azurerm devuelve el ID de recurso de Azure, pero un workspace espera el GUID de Fabric. La data source busca la capacity por nombre y devuelve el ID que necesita el workspace.

modules/templates/medallion-cicd/main.tf: las combinaciones

locals {
  workspace_specs = {
    for pair in setproduct(var.layers, var.environments) :
    "${pair[0]}-${pair[1]}" => { layer = pair[0], env = pair[1] }
  }
}

module "workspace" {
  source   = "../../workspace-with-roles"
  for_each = local.workspace_specs

  workspace_name  = "${var.project_name}-${each.value.layer}-${each.value.env}"          # ventas-bronze-dev
  ad_group_prefix = "sg-fbc-${var.project_name}-${each.value.layer}-${each.value.env}"   # sg-fbc-ventas-bronze-dev
  capacity_id     = var.capacity_id
  admin_members   = var.admin_members
}

setproduct combina las dos listas y salen nueve parejas capa-entorno. Para cada una se llama al mismo módulo con su nombre y su prefijo de grupos.

modules/workspace-with-roles/main.tf: workspaces, grupos y roles

resource "fabric_workspace" "this" {
  display_name = var.workspace_name
  capacity_id  = var.capacity_id
}

resource "azuread_group" "roles" {
  for_each         = var.roles   # Admin, Contributor, Member, Viewer
  display_name     = "${var.ad_group_prefix}-${lower(each.value)}"
  security_enabled = true
  members          = each.value == "Admin" ? var.admin_members : null
}

resource "fabric_workspace_role_assignment" "this" {
  for_each     = azuread_group.roles
  workspace_id = fabric_workspace.this.id
  role         = each.key
  principal    = { id = each.value.object_id, type = "Group" }
}

Un workspace, un grupo por rol y una asignación que ata cada grupo a su rol. Este módulo, llamado nueve veces, es el que genera los 36 grupos y las 36 asignaciones.


Paso 2: terraform plan

Con las credenciales en variables de entorno:

terraform init
terraform plan "-var-file=environments/ventas.tfvars" "-out=ventas.tfplan"

Ojo: en Windows PowerShell 5.1, entrecomilla los argumentos que llevan un punto. Sin comillas, -var-file=environments/ventas.tfvars se parte en dos y Terraform se queja de «Too many command line arguments».

En el plan conviene pararse en cuatro sitios: la capacity, un workspace, un grupo y una asignación de rol. Y luego en la última línea:

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

Las 84 son nueve workspaces, 36 grupos, 36 asignaciones, la capacity, su resource group y el sufijo aleatorio de su nombre. Ningún cambio y ningún borrado, que es lo que esperas la primera vez.


Paso 3: terraform apply

terraform apply ventas.tfplan

Primero se crea la capacity, que tarda un par de minutos. Después, en paralelo, los grupos y los workspaces. Al final:

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

Mientras corre, da tiempo a un café.


Paso 4: lo que ha creado

  • Azure: la capacity, en su propio resource group.
  • Entra ID: buscas sg-fbc-ventas y salen 36 grupos. En sg-fbc-ventas-silver-prod-admin, pestaña Members, estamos Alfonso y yo.
  • Fabric: filtras por «ventas» y salen los nueve workspaces. En ventas-silver-prod, Manage access muestra los cuatro grupos, cada uno con su rol. Y en Workspace settings → License, la capacity.

Los nueve workspaces siguen el mismo patrón de nombres y tienen exactamente los mismos permisos.


Paso 5: volver a lanzar el plan

¿Qué pasa si vuelvo a hacer plan sin cambiar nada?

No changes. Your infrastructure matches the configuration.

Nada. Como contaba en la teoría, apply se comporta como un MERGE: lo puedes lanzar diez veces y sigues teniendo nueve workspaces.


Paso 6: drift, alguien ha tocado el portal

Ahora hago de compañera con prisa: entro en ventas-silver-prod, Manage access, y quito el grupo sg-fbc-ventas-silver-prod-viewer. «Un momentito, para probar».

Plan otra vez:

  # module.medallion_cicd[0].module.workspace["silver-prod"].fabric_workspace_role_assignment.this["Viewer"] will be created
  + resource "fabric_workspace_role_assignment" "this" { ... }

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

Terraform ha consultado Fabric, ha visto que esa asignación ya no existe y propone volver a crearla. Un apply y el grupo vuelve a tener su rol.


Paso 7: un entorno más en una línea

¿Y si mañana hace falta QA? Se añade al .tfvars:

environments = ["dev", "test", "qa", "prod"]

Y el plan:

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

Tres workspaces (bronze, silver y gold de QA), doce grupos y doce asignaciones, con el mismo patrón que el resto. Los entornos que ya existían no se tocan.


Paso 8: terraform destroy

Cuando el proyecto, la demo o la prueba de concepto se acaba:

terraform plan -destroy "-var-file=environments/ventas.tfvars" "-out=borrar.tfplan"
terraform apply borrar.tfplan

Lee el plan antes: tiene que decir 84 to destroy y nada más. Se borran los workspaces, los grupos y la capacity, y con ella deja de facturarse.

Ojo: con la capacity pausada, el provider de Fabric no deja hacer ni plan, ni apply, ni destroy. Reanúdala antes y espera a que esté Active.


El script de la demo

En el repo verás scripts/demo.ps1. Envuelve estos mismos comandos para no tener que teclearlos delante de 200 personas.

AtajoQué hace
. .\scripts\demo.ps1 envCarga las credenciales en la sesión (con el punto delante)
.\scripts\demo.ps1 initterraform init y el workspace de estado
.\scripts\demo.ps1 planPlan con el .tfvars del escenario
.\scripts\demo.ps1 applyPlan a fichero, confirmación y apply de ese fichero
.\scripts\demo.ps1 driftPlan para ver qué cambió fuera de Terraform
.\scripts\demo.ps1 qa / sin-qaAñade o quita el entorno de QA
.\scripts\demo.ps1 pausa / reanudaPausa o reanuda la capacity
.\scripts\demo.ps1 destroyPlan de borrado, confirmación y destroy

Antes y después

AntesDespués
Una tarde de clics por proyectoUn apply de unos minutos
Un Word desactualizado como documentaciónEl código describe cómo está la plataforma
QA y pro parecidos, pero no igualesLos entornos salen del mismo módulo
«¿Quién quitó este rol?» sin respuestaEl drift aparece en el siguiente plan

El siguiente proyecto empieza copiando un .tfvars en vez de abriendo el portal. Si lo pruebas con el repo, cuéntame en los comentarios qué tal te ha ido.

Pris, mi gata, ha visto el vídeo entero y su única aportación ha sido sentarse en el teclado durante el apply. Por suerte ya estaba aplicando el plan guardado.


Enlaces de interés

¿Te ha servido? Aplaude, sin cuenta. Puedes hacerlo varias veces.

Comentarios