Pandora Plugin Executor
Ejecutor genérico de plugins para Pandora FMS.
- Introducción
- Matriz de compatibilidad
- Prerrequisitos
- Parámetros
- Ejecución manual
- Configuración en Pandora FMS
- Agentes y módulos generados por el plugin
- Plantillas incluidas
- Referencia de plantillas
- Crear tus propias plantillas
- Referencia rápida del lenguaje de expresiones
- Ejemplo práctico: sustituir un plugin real
- Lista de verificación de plantillas (para humanos y generadores de IA)
Introducción
pandora-plugin_exec es un único binario en Go que sustituye a los plugins por tecnología. Una plantilla YAML describe el trabajo a realizar:
- Tareas a ejecutar — peticiones HTTP o ejecuciones de comandos locales.
- Filtros (jq o regexp) que extraen valores de la salida de una tarea hacia variables.
- Agentes y módulos construidos a partir de esas variables (incluyendo expresiones aritméticas).
- Un método de transferencia que entrega los datos resultantes a Pandora FMS.
El ejecutor ejecuta la plantilla una vez y termina (one-shot). Añadir soporte para una nueva tecnología significa escribir una nueva plantilla YAML — no un plugin nuevo, ni un lenguaje nuevo, ni un pipeline de empaquetado nuevo. Está pensado para usarse junto con el agente de Pandora FMS.
template.yml ─▶ ejecutar tareas ─▶ filtrar salidas en variables ─▶ renderizar módulos ─▶ entregar
-
Versión:
0.1.0(pandora-plugin_exec --version).
Audiencia: personas que escriben plantillas y agentes de IA que las generan. Todo lo necesario para escribir una plantilla válida está en este archivo.
De serie, el plugin incluye plantillas ya construidas bajo templates/: ejemplos de aprendizaje y sustitutos directos de plugins de agente que antes venían con el agente. Consulta Plantillas incluidas.
Matriz de compatibilidad
Por ahora solo se ha probado Linux. Windows está planificado, pero todavía no se ha probado.
| Plataforma / runtime | Estado |
|---|---|
| Linux amd64 | Probado |
| Linux arm64 | No establecido |
| Windows amd64 | No establecido |
| macOS | No establecido |
El binario entregado es estático, por lo que no tiene dependencias de runtime en el host.
Prerrequisitos
Para ejecutar el binario:
- No se requiere runtime de Go, ni
jqexterno, ni Perl/Python — el binario es estático e incorpora el motor jq (gojq). - Se usa
/bin/shpara ejecutar los comandos de las tareaslocal, así que los comandos referenciados por la plantilla (df,ss,awk, archivos de/proc, etc.) deben existir en el host.
Por modo de transferencia:
| Modo | Requisito |
|---|---|
agent_plugin |
Un agente de software de Pandora FMS que pueda invocar el binario mediante module_plugin |
tentacle |
El binario tentacle_client (en $PATH, o su ruta configurada mediante transfer.tentacle.binary) y acceso de red al servidor de Pandora FMS (puerto por defecto 41121) |
local |
Permiso de escritura sobre el directorio de destino (transfer.local.directory) |
Parámetros
El ejecutor tiene tres superficies de configuración:
- Flags de CLI (esta sección).
-
Parámetros en tiempo de ejecución —
argsposicionales yparamscon nombre, pasados después de los flags (esta sección). - La plantilla YAML — tareas, agentes/módulos y transferencia. Referencia completa en Referencia de plantillas.
Flags de CLI
pandora-plugin_exec -t <plantilla.yml> [flags] [args...] [clave=valor...]
| Flag | Efecto |
|---|---|
-t, --template <ruta> |
Plantilla a ejecutar (obligatorio) |
--dry-run |
Imprime el XML generado por stdout, no transfiere |
--strict |
Aborta en el primer fallo de tarea en lugar de omitir |
-v, --verbose |
Registro de depuración en stderr (resultados de tareas, omisiones, avisos) |
--version |
Imprime la versión y sale |
| Código de salida | Significado |
|---|---|
| 0 | Éxito |
| 1 | Error de parseo/validación de la plantilla |
| 2 | Fallo de tarea bajo --strict |
| 3 | Error de transferencia |
Los flags van antes de los argumentos: el parseo de flags se detiene en el primer token que no es flag, así que los flags deben preceder a los argumentos (pandora-plugin_exec -t tpl.yml --dry-run sda1, no ... sda1 --dry-run). Un argumento que empieza por guion se rechaza con un error claro en lugar de tratarse silenciosamente como posicional.
Parámetros en tiempo de ejecución
Los tokens que quedan después de los flags alimentan dos variables reservadas, sembradas en el almacén antes de que se ejecute ninguna tarea:
-
args— lista ordenada de tokens posicionales:{{ args[0] }},{{ len(args) }},for_each: args. -
params— mapa de tokens con nombre:{{ params.host }},{{ params.threshold + 5 }}.
Reglas de parseo:
- Cada token se divide por el primer
=sin escapar. Un=inmediatamente precedido por\es un literal escapado y no divide. - El token tiene nombre solo si la clave candidata (tras desescapar
\=→=) coincide con[A-Za-z_][A-Za-z0-9_]*. En caso contrario, todo el token es posicional.
Todo token se clasifica — no hay casos de error. Claves con nombre duplicadas: gana la última. Los valores con aspecto numérico se convierten en números, así que la aritmética funciona con ellos. Las variables de tarea no pueden llamarse args ni params (reservadas); for_each: args sí es válido.
Trampa del escapado en shell: bash consume \= antes de que el ejecutor lo vea. Protege la barra invertida con comillas simples ('query\=a=b') o duplícala (query\\=a=b).
| Token | Se convierte en |
|---|---|
sda1 |
args[0] = "sda1" |
90 |
args[0] = 90 (número) |
host=db01 |
params.host = "db01" |
threshold=90 |
params.threshold = 90 (número) |
conn=user=a;pw=b |
params.conn = "user=a;pw=b" (solo el primer = divide) |
'query\=a=b' |
posicional "query=a=b" (la clave contendría = — no es un identificador) |
=foo |
posicional "=foo" (clave vacía) |
k=1 k=2 |
params.k = 2 (gana la última) |
Parámetros opcionales
Los parámetros en tiempo de ejecución fallan de forma explícita, no silenciosa: referenciar una variable no definida no renderiza una cadena vacía — el módulo se omite con un aviso, para proteger contra erratas. Por tanto, un {{ params.x }} a secas omite el módulo cuando x no se pasó. Para hacer opcional un parámetro, elige un patrón explícito:
| Patrón | Efecto |
|---|---|
{{ params.missing ?? "default" }} |
Renderiza un valor por defecto cuando falta la clave |
when: "'k' in params" |
Incluye el módulo solo cuando se pasó la clave |
{{ len(args) > 1 ? args[1] : "all" }} |
Protege un acceso posicional según el tamaño |
Parámetros aceptados por las plantillas incluidas
| Plantilla | Parámetro | Por defecto | Efecto |
|---|---|---|---|
templates/df_used.yml |
include |
— | Fuerza monitorizar un montaje cuyo fstype está fuera de la lista por defecto (coincidencias exactas de montaje separadas por comas) |
templates/df_used.yml |
exclude |
— | Nunca monitoriza un montaje — exclude siempre gana sobre include |
templates/pandora_netusage.yml |
include |
— | Monitoriza solo estas interfaces (nombres exactos separados por comas) |
templates/pandora_netusage.yml |
exclude |
— | Nunca monitoriza una interfaz — exclude siempre gana sobre include |
templates/pandora_mem_used.yml |
mem_critical |
95 |
Umbral crítico para el porcentaje de Memory_Used |
templates/pandora_mem_used.yml |
swap_critical |
95 |
Umbral crítico para el porcentaje de Swap_Used |
Ejecución manual
Formato de ejecución
pandora-plugin_exec -t <plantilla.yml> [flags] [args...] [clave=valor...]
Ejemplos
Ejecuta una plantilla incluida sin enviar nada (el binario pandora-plugin_exec se entrega ya compilado):
pandora-plugin_exec -t templates/df_used.yml --dry-run
Deberías ver bloques XML <module> en stdout, un par por sistema de archivos montado.
Ejecuciones reales con parámetros en tiempo de ejecución:
pandora-plugin_exec -t templates/df_used.yml # lista de fstype por defecto
pandora-plugin_exec -t templates/df_used.yml exclude=/DB,/snap # descarta dos montajes
pandora-plugin_exec -t templates/df_used.yml include=/mnt/zfs # monitoriza también un montaje zfs
pandora-plugin_exec -t templates/pandora_netusage.yml # todas las interfaces
pandora-plugin_exec -t templates/pandora_netusage.yml exclude=lo,docker0 # descarta loopback y docker0
pandora-plugin_exec -t templates/pandora_netusage.yml include=eth0,ens33 # solo eth0 + ens33
pandora-plugin_exec -t templates/pandora_netusage.yml include=lo exclude=lo # exclude gana → 0 bytes
pandora-plugin_exec -t templates/pandora_mem_used.yml # valores por defecto
pandora-plugin_exec -t templates/pandora_mem_used.yml mem_critical=90 swap_critical=85
Modo verbose
-v / --verbose escribe el registro de depuración en stderr, dejando stdout limpio para el XML generado. Informa de los resultados por tarea (variables almacenadas), módulos omitidos y avisos:
pandora-plugin_exec -t templates/df_used.yml --dry-run -v
No hay archivo de log ni nivel de log; la salida verbose es un único flujo de stderr.
Configuración en Pandora FMS
El ejecutor es one-shot: ejecuta la plantilla una vez y termina. La planificación es externa.
| Escenario | Cómo |
|---|---|
| Como plugin de agente | La plantilla usa transfer.mode: agent_plugin; configuración del agente: module_plugin /usr/bin/pandora-plugin_exec -t /etc/pandora/templates/df_used.yml |
| Desde cron / lado servidor | La plantilla usa el modo tentacle o local; crontab: */5 * * * * pandora-plugin_exec -t /etc/pandora/templates/myapp.yml |
| Ejecutándose en el propio servidor de Pandora FMS | Modo local apuntando al directorio de datos entrantes (/var/spool/pandora/data_in) |
| Depurando una plantilla | pandora-plugin_exec -t plantilla.yml --dry-run -v |
No hay asistente ni paquete .disco para este plugin: la instalación consiste en colocar el binario (y las plantillas) donde el agente/cron pueda alcanzarlos, y luego conectar la línea module_plugin o la entrada de crontab mostradas arriba.
Agentes y módulos generados por el plugin
Agentes. Cada bloque agents: se convierte en un documento XML <agent_data> (en los modos tentacle / local). La identidad del agente sale directamente de la plantilla: name, alias, parent_agent_name, description, version, os_name, os_version, timestamp, address, group, interval, agent_mode. En modo agent_plugin no se emite cabecera de agente — solo fragmentos <module> — y con varios bloques agents: cada nombre de módulo se prefija con <nombre_agente> - <nombre_módulo> (separador configurable mediante transfer.module_prefix_separator) para que los módulos no colisionen bajo el único agente real.
Módulos. Un módulo por bloque de módulo resuelto (un bloque for_each se resuelve en un módulo por elemento). El tipo de módulo es el que declare la plantilla (generic_data, generic_data_inc, generic_data_string, async_data, ...) — el ejecutor reenvía los campos del módulo al servidor de datos sin modificarlos. La lista completa de campos de módulo admitidos está en Referencia de plantillas.
Una tarea fallida (error de ejecución, estado HTTP fuera de 2xx, filtro con 0 coincidencias) deja su variable sin definir y registra un aviso; un módulo que referencia una variable sin definir se omite con un aviso y los módulos restantes se entregan igualmente. --strict convierte cualquiera de lo anterior en un aborto (código de salida 2).
Plantillas incluidas
El plugin incluye plantillas ya construidas bajo templates/, divididas en dos grupos.
Ejemplos
| Plantilla | Propósito |
|---|---|
templates/http_api_example.yml |
Encadenamiento de API HTTP con autenticación por token (login → token → petición autenticada) |
templates/local_commands_example.yml |
Comandos locales como plugin de agente, mostrando todas las formas de resultado de regexp |
Sustitutos de plugins de agente heredados
| Plantilla | Sustituye a |
|---|---|
templates/df_used.yml |
pandora_df_used_go — espacio usado del sistema de archivos |
templates/pandora_mem_used.yml |
pandora_mem_used — memoria y swap |
templates/pandora_netusage.yml |
pandora_netusage — uso de red |
Referencia de plantillas
Una plantilla es un archivo YAML con tres secciones: tasks, agents (o modules de nivel superior) y transfer. El parseo es estricto: un nombre de campo desconocido es un error, así que las erratas fallan rápido.
Ejemplo mínimo completo
tasks:
- name: disk
type: local
local: { command: "df --output=pcent / | tail -1 | tr -d ' %'" }
variable: disk_pct
agents:
- name: "myhost"
group: "Servers"
interval: 300
modules:
- name: "Disk used"
type: generic_data
data: "{{ disk_pct }}"
unit: "%"
transfer:
mode: tentacle
tentacle: { address: "pandora.example.com" }
tasks: — lista ordenada de ejecuciones
Las tareas se ejecutan secuencialmente, en el orden declarado. Una tarea puede usar variables producidas por tareas anteriores en cualquiera de sus campos de cadena (encadenamiento: login → token → petición autenticada).
| Campo | Obligatorio | Descripción |
|---|---|---|
name |
sí | Identificador único de tarea (usado en los logs) |
type |
sí | request o local |
request |
si type=request | Especificación de petición HTTP (abajo) |
local |
si type=local | Especificación de comando (abajo) |
filter |
no | Filtro de extracción (abajo). Sin él, se almacena la salida bruta recortada |
variable |
no | Nombre bajo el que almacenar el resultado. Debe coincidir con [A-Za-z_][A-Za-z0-9_]* |
request: (HTTP)
| Campo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|
url |
sí | — | URL de destino. Admite {{ }} |
method |
no | GET |
GET, POST, PUT, DELETE, PATCH, HEAD |
headers |
no | — | Mapa de cabecera → valor. Los valores admiten {{ }} |
body |
no | — | Cuerpo de la petición. Admite {{ }} |
timeout |
no | 30 |
Segundos |
skip_tls_verify |
no | false |
Acepta certificados TLS inválidos |
Una respuesta con estado fuera de 2xx es un fallo de tarea. Los cuerpos de respuesta están limitados a 10 MiB.
local: (comando)
| Campo | Obligatorio | Por defecto | Descripción |
|---|---|---|---|
command |
sí | — | Se ejecuta con /bin/sh -c. Admite {{ }}. El $ de shell no se toca (awk '{print $5}' funciona tal cual) |
timeout |
no | 30 |
Segundos; el proceso se mata al expirar |
Un código de salida distinto de cero es un fallo de tarea (stderr se incluye en el log).
filter: — extracción de valores
| Campo | Obligatorio | Descripción |
|---|---|---|
type |
sí | jq o regexp |
expression |
sí | La expresión del filtro |
Cardinalidad del resultado (ambos tipos de filtro): 0 coincidencias → variable sin definir (semántica de fallo de tarea) · 1 coincidencia → escalar · N coincidencias → array.
jq — la salida de la tarea debe ser JSON válido. La expresión se ejecuta con un motor jq incorporado (no se necesita el binario jq). Cada valor que emite la expresión es un elemento: .token → escalar, .items[].name → array, .items[] → array de objetos.
regexp — sintaxis RE2 de Go (sin backtracking, sin lookahead) aplicada a la salida bruta. Por coincidencia:
| Forma del patrón | Cada coincidencia se convierte en |
|---|---|
Sin grupo de captura (cpu\d) |
La coincidencia completa (cadena) |
Un grupo sin nombre ((\d+)%) |
El grupo de captura 1 (cadena) |
2+ grupos sin nombre ((a)(b)(c)) |
Array posicional: [[a, b, c], ...] |
Grupos con nombre ((?P<mount>...)) |
Un objeto: nombre del grupo → texto capturado |
Usa (?m) para patrones multilínea anclados por línea y (?i) para coincidencia sin distinción de mayúsculas.
No mezcles grupos con nombre y sin nombre en la misma regexp si quieres un acceso predecible; la forma con nombre solo expone los campos con nombre.
Coerción de tipos: las cadenas extraídas con aspecto numérico se convierten en números (recursivamente, incluyendo campos de objetos), así que la aritmética funciona con ellas.
Variables y expresiones {{ }}
Cualquier campo de cadena de tareas (después de la tarea que la produce), agentes y módulos puede incluir {{ expresión }}. Las expresiones se evalúan con expr-lang contra el almacén de variables.
| Expresión | Resultado |
|---|---|
{{ token }} |
Valor de la variable |
{{ 100 - disk_pct }} |
Aritmética |
{{ names[0] }} |
Indexado de array |
{{ value.mount }} |
Campo de objeto (dentro de for_each) |
{{ names[index] }} |
Array paralelo sincronizado (dentro de for_each) |
Referenciar una variable no definida es un error → el módulo afectado se omite (o la ejecución aborta bajo --strict).
agents: — un bloque por agente
Obligatorio a menos que uses modules: de nivel superior (ver el modo agent_plugin). Cada agente se convierte en un documento XML <agent_data>.
| Campo | Obligatorio | Descripción |
|---|---|---|
name |
sí | Nombre del agente |
modules |
sí | Lista de bloques de módulo (al menos uno) |
alias |
no | Alias del agente |
parent_agent_name |
no | Agente padre |
description |
no | Descripción |
version |
no | Cadena de versión del agente |
os_name, os_version |
no | Identificación del SO |
timestamp |
no | Sobrescribe la marca de tiempo de los datos |
address |
no | IP/nombre de host |
group |
no | Grupo de destino |
interval |
no | Segundos (entero) |
agent_mode |
no | Modo del agente |
Todos los campos de cadena admiten {{ }}.
modules: — bloques de módulo
Los bloques de módulo viven dentro de un agente, o a nivel superior (solo con transfer.mode: agent_plugin).
Campos de control (semántica del ejecutor, no se envían a Pandora):
| Campo | Descripción |
|---|---|
for_each |
Nombre de variable array. El bloque se expande a un módulo por elemento; {{ value }} e {{ index }} quedan disponibles. Una variable escalar itera como array de 1 elemento. Para grupos de captura sin nombre con 2+ grupos, cada value es un array, así que puedes usar {{ value[0] }}. ¿Varios módulos por elemento? Escribe varios bloques con el mismo for_each |
when |
Expresión booleana expr-lang en bruto (sin {{ }}). Falso → módulo (o elemento) omitido. Dentro de for_each se evalúa por elemento y ve value/index. Ejemplo: when: 'not (value.mount matches "^/DB")' |
Campos de datos — los dos obligatorios más el conjunto completo aceptado por el servidor de datos. Los valores pueden ser números o cadenas en YAML; todos admiten {{ }}:
| Campo | Obligatorio | Descripción |
|---|---|---|
name |
sí | Nombre del módulo |
type |
sí | Tipo de módulo (generic_data, generic_proc, generic_data_string, async_data, ...) |
data |
no | Valor del módulo |
description |
no | Descripción |
unit |
no | Etiqueta de unidad |
interval |
no | Intervalo del módulo |
tags |
no | Etiquetas |
module_group |
no | Grupo de módulo |
module_parent, module_parent_unlink |
no | Relación de módulo padre |
min_warning, max_warning, min_critical, max_critical |
no | Umbrales numéricos |
min_warning_forced, max_warning_forced, min_critical_forced, max_critical_forced |
no | Variantes de umbral forzado |
str_warning, str_critical |
no | Umbrales por coincidencia de cadena |
str_warning_forced, str_critical_forced |
no | Umbrales de cadena forzados |
warning_inverse, critical_inverse |
no | Invertir la lógica del umbral |
min, max |
no | Rango de datos válido |
post_process |
no | Multiplicador aplicado por el servidor |
disabled |
no | Crear deshabilitado |
status |
no | Forzar estado |
timestamp |
no | Sobrescribe la marca de tiempo de los datos |
custom_id |
no | Identificador personalizado |
critical_instructions, warning_instructions, unknown_instructions |
no | Instrucciones del operador |
quiet |
no | Modo silencioso |
min_ff_event, min_ff_event_normal, min_ff_event_warning, min_ff_event_critical |
no | Umbrales FlipFlop |
module_ff_interval, ff_type, ff_timeout, each_ff |
no | Comportamiento FlipFlop |
crontab |
no | Planificación de módulo estilo cron |
extra_data |
no | Carga extra |
alert_templates |
no | Lista de nombres de plantillas de alerta a vincular |
transfer: — entrega
| Campo | Obligatorio | Descripción |
|---|---|---|
mode |
sí | tentacle, local o agent_plugin |
tentacle.address |
para tentacle | Dirección del servidor Pandora |
tentacle.port |
no (por defecto 41121) |
Puerto tentacle |
tentacle.binary |
no | Ruta a tentacle_client si no está en $PATH |
tentacle.extra_args |
no | Argumentos extra del cliente tentacle (lista) |
local.directory |
para local | Directorio donde se escribe el archivo XML .data |
module_prefix_separator |
no (por defecto " - ") |
Separador para el prefijado multi-agente en modo agent_plugin |
| Modo | Salida | Caso de uso |
|---|---|---|
tentacle |
XML <agent_data> completo enviado vía cliente tentacle |
Ejecución remota (cron, Discovery) |
local |
XML <agent_data> completo escrito en un directorio |
Ejecutarse en el propio servidor Pandora (apuntando al directorio de entrada), o depuración |
agent_plugin |
Solo fragmentos <module> impresos en stdout — el agente real añade la cabecera |
Ejecutarse como module_plugin de un agente de software |
Particularidades de agent_plugin:
- Se permite
modules:de nivel superior sin ningún bloqueagents:(y solo se permite en este modo). - Con varios agentes definidos, cada nombre de módulo se prefija con
<nombre_agente> - <nombre_módulo>para que los módulos no colisionen bajo el único agente real.
Crear tus propias plantillas
Una plantilla es solo YAML. Constrúyela de arriba a abajo: decide qué medir (tareas), cómo extraer los números (filtros), qué módulos exponer (agentes/módulos) y cómo entregar (transferencia).
Paso 1 — Parte de un ejemplo incluido
templates/local_commands_example.yml es la plantilla completa más corta y demuestra las tres formas de resultado de regexp. Cópiala y sustituye las tareas:
cp templates/local_commands_example.yml templates/my_template.yml
Paso 2 — Escribe las tareas
Cada tarea ejecuta un comando o una petición HTTP y almacena su salida (opcionalmente filtrada) en una variable.
tasks:
# Un escalar: un valor, sin filtro (se almacena la salida bruta recortada).
- name: uptime_seconds
type: local
local: { command: "awk '{print int($1)}' /proc/uptime" }
variable: uptime
# Un escalar mediante una regexp con una sola captura.
- name: load_avg
type: local
local: { command: "cat /proc/loadavg" }
filter:
type: regexp
expression: '^(\d+\.\d+)'
variable: load1
# Un array de objetos mediante grupos de captura con nombre.
- name: df
type: local
local: { command: "df -kTP" }
filter:
type: regexp
expression: '(?m)^(?P<fs>\S+)\s+(?P<fstype>\S+)\s+(?P<total>\d+)\s+(?P<used>\d+)\s+\d+\s+(?P<pct>\d+)%\s+(?P<mount>.+)$'
variable: filesystems
Paso 3 — Convierte variables en módulos
Una variable escalar alimenta directamente un módulo; un array alimenta bloques for_each (un módulo por elemento).
modules:
- name: "Uptime seconds"
type: generic_data
data: "{{ uptime }}"
- name: "Load average 1m"
type: generic_data
data: "{{ load1 }}"
# Un módulo por sistema de archivos; value.* lee los grupos de captura con nombre.
- for_each: filesystems
when: 'value.fstype != "tmpfs"'
name: "DiskUsed_{{ value.mount }}"
type: generic_data
data: "{{ value.pct }}"
unit: "%"
Dentro de un bloque for_each:
-
{{ value }}es el elemento actual,{{ index }}su posición (base 0). - Los grupos con nombre hacen que cada elemento sea un objeto → accede a los campos como
{{ value.mount }}. - 2+ grupos sin nombre hacen que cada elemento sea un array posicional → accede como
{{ value[0] }},{{ value[1] }}, ...; escribe varios bloques con el mismofor_eachpara emitir varios módulos por elemento.
Paso 4 — Elige el modo de transferencia
transfer:
mode: agent_plugin # salida solo de módulos; ejecutar como plugin de agente de software
transfer:
mode: tentacle # XML de agente completo a un servidor Pandora
tentacle: { address: "pandora.example.com" }
transfer:
mode: local # XML de agente completo escrito en un directorio
local: { directory: "/var/spool/pandora/data_in" }
Paso 5 — Valida antes de entregar
pandora-plugin_exec -t templates/my_template.yml --dry-run -v
--dry-run imprime el XML generado sin transferir; -v muestra qué variables se almacenaron y qué módulos se omitieron. El parser es estricto, así que cualquier errata en un nombre de campo falla inmediatamente.
Errores comunes
-
Los flags después de los argumentos no funcionan. El parseo de flags se detiene en el primer token que no es flag: pon
--dry-runantes de cualquier argumento posicional. -
Las variables no definidas omiten el módulo, no renderizan vacío. Usa
{{ params.x ?? "default" }}owhen: "'x' in params"para parámetros opcionales. -
filter.expressionno es expr. Es jq o una regexp RE2 segúnfilter.type. expr solo se usa dentro de{{ }}y enwhen:. -
Duplica la barra invertida en literales regexp dentro de
when:— los literales de cadena de expr procesan escapes, así que\\dentrega\dal motor de regexp. -
RE2 no tiene lookahead/backtracking — usa
when:para exclusiones (p. ej.when: 'not (value.mount matches "^/DB")').
Referencia rápida del lenguaje de expresiones
Los placeholders {{ }} y las condiciones when: son expresiones expr-lang. Funciones útiles para autores de plantillas:
| Categoría | Funciones / operadores |
|---|---|
| Cadenas | split(s, sep) · join(list, sep) · upper(s) · lower(s) · trim(s) · replace(s, old, new) · contains(s, sub) · startsWith(s, pre) · endsWith(s, suf) · s matches "patrón-re2" |
| Colecciones | x in list · len(list) · list[i] · first(list) · last(list) · filter(list, pred) · map(list, expr) · all/any/none(list, pred) · sort(list) · uniq(list) · concat(a, b) |
| Lógica y nil | a ?? "default" (coalescencia de nil) · obj?.field (encadenamiento opcional) · cond ? a : b (ternario) · 'clave' in map (presencia de clave) |
| Números | + - * / % · abs(x) · min/max(a, b) · sum(list) · avg(list) · int(x) · float(x) · string(x) |
| JSON | toJSON(x) · fromJSON(s) |
En los predicados de filter/map/all/any/none el elemento actual es # (p. ej. filter(filesystems, #.pct > 90)).
No confundas lenguajes: el filter.expression de la tarea no es expr — es jq (vía gojq) o una regexp RE2, según filter.type. expr solo se usa dentro de {{ }} y en when:.
Ejemplo práctico: sustituir un plugin real
templates/df_used.yml sustituye al plugin de agente pandora_df_used_go. El patrón a aprender: una ejecución → array de objetos → N módulos.
tasks:
- name: df
type: local
local: { command: "df -kTP" }
filter:
type: regexp
# Grupos con nombre: cada fila de df se convierte en {fs, fstype, total, used, pct, mount}
expression: '(?m)^(?P<fs>\S+)\s+(?P<fstype>\S+)\s+(?P<total>\d+)\s+(?P<used>\d+)\s+\d+\s+(?P<pct>\d+)%\s+(?P<mount>.+)$'
variable: filesystems
modules:
- for_each: filesystems
when: >
(value.fstype matches "^(?i:adfs|affs|autofs|btrfs|cifs|coda|coherent|efs|ext\\d?|hfs|hfsplus|hpfs|jfs|minix|msdos|ncpfs|nfs4?|ntfs|proc|qnx4|reiserfs|smbfs|sysv|ubifs|udf|ufs|umsdos|usbfs|vfat|xenix|xfs|xiafs)$"
|| value.mount in split(params.include ?? "", ","))
&& not (value.mount in split(params.exclude ?? "", ","))
name: "DiskUsed_{{ value.mount }}"
type: generic_data
data: "{{ value.pct }}"
unit: "%"
- for_each: filesystems
when: >
(value.fstype matches "^(?i:adfs|affs|autofs|btrfs|cifs|coda|coherent|efs|ext\\d?|hfs|hfsplus|hpfs|jfs|minix|msdos|ncpfs|nfs4?|ntfs|proc|qnx4|reiserfs|smbfs|sysv|ubifs|udf|ufs|umsdos|usbfs|vfat|xenix|xfs|xiafs)$"
|| value.mount in split(params.include ?? "", ","))
&& not (value.mount in split(params.exclude ?? "", ","))
name: "DiskUsed_{{ value.mount }} bytes"
type: generic_data
data: "{{ value.used }}"
unit: "bytes"
transfer:
mode: agent_plugin
La regexp captura todas las filas de df; la lista permitida de fstype por defecto vive en when: para que los parámetros en tiempo de ejecución puedan sobrescribirla. Observa el escalar plegado (when: >) y la barra invertida duplicada (\\d): los literales de cadena de expr procesan escapes, así que \\d entrega un \d literal al motor de regexp.
Ejemplo de capturas posicionales
Úsalo cuando quieras el orden de captura en lugar de nombres de campo:
tasks:
- name: fs
type: local
local:
command: |
printf '/dev/sda1 ext4 40 81 /\n/dev/sdb1 xfs 90 12 /data\n'
filter:
type: regexp
expression: '(?m)^(\S+)\s+(\S+)\s+(\d+)\s+(\d+)\s+(.+)$'
variable: filesystems
modules:
- for_each: filesystems
name: "FS {{ value[0] }}"
type: generic_data
data: "{{ value[3] }}"
unit: "%"
transfer:
mode: agent_plugin
Ejemplo de API HTTP
templates/http_api_example.yml demuestra el encadenamiento con autenticación por token: una petición login almacena el token de sesión, y las peticiones posteriores lo incorporan mediante Authorization: "Bearer {{ token }}".
Lista de verificación de plantillas (para humanos y generadores de IA)
- Cada tarea tiene un
nameúnico y su bloque coincide con sutype(request:xorlocal:). - Cada nombre de
variablecoincide con[A-Za-z_][A-Za-z0-9_]*(y no esargs/params). - Las tareas están ordenadas de modo que las variables se producen antes de consumirse.
- O bien
agents:(con ≥1 módulo cada uno) omodules:de nivel superior + modoagent_plugin— nunca ambos. - Cada módulo tiene
nameytype. -
for_eachreferencia una variable que un filtro produce como array; los campos de elemento se acceden como{{ value.field }}para capturas con nombre o{{ value[0] }}para capturas posicionales. -
when:es una expresión en bruto (sin{{ }}); todo lo demás usa placeholders{{ }}. -
transfer.modetiene sus opciones obligatorias (tentacle.address/local.directory). - Validado con
pandora-plugin_exec -t plantilla.yml --dry-run -vantes de entregar.