Creating your own templates
A template is just YAML. Build it top-down: decide what to measure (tasks), how to extract the numbers (filters), which modules to expose (agents/modules), and how to deliver (transfer).
Step 1 — Start from a shipped example
templates/local_commands_example.yml is the shortest complete template and demonstrates all three regexp result shapes. Copy it and replace the tasks:
cp templates/local_commands_example.yml templates/my_template.yml
Step 2 — Write the tasks
Each task runs one command or one HTTP request and stores its (optionally filtered) output in a variable.
tasks:
# A scalar: one value, no filter (trimmed raw output is stored).
- name: uptime_seconds
type: local
local: { command: "awk '{print int($1)}' /proc/uptime" }
variable: uptime
# A scalar via a single-capture regexp.
- name: load_avg
type: local
local: { command: "cat /proc/loadavg" }
filter:
type: regexp
expression: '^(\d+\.\d+)'
variable: load1
# An array of objects via named capture groups.
- 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
Step 3 — Turn variables into modules
A variable that is a scalar feeds one module directly; an array feeds for_each blocks (one module per element).
modules:
- name: "Uptime seconds"
type: generic_data
data: "{{ uptime }}"
- name: "Load average 1m"
type: generic_data
data: "{{ load1 }}"
# One module per filesystem; value.* reads the named capture groups.
- for_each: filesystems
when: 'value.fstype != "tmpfs"'
name: "DiskUsed_{{ value.mount }}"
type: generic_data
data: "{{ value.pct }}"
unit: "%"
Inside a for_each block:
-
{{ value }}is the current element,{{ index }}its 0-based position. - Named groups make each element an object → access fields as
{{ value.mount }}. - 2+ unnamed groups make each element a positional array → access as
{{ value[0] }},{{ value[1] }}, ...; write several blocks with the samefor_eachto emit several modules per element.
Step 4 — Pick the transfer mode
transfer:
mode: agent_plugin # modules-only output; run as a software-agent plugin
transfer:
mode: tentacle # full agent XML to a Pandora server
tentacle: { address: "pandora.example.com" }
transfer:
mode: local # full agent XML written to a directory
local: { directory: "/var/spool/pandora/data_in" }
Step 5 — Validate before shipping
pandora-plugin_exec -t templates/my_template.yml --dry-run -v
--dry-run prints the generated XML without transferring; -v shows which variables were stored and which modules were skipped. The parser is strict, so any typo in a field name fails immediately.
Common pitfalls
-
Flags after arguments don't work. Go flag parsing stops at the first non-flag token: put
--dry-runbefore any positional argument. -
Undefined variables skip the module, they don't render empty. Use
{{ params.x ?? "default" }}orwhen: "'x' in params"for optional parameters. -
filter.expressionis not expr. It is jq or an RE2 regexp depending onfilter.type. expr is only used inside{{ }}and inwhen:. -
Double the backslash in regexp literals inside
when:— expr string literals process escapes, so\\ddelivers\dto the regexp engine. -
RE2 has no lookahead/backtracking — use
when:for exclusions (e.g.when: 'not (value.mount matches "^/DB")').