# 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:

```bash
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.

```yaml
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).

```yaml
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 same `for_each` to emit several modules per element.

## Step 4 — Pick the transfer mode

```yaml
transfer:
  mode: agent_plugin            # modules-only output; run as a software-agent plugin
```

```yaml
transfer:
  mode: tentacle                # full agent XML to a Pandora server
  tentacle: { address: "pandora.example.com" }
```

```yaml
transfer:
  mode: local                   # full agent XML written to a directory
  local: { directory: "/var/spool/pandora/data_in" }
```

## Step 5 — Validate before shipping

```bash
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-run` before any positional argument.
- **Undefined variables skip the module**, they don't render empty. Use `{{ params.x ?? "default" }}` or `when: "'x' in params"` for optional parameters.
- **`filter.expression` is not expr.** It is jq or an RE2 regexp depending on `filter.type`. expr is only used inside `{{ }}` and in `when:`.
- **Double the backslash in regexp literals inside `when:`** — expr string literals process escapes, so `\\d` delivers `\d` to the regexp engine.
- **RE2 has no lookahead/backtracking** — use `when:` for exclusions (e.g. `when: 'not (value.mount matches "^/DB")'`).

---