Skip to content

Commit 0984acb

Browse files
authored
Merge pull request #163 from flashcatcloud/fix/pipeline-if-decode
fix: read alert pipelines that carry an `if` condition
2 parents 4a57616 + bbca4a6 commit 0984acb

4 files changed

Lines changed: 61 additions & 4 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/flashcatcloud/flashduty-cli
33
go 1.25.1
44

55
require (
6-
github.com/flashcatcloud/go-flashduty v0.14.1
6+
github.com/flashcatcloud/go-flashduty v0.14.2
77
github.com/mattn/go-runewidth v0.0.27
88
github.com/spf13/cobra v1.10.2
99
github.com/spf13/pflag v1.0.10

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJ
33
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
44
github.com/flashcatcloud/go-flashduty v0.14.1 h1:yrpKPuMJuC1z8bhIaZfGV66lb+sAsBUcqOZZsFZjsiI=
55
github.com/flashcatcloud/go-flashduty v0.14.1/go.mod h1:aA0RtZEs0AYOwwdNKdtVeD8YMOdnmVY1zAlVD+9Ovx8=
6+
github.com/flashcatcloud/go-flashduty v0.14.2 h1:SIF8AMoaF67eEGAiiq8e4y5Dvv/0OOA/VzoxYpDB1CE=
7+
github.com/flashcatcloud/go-flashduty v0.14.2/go.mod h1:aA0RtZEs0AYOwwdNKdtVeD8YMOdnmVY1zAlVD+9Ovx8=
68
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
79
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
810
github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0=

skills/flashduty/reference/alert.md

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,9 +146,57 @@ View alert timeline
146146

147147
`pipeline-upsert` replaces the whole pipeline (max 50 rules); `rules[].kind` values: `title_reset` · `description_reset` · `severity_reset` · `alert_drop` · `alert_inhibit`. The `rules` array has no typed flag — pass it via `--data '{"rules":[...]}'`. The call is idempotent (upsert), so re-running with the same body is safe.
148148

149-
`settings` shape depends on `kind`: `title_reset``{"title": "<template>"}`; `description_reset``{"description": "<template>"}`; `severity_reset``{"severity": "Critical"|"Warning"|"Info"}`; `alert_drop``{}` (empty object); `alert_inhibit``{"equals": ["<label_key>", ...], "source_filters": <OrFilterGroup>}`.
149+
`settings` shape depends on `kind`: `title_reset``{"title": "<template>"}`; `description_reset``{"description": "<template>"}`; `severity_reset``{"severity": "Critical"|"Warning"|"Info"}`; `alert_drop``{}` (empty object); `alert_inhibit``{"equals": ["<label_key>", ...], "source_filters": [<condition>, ...]}`.
150150

151-
**`rules[].if` (and `alert_inhibit`'s `source_filters`) are OR-of-AND condition trees** — read `reference/filters.md` before composing them. Note this differs from `enrichment`'s rule-level `if`, which is a single flat AND-only condition list.
151+
**`rules[].if` and `alert_inhibit.source_filters` are FLAT AND-lists** — one array of `{key, oper, vals}` conditions, ALL of which must match:
152+
153+
```json
154+
"if": [{"key": "labels.env", "oper": "IN", "vals": ["prod"]}]
155+
```
156+
157+
This is the same shape as `enrichment`'s rule-level `if`, and it is **not** the
158+
OR-of-AND tree used by silence / inhibit / drop / escalation rules. Wrapping the
159+
conditions in a second array is rejected before the request leaves the CLI
160+
(`cannot unmarshal array into ... of type FilterCondition`). Read
161+
`reference/filters.md` for the operators, the missing-key trap, and the key
162+
vocabulary — those all apply here; only the nesting differs.
163+
164+
## Writing `title_reset` / `description_reset` templates
165+
166+
The `settings.title` and `settings.description` values are NOT plain strings —
167+
they are rendered, and the two kinds render differently:
168+
169+
| | with a leading `[TPL]` | without it |
170+
|---|---|---|
171+
| `title_reset` | rendered as a template | treated as a `::`-joined key list (below) |
172+
| `description_reset` | rendered as a template | **silently does nothing** — the rule is a no-op |
173+
174+
**Always write `[TPL]` for `description_reset`.** Omitting it is not an error;
175+
the rule simply never fires, which reads as "the pipeline didn't apply".
176+
177+
Inside a `[TPL]` value, two substitutions run in order:
178+
179+
1. `${label_name}` — replaced with that label's value; a missing label renders
180+
`<no value>` rather than failing.
181+
2. Go `text/template` — the event is the dot, so labels are
182+
`{{.Labels.<name>}}` (`Labels` capitalised; it is a map).
183+
184+
```json
185+
{"kind": "title_reset", "settings": {"title": "[TPL]{{.Labels.service}} / {{.Labels.check}}"}}
186+
{"kind": "description_reset", "settings": {"description": "[TPL]${instance} is late by ${seconds}s"}}
187+
```
188+
189+
**The bare `::` form (title only).** Without `[TPL]`, the title is split on
190+
`::`; each segment is either a literal or `$name`, which resolves to
191+
`labels.<name>`. Segments are joined with position-fixed separators — nothing,
192+
then ` / `, then ` - `, then `` for the rest — so `$service::$check` renders
193+
as `payments-api / disk_used`. Prefer the `[TPL]` form: it is explicit about
194+
where values come from and does not hard-code separators.
195+
196+
**Labels the template reads must already exist.** Enrichment runs BEFORE the
197+
pipeline, so labels produced by `fduty enrichment upsert` (extraction,
198+
composition, mapping) are available here — but a label produced by a LATER
199+
pipeline rule is not, and a typo just renders `<no value>` into the title.
152200

153201
## Gotchas
154202

skills/flashduty/reference/filters.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ the rule fires when ANY group matches; each inner array holds
2323

2424
→ (Critical AND service=payments-api) OR (env=staging).
2525

26+
**One exception — alert pipeline.** `rules[].if` and
27+
`alert_inhibit.source_filters` (`reference/alert.md`) take a FLAT array of
28+
conditions, all AND-ed, with no outer grouping array. Same operators, same
29+
keys, one less level of nesting. Everything below applies to them except the
30+
shape.
31+
2632
## Operators and values
2733

2834
- `oper` is `IN` (the object's value for `key` must equal one of `vals`) or
@@ -42,7 +48,8 @@ custom label. Per family:
4248

4349
| rule family | matched against | extra keys | keys that DO NOT exist here |
4450
|---|---|---|---|
45-
| silence / drop (`filters`), inhibit (`source_filters` / `target_filters`), alert pipeline (`rules[].if`, `alert_inhibit.source_filters`) | each alert event | `alert_key`, `title_rule` | `dedup_key` |
51+
| silence / drop (`filters`), inhibit (`source_filters` / `target_filters`) | each alert event | `alert_key`, `title_rule` | `dedup_key` |
52+
| alert pipeline (`rules[].if`, `alert_inhibit.source_filters`) — **flat AND-list**, see above | each alert event | `alert_key`, `title_rule` | `dedup_key` |
4653
| escalation (`filters`) | the incident | `dedup_key` | `alert_key`, `title_rule` |
4754

4855
A key outside the family's vocabulary (e.g. `dedup_key` in a silence rule)

0 commit comments

Comments
 (0)