# 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

```yaml
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](https://expr-lang.org) 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 bloque `agents:` (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.

---