En nuestros artículos anteriores, cubrimos los fundamentos de Infrastructure as Code con Terraform y Jamf Pro, y luego recorrimos los proveedores de Terraform dedicados para Jamf Protect y la Plataforma Jamf. Si aún no los ha leído, le recomendamos que comience allí — cubren los fundamentos de Terraform, la estructura del proyecto, la gestión del estado y cómo definir recursos de Jamf en código desde cero.
Esta vez, abordamos una pregunta que escuchamos con frecuencia: "Todo esto suena genial, pero ya tengo cientos de políticas, perfiles, scripts y grupos en mi entorno de Jamf. ¿Realmente tengo que reescribir todo eso manualmente?"
La respuesta corta es no. Hemos construido una herramienta llamada jamformer para ayudar con exactamente esto — pero antes de profundizar, es importante ser honesto sobre lo que es y lo que no es.
¿Qué es jamformer?
jamformer es una herramienta de capacitación y aceleración para equipos que adoptan Jamf con Terraform. Se conecta a su instancia de Jamf existente, descubre todo lo que puede encontrar y genera un proyecto de Terraform como punto de partida — un andamio realista del que puede aprender y refinar. Piénselo como un puente entre donde está hoy (un entorno de Jamf completamente configurado gestionado a través de la consola) y donde quiere estar (ese mismo entorno gestionado como código) — pero el puente no es una cinta transportadora. Aún debe caminar sobre él.
Deliberadamente no es:
- Un generador de código de producción. El HCL que produce es un primer borrador. Antes de gestionar infraestructura real con él, espere revisar cada archivo, refactorizar en su propia estructura de módulos, aplicar convenciones de nombres, reforzar el manejo de secretos y lidiar con peculiaridades de desviación del proveedor.
- Un sustituto para aprender Terraform o los proveedores de Jamf. Aplana la curva de aprendizaje; no la elimina.
- Un botón de migración de un solo clic. Los entornos de Jamf grandes o inusuales siempre requerirán criterio humano.
Lo que sí es bueno para:
- Ver qué hay realmente en su instancia de Jamf, expresado en el modelo de recurso propio de los proveedores de Terraform — atributos, referencias cruzadas, peculiaridades del ciclo de vida y todo.
- Arrancar pruebas de concepto, demostraciones, talleres, capacitación interna y sesiones de planificación de migración.
- Darles a los ingenieros un andamio realista para refactorizar en IaC en lugar de mirar a un editor en blanco.
También es de solo lectura. jamformer no modifica, crea ni elimina nada en su instancia de Jamf. Lo único que escribe es un conjunto de archivos de Terraform en su máquina local.
Mantenga este marco en mente para el resto de este artículo. Cuando recorramos el flujo de trabajo y mostremos ejemplos de salida, está viendo material prima para que un ingeniero humano refine — no el artículo terminado.
¿Por qué no solo usar terraform import?
Absolutamente puede, y si ha leído el artículo de Protect, ya ha visto cómo terraform import y terraform query con bloques list pueden llevar recursos existentes bajo gestión.
Pero si ha estado gestionando un entorno de Jamf a través de la consola durante un tiempo, sabrá que hay mucho allí. Importar manualmente todo significa inventariar cada tipo de recurso, escribir cientos de bloques de importación con los IDs de Jamf correctos, descubrir qué política hace referencia a qué script y qué grupo y qué categoría, extraer scripts incrustados y payloads de perfil de configuración en archivos independientes, y lidiar con peculiaridades del proveedor alrededor de valores predeterminados.
Eso es mucho trabajo. Queríamos hacerlo más fácil.
¿Qué soporta?
jamformer funciona con cuatro proveedores de Terraform en la familia de productos Jamf:
| Proveedor | Cómo descubre |
|---|---|
| Jamf Pro (predeterminado) | API de Jamf Pro |
| Jamf Protect | terraform query |
| Jamf Platform | terraform query |
| Jamf Security Cloud (JSC) | Fuentes de datos de Terraform |
Solo para Jamf Pro, eso cubre políticas, scripts, perfiles de configuración, Smart Groups, Static Groups, prestages de computadora, perfiles de dispositivos móviles, atributos de extensión, paquetes, categorías, edificios, departamentos y mucho más — además de configuraciones como Client Check-In, Cloud Distribution Point y configuración de Self Service. La lista autoritativa y siempre actualizada es jamformer -list-resources (opcionalmente filtrada con -provider).
¿Qué sucede cuando lo ejecuta?
Cuando apunta jamformer a su instancia de Jamf, pasa por varios pasos. Recorreremos lo que cada uno hace para que sepa qué esperar.
Primero, descubre sus recursos. Para Jamf Pro, se autentica en su instancia (OAuth2 o autenticación básica) y consulta la API para cada tipo de recurso compatible. Recorre su entorno en orden de dependencia — primero sitios y categorías, luego scripts y paquetes, luego políticas y perfiles — para que pueda asignar las relaciones entre recursos. Para Jamf Protect y Jamf Platform, utiliza terraform query con bloques list (el mismo enfoque que cubrimos en el artículo de Protect) para descubrir recursos directamente a través del proveedor.
A continuación, genera bloques de importación. Para cada recurso descubierto, jamformer escribe un bloque import de Terraform con una etiqueta derivada del nombre del recurso. Los espacios se convierten en guiones bajos, se eliminan los caracteres especiales y, si dos recursos terminan con la misma etiqueta, se añade un sufijo numérico para mantener las cosas únicas. También genera provider.tf, variables.tf y terraform.tfvars configurados para el proveedor correcto.
Así es como podrían verse esos bloques de importación:
import {
to = jamfpro_policy.software_update_enforce
id = "142"
}
import {
to = jamfpro_script.install_rosetta
id = "87"
}
import {
to = jamfpro_category.productivity
id = "5"
}
Luego le pide a Terraform que genere el HCL. jamformer ejecuta terraform plan -generate-config-out (o terraform query -generate-config-out para Protect y Platform), que hace que el proveedor produzca el HCL para cada recurso importado. Dado que el proveedor mismo está haciendo el trabajo pesado aquí, la configuración generada siempre coincide con lo que el proveedor espera.
Si recursos específicos no se importan — sucede, a veces un proveedor no puede leer un recurso en particular de forma limpia — jamformer elimina automáticamente el bloque de importación que falla y lo reintenta. Verá un mensaje sobre qué recursos se omitieron para que pueda ocuparse de ellos más tarde.
Finalmente, limpia la salida. La salida bruta de Terraform es un archivo grande único con valores literales en todas partes. jamformer lo transforma en algo con lo que realmente querría trabajar:
- jamformer reescribe IDs literales de Jamf en referencias de Terraform. Una política que hace referencia a la categoría ID
5se convierte enjamfpro_category.productivity.id, para que Terraform entienda las relaciones entre sus recursos — tal como lo escribiría manualmente. - Los scripts incrustados van a archivos individuales en
support_files/scripts/, los payloads de perfil de configuración asupport_files/macos_configuration_profiles/, los scripts de atributo de extensión asupport_files/extension_attributes/, y las configuraciones de aplicación de dispositivos móviles asupport_files/app_configurations/. jamformer actualiza el HCL con referenciasfile()para que sus scripts y perfiles sean archivos propios que puede diferenciar, revisar y editar de forma independiente. Los tokens de inscripción de dispositivos y compra por volumen utilizan variables de rutafile(var.xxx)— coloca los archivos de token (descargados de Apple Business Manager) en la ruta que especifica en la variable. - jamformer resuelve recursos de icono a URLs de CDN (los iconos no se descargan localmente) y añade un bloque
lifecycle { ignore_changes }para evitar ciclos de destrucción/creación innecesarios en la primera aplicación. - Los atributos opcionales que regresan como
nullse eliminan, los valores conocidos por causar problemas (comocategory_id = -1para recursos sin categorizar) se eliminan, y el archivo grande único se divide en archivos por tipo de recurso —policies.tf,scripts.tf,categories.tfy así sucesivamente. - Una pasada de validación ejecuta
terraform validatepara detectar atributos condicionalmente inválidos — por ejemplo, atributos que solo son válidos para un tipo de CDN específico — y los elimina automáticamente para que no tenga que perseguir errores de esquema manualmente.
Después del post-procesamiento, jamformer escanea la salida en busca de secretos. Los entornos de Jamf a menudo contienen credenciales incrustadas en perfiles de configuración, scripts y atributos de recursos — cosas como contraseñas de enlace de LDAP, credenciales de SMTP, secretos compartidos de Wi-Fi y tokens de API. jamformer utiliza gitleaks con reglas específicas de Jamf para encontrar estos antes de que accidentalmente los confirme en Git.
Cuando jamformer encuentra secretos, muestra un informe agrupado por recurso y atributo. En modo interactivo, puede elegir remediar automáticamente todos los hallazgos, recorrerlos individualmente u omitir completamente la remediación. La remediación mueve secretos a variables de Terraform sensibles — para archivos .tf, el valor del secreto se reemplaza con una referencia var.; para archivos de soporte como scripts y perfiles, el archivo se convierte en una plantilla .tpl utilizando templatefile(). Puede omitir este paso con -skip-secret-scan si prefiere manejar secretos usted mismo.
Comenzar
Si siguió el artículo de introducción, ya debería tener Terraform y un editor de texto configurados. Los pasos aquí son sencillos.
1. Instalar jamformer
Homebrew:
brew install Jamf-Concepts/tap/jamformer
Los binarios precompilados y un instalador .pkg están disponibles en la página de versiones.
No necesita instalar Terraform por separado — jamformer lo descarga automáticamente en un directorio temporal, para que no entre en conflicto con ninguna instalación de Terraform existente en su máquina.
2. Ejecutarlo contra su instancia
La forma más fácil de comenzar es simplemente ejecutar jamformer sin argumentos:
jamformer
Le guiará a través de todo de forma interactiva — qué proveedor desea usar, la URL de su instancia y sus credenciales. Ingresa contraseñas y secretos de cliente como entrada oculta para que no aparezcan en su historial de terminal.
Los URL abreviadas también funcionan aquí — simplemente puede escribir yourinstance y jamformer lo expandirá a yourinstance.jamfcloud.com. Para Protect, your-tenant se expande a your-tenant.protect.jamfcloud.com.
Una vez que se sienta cómodo con la herramienta o quiera secuenciarla, puede configurar credenciales a través de variables de entorno:
# Jamf Pro con OAuth2
export JAMF_CLIENT_ID='your-client-id'
export JAMF_CLIENT_SECRET='your-client-secret'
jamformer -url https://yourinstance.jamfcloud.com
# Jamf Protect
export JAMF_CLIENT_ID='your-client-id'
export JAMF_CLIENT_SECRET='your-client-secret'
jamformer -provider jamfprotect -url https://your-tenant.protect.jamfcloud.com
jamformer solo lee credenciales de variables de entorno o avisos interactivos — nunca indicadores CLI — para evitar filtrar secretos en el historial de shell y listados de procesos. Puede pasar la URL como un indicador (-url) o a través de JAMF_URL.
jamformer no escribe credenciales en terraform.tfvars y genera un .gitignore que excluye archivos tfvars, archivos de estado y el directorio .terraform/.
3. Importar todo a la vez es opcional
Si su entorno es grande y prefiere comenzar pequeño, puede apuntar a tipos de recurso específicos:
# Comience solo con políticas, scripts y categorías
./jamformer -include-resources "policies scripts categories"
# Todo excepto paquetes e iconos
./jamformer -exclude-resources "packages icons"
# Ver todos los nombres de filtro disponibles
./jamformer -list-resources
Cuando un tipo de dependencia no está incluido (digamos que importa políticas pero no categorías), las referencias a esos tipos permanecen como IDs literales en lugar de expresiones de Terraform. Siempre puede ejecutar de nuevo con más tipos más tarde a medida que se sienta cómodo.
Entonces, ¿qué aspecto tiene realmente la salida?
Después de que jamformer termina, tendrá un directorio que se ve algo así:
generated/
.gitignore
provider.tf
variables.tf
terraform.tfvars
policies.tf
policies_import.tf
scripts.tf
scripts_import.tf
categories.tf
categories_import.tf
...
support_files/
scripts/
install_rosetta.sh
set_wallpaper.sh
extension_attributes/
battery_health.sh
macos_configuration_profiles/
wifi_corporate.mobileconfig
filevault_enforce.mobileconfig
device_enrollment_tokens/ (vacío - coloque sus archivos .p7m aquí)
volume_purchasing_tokens/ (vacío - coloque sus archivos .vpptoken aquí)
app_configurations/
managed_browser.xml
Cada tipo de recurso obtiene su propio archivo .tf y un archivo _import.tf correspondiente. El directorio support_files/ contiene scripts, perfiles y configuraciones de aplicación extraídas referenciadas a través de expresiones file(). jamformer crea directorios de token (device_enrollment_tokens/ y volume_purchasing_tokens/) como la ubicación recomendada para sus archivos de token de Apple Business Manager — estos utilizan variables de ruta file(var.xxx) ya que la API no devuelve datos de token.
Modo compacto
De forma predeterminada, jamformer genera un bloque de recurso por objeto — un diseño plano y explícito que es fácil de leer y revisar. Pero si su entorno tiene docenas de recursos similares (categorías, edificios, departamentos), la salida puede parecer repetitiva.
El indicador -compact consolida tipos de recurso elegibles en patrones for_each + locals, produciendo salida que está más cerca de lo que escribiría manualmente:
jamformer -compact
Sin modo compacto, obtendría bloques individuales:
resource "jamfpro_category" "productivity" {
name = "Productivity"
}
resource "jamfpro_category" "security" {
name = "Security"
}
Con modo compacto habilitado, estos se convierten en:
locals {
categories = {
productivity = { name = "Productivity" }
security = { name = "Security" }
}
}
resource "jamfpro_category" "all" {
for_each = local.categories
name = each.value.name
}
jamformer reescribe automáticamente referencias entre recursos para usar el nuevo direccionamiento — jamfpro_category.all["productivity"].id en lugar de jamfpro_category.productivity.id — para que todo permanezca conectado. Actualiza los bloques de importación de la misma manera.
jamformer determina elegibilidad dinámicamente en tiempo de ejecución. Un tipo de recurso califica cuando el archivo contiene dos o más bloques de recurso que comparten todos el mismo conjunto de atributos y ninguno tiene bloques anidados (excepto lifecycle). Los atributos que son idénticos en todas las instancias se convierten en valores literales en el bloque de recurso; solo los valores que varían van al mapa de locales. Esto funciona en los cuatro proveedores.
Si desea un control más fino, puede apuntar a tipos específicos:
# Solo compactar categorías y edificios
jamformer -compact -compact-include "categories buildings"
# Compactar todo lo elegible excepto políticas
jamformer -compact -compact-exclude "policies"
Soporte multi-ambiente
⚠️ Experimental y altamente avanzado. Esta característica se dirige a personas que ya son fluidas en módulos de Terraform, flujos de trabajo de ramas de larga duración y el modelo de recurso del proveedor de Jamf. La salida es más de nivel andamio que el modo de entorno único, no menos — espere editar el módulo generado, las raíces por ambiente y la extracción de variables antes de que alguno de esto sea utilizable. Trátelo como una característica de investigación/capacitación, no como un flujo de trabajo de producción compatible. Si es nuevo en Terraform o aún está trabajando en su primer proyecto de instancia única, comience allí y vuelva a esto más tarde.
Si gestiona múltiples entornos de Jamf Pro — ensayo y producción, o dev y prod — probablemente ha pensado en cómo mantenerlos sincronizados. jamformer puede generar un proyecto de Terraform estructurado alrededor de un módulo compartido con directorios raíz por ambiente, diseñado para un flujo de trabajo de rama de Git donde cada rama de larga duración representa un ambiente.
export JAMF_URL_STAGING=https://staging.jamfcloud.com
export JAMF_CLIENT_ID_STAGING=xxx
export JAMF_CLIENT_SECRET_STAGING=xxx
export JAMF_URL_PROD=https://prod.jamfcloud.com
export JAMF_CLIENT_ID_PROD=xxx
export JAMF_CLIENT_SECRET_PROD=xxx
jamformer -multi-env "staging prod"
jamformer ejecuta descubrimiento contra cada ambiente de forma independiente, hace coincidir recursos por nombre y produce salida que se ve así:
generated/
modules/jamf/ # definiciones de recursos compartidas
policies.tf # recursos presentes en todos los ambientes
scripts.tf
categories.tf
policies_staging_only.tf # recursos solo en ensayo
variables.tf # variables de entrada del módulo
providers.tf
support_files/ # archivos idénticos en todos los ambientes
scripts/
macos_configuration_profiles/
environments/
staging/
main.tf # configuración del proveedor + llamada del módulo
backend.tf # marcador de posición para su backend de estado
variables.tf
terraform.tfvars # valores específicos del ambiente
imports.tf # bloque de importación con prefijo module.jamf.
support_files/ # archivos que difieren del otro ambiente
prod/
main.tf
backend.tf
variables.tf
terraform.tfvars
imports.tf
support_files/
El módulo compartido en modules/jamf/ contiene definiciones de recursos comunes a todos los ambientes. Donde los valores de atributo difieren entre ambientes — una política con un category_id diferente o un perfil con una description diferente — jamformer extrae esos valores a variables del módulo y los configura por ambiente en terraform.tfvars. Las referencias entre recursos permanecen como expresiones de Terraform propias (p. ej., jamfpro_category.productivity.id), para que las relaciones entre recursos permanezcan intactas.
Esto funciona mejor cuando mantiene intencionalmente sus ambientes sincronizados — las mismas políticas, scripts, perfiles y grupos implementados en instancias. Cuanto más coincidan, más limpia será la salida. Dicho esto, jamformer maneja las diferencias comunes del mundo real:
- Mismo recurso, configuración diferente (p. ej., una política con una categoría o frecuencia diferente en ensayo vs prod) — los atributos que difieren se convierten en variables del módulo, con los valores de cada ambiente en su propio
terraform.tfvars. jamformer ordena las variables alfabéticamente y las agrupa por tipo de recurso para legibilidad. - Recursos que existen en algunos ambientes pero no en otros (p. ej., una política de prueba solo en ensayo) — jamformer separa estos en archivos claramente etiquetados como
policies_staging_only.tfdentro del módulo. En la rama de otro ambiente, simplemente elimina esos archivos. - Archivos de soporte que difieren (p. ej., un script con contenido ligeramente diferente en instancias) — los archivos idénticos en todos los ambientes viven en el directorio
support_files/del módulo compartido. Cuando los archivos difieren, jamformer los coloca en el directoriosupport_files/de cada ambiente y los pasa al módulo como variables.
Cada directorio de ambiente tiene su propio marcador de posición backend.tf, su propio estado y sus propios bloques de importación con prefijo module.jamf.. Esto significa que puede ejecutar terraform plan y terraform apply de forma independiente por ambiente, en paralelo si es necesario, sin ningún riesgo de que el estado de un ambiente interfiera con otro.
jamformer utiliza el primer ambiente listado como la fuente de verdad para las definiciones de recursos compartidas; use -source-env para anular esto. Hace coincidir recursos por nombre, por lo que una política llamada "Software Update - Enforce" en ensayo coincidirá con una política con el mismo nombre en prod independientemente de sus IDs de Jamf.
El flujo de trabajo previsto es confirmar la salida en un repositorio, crear una rama de larga duración por ambiente, configurar cada rama con backend.tf con el backend de estado apropiado y configurar CI para ejecutar terraform apply en el directorio correspondiente de environments/<env>/ cuando el código llega a esa rama. Los cambios fluyen desde ramas de característica hacia el primer ambiente, luego se promocionan a través de ramas a través de solicitudes de extracción.
Cuanto más se desvíen sus ambientes, más variables y archivos específicos del ambiente contendrá la salida. Si sus instancias son sustancialmente diferentes, puede ser mejor ejecutar jamformer por separado para cada una y mantener proyectos de Terraform independientes.
Trabajar con la salida generada
Lo dijimos en la parte superior y lo diremos de nuevo aquí, porque esta es la parte que más importa: la configuración generada no es Terraform listo para producción. jamformer es una herramienta de capacitación y aceleración. Le da un punto de partida realista en el viaje hacia la gestión de su entorno de Jamf como código — no un atajo que lo lleve allí inmediatamente. Planifique pasar tiempo real revisando, refinando y comprendiendo lo que produjo antes de que nada controle cambios de infraestructura real.
La salida es intencionalmente plana — un bloque de recurso por objeto, un archivo por tipo, sin módulos. En la práctica, probablemente querrá refactorizarla en una estructura de proyecto modular, aprovechar características de HCL como for_each y locals para reducir la repetición, convertir cosas como URL de instancia y configuraciones comunes en variables para su ambiente, y organizar recursos de una manera que tenga sentido para su equipo. jamformer le da el material prima para hacer eso; cómo lo forma es cosa suya. Ese trabajo de formación es donde sucede la mayoría del aprendizaje real de Terraform — y es toda la razón por la que jamformer existe en la forma en que existe.
Con eso en mente, aquí está el flujo de trabajo que recomendamos.
Los mismos comandos que aprendió en la introducción se aplican aquí:
cd generated
terraform plan # Revisar el plan de importación, verificar errores del proveedor
Espere diffs en su primer plan. Es normal ver diferencias entre lo que Terraform genera y lo que realmente está en su instancia de Jamf. Algunos atributos pueden mostrar valores que no coinciden con lo que ve en la consola, algunos campos opcionales pueden tener predeterminados que el proveedor rellena que difieren de la configuración activa, y algunos recursos pueden tener errores de validación debido a campos de solo escritura que el proveedor no puede leer. Estas son peculiaridades de nivel de proveedor, no errores en jamformer, y necesitarán atención manual.
Trabaje a través de la salida del plan, corrija los errores y ajuste los atributos hasta que esté satisfecho con lo que Terraform está reportando. Este proceso de revisión es donde aprenderá más sobre cómo el proveedor representa sus recursos — y es un paso importante antes de entregar el control a Terraform.
Una vez que el plan se vea limpio:
terraform apply # Importar recursos en estado (se le pedirá confirmación)
Ejecutar terraform apply aquí importa sus recursos existentes en el estado de Terraform. No cambia nada en Jamf — solo le dice a Terraform "estos recursos ya existen y así es como se ven."
Una vez que ha aplicado con éxito, los bloques de importación han cumplido su función. Puede eliminarlos:
rm *_import.tf
Luego ejecute terraform plan de nuevo. Idealmente no ve cambios. Si aparecen diferencias, estos son casos donde los predeterminados del proveedor no coinciden exactamente con lo que realmente está en Jamf — ajuste el HCL generado hasta que obtenga un plan limpio.
En este punto, tiene un proyecto de Terraform que representa su entorno de Jamf. Confírmelo en Git y está listo para comenzar el trabajo real: refinar la configuración, organizarla para que se ajuste al flujo de trabajo de su equipo y gradualmente pasar a realizar cambios a través de solicitudes de extracción, revisión de código y terraform apply en lugar de la consola.
Cosas a tener en mente
De forma predeterminada, jamformer descarga paquetes del Cloud Distribution Point. Para entornos grandes con muchos paquetes, esto puede tomar un tiempo. Use -skip-package-downloads para omitir este paso y manejo paquetes por separado.
El escaneo de secretos se ejecuta automáticamente después de la generación. Si prefiere manejar la gestión de secretos usted mismo o está ejecutando en un ambiente sin interfaz, use -skip-secret-scan para omitirlo. Incluso si omite el escaneo incorporado, le recomendamos encarecidamente ejecutar alguna forma de detección de secretos antes de confirmar la salida en Git.
El modo compacto (-compact) es puramente cosmético — cambia la forma del HCL pero no lo que Terraform hace. Si no está seguro si lo desea, comience sin él e intente en una segunda ejecución. También puede usar -compact-include y -compact-exclude para apuntar a tipos de recurso específicos mientras deja otros planos.
El provider.tf generado incluye una restricción de versión mínima (>= X.Y.Z) basada en la versión del proveedor que Terraform descargó. Si prefiere fijar una versión exacta, use -provider-version 1.2.3.
Jamf Protect y Jamf Platform requieren Terraform 1.14+ para compatibilidad con terraform query. jamformer descarga esto automáticamente, pero vale la pena saberlo si está proporcionando su propio binario de Terraform a través de -terraform-path.
Lo próximo
Si ha estado siguiendo esta serie, ahora ha visto cómo escribir Terraform para Jamf desde cero, cómo gestionar la configuración de Jamf Protect y Jamf Platform como código con un proveedor dedicado, y ahora cómo llevar un ambiente existente bajo gestión de Terraform sin empezar de nuevo. Hay más por venir — cubriremos temas adicionales en publicaciones futuras a medida que los proveedores y herramientas continúen evolucionando.
Estamos trabajando activamente en jamformer y recopilando comentarios. Si lo prueba, nos gustaría escuchar qué funciona y qué no. Abra un problema en el repositorio de GitHub o comuníquese en Jamf Nation.