|
2 | 2 |
|
3 | 3 | Prereq: `SKILL.md` read. Read verbs are free. `create`, `update`, `delete` mutate account-wide notification templates — confirm before running. `delete <template-id>` is **irreversible**. |
4 | 4 |
|
| 5 | +**`update` writes only the fields you send.** A channel you omit keeps its current |
| 6 | +content; a channel you send as an empty string is cleared. So a one-channel edit sends one |
| 7 | +channel — but read the clearing caveat under Gotchas before you try to empty one. |
| 8 | + |
5 | 9 | ## Route here when |
6 | 10 |
|
7 | 11 | "通知模板 / 消息模板 / 告警通知格式 / 飞书模板 / Slack 模板 / 邮件模板 / template CRUD / custom template / preview notification / validate template" → **template**. NOT `channel` (channel = escalation policy routing; template = the rendered text/card body). The key ID is **`template_id`** (string), returned by `list` or `create`. |
@@ -46,15 +50,41 @@ fduty template create \ |
46 | 50 | fduty template info <template-id> --output-format toon |
47 | 51 | ``` |
48 | 52 |
|
49 | | -## Hot flow — update one channel on an existing template |
| 53 | +## Hot flow — change one channel on an existing template |
| 54 | + |
| 55 | +`update` patches, so you send only the channel you are changing. What still bites is the |
| 56 | +*body*: carry it with `jq --rawfile` and `--data -`, never through `"$(...)"`. |
50 | 57 |
|
51 | 58 | ```bash |
52 | | -# template-id is POSITIONAL; --template-name is required even on update |
53 | | -fduty template update <template-id> \ |
54 | | - --template-name "Critical-Feishu-v2" \ |
55 | | - --feishu "$(cat ./feishu-v3.tpl)" |
| 59 | +T=<template-id> # POSITIONAL on update/info/delete; --template-name always required |
| 60 | + |
| 61 | +# 1. Pull the current source of the channel you are changing |
| 62 | +fduty template info "$T" --json > /tmp/tpl.json |
| 63 | +jq -r '.feishu_app' /tmp/tpl.json > /tmp/feishu_app.tpl |
| 64 | +# …edit /tmp/feishu_app.tpl… |
| 65 | + |
| 66 | +# 2. Preview the edited source against a REAL incident before writing |
| 67 | +jq -n --rawfile c /tmp/feishu_app.tpl \ |
| 68 | + '{type:"feishu_app", content:$c, incident_id:"<incident-id>"}' \ |
| 69 | + | fduty template preview --data - |
| 70 | + |
| 71 | +# 3. Write that one channel. NEVER move the body through "$(...)": command substitution |
| 72 | +# strips every trailing newline, so a body ending in a blank line is silently shortened. |
| 73 | +jq -n --rawfile feishu_app /tmp/feishu_app.tpl \ |
| 74 | + --arg t "$T" --arg n "<template-name>" \ |
| 75 | + '{template_id:$t, template_name:$n, feishu_app:$feishu_app}' \ |
| 76 | + | fduty template update --data - |
| 77 | + |
| 78 | +# 4. Verify the body round-tripped byte-for-byte — cmp catches a silent truncation that |
| 79 | +# "the field is still non-empty" would not. |
| 80 | +fduty template info "$T" --json | jq -r '.feishu_app' | cmp - /tmp/feishu_app.tpl \ |
| 81 | + && echo "round-trip OK" |
56 | 82 | ``` |
57 | 83 |
|
| 84 | +Every channel you did not name is untouched — that is the server contract now, not luck. |
| 85 | +Verify the body you wrote anyway: `cmp` is what separates "wrote the right bytes" from |
| 86 | +"wrote something non-empty". |
| 87 | + |
58 | 88 | <!-- GENERATED:template START · 由 fduty __dump-commands 同步 · 勿手改 fence 内 --> |
59 | 89 |
|
60 | 90 | ### create |
@@ -166,8 +196,30 @@ Note: `create` / `update` flags use **hyphenated** names (`--dingtalk-app`, `--f |
166 | 196 | ## Gotchas |
167 | 197 |
|
168 | 198 | - **`info`, `update`, `delete` take `<template-id>` as a positional first argument** — pass it bare, not as `--template-id`. `create`, `list`, `preview`, `validate`, `get-preset`, `functions`, `variables` take all inputs as flags. |
169 | | -- **`update` replaces every channel field you pass — omitted channel flags are left unchanged** (server behavior: only supplied fields overwrite). Always pass `--template-name` even if the name is unchanged — it is required on update. |
170 | | -- **`--feishu-app-card-table-enabled` uses pointer semantics on `update`** — unlike the plain string channel-content flags, it patches the table-rendering setting only when the flag is explicitly passed; omit it to leave the existing setting untouched. It is a plain bool on `create` (no prior setting to preserve). |
| 199 | +- **`update` is a partial update: omitting a field leaves it alone.** The server writes |
| 200 | + only what the request contains, so naming one channel rewrites that channel and nothing |
| 201 | + else. All 14 channel-content fields plus `description`, `team_id`, |
| 202 | + `feishu_app_card_v2_table_enabled` and `incident_card_hidden_fields` behave this way. |
| 203 | + (`status` is not part of `update`'s request at all — it moves only through the separate |
| 204 | + enable/disable endpoints, which the CLI does not expose — so `update` can never change |
| 205 | + it.) `--template-name` is still required on every update even when unchanged. |
| 206 | +- **To CLEAR a channel you must send it as an explicit empty string** — omitting it now |
| 207 | + means "keep", not "clear". `--dingtalk-app ''` is the intent, but a flag set to the empty |
| 208 | + string was dropped before it reached the wire in `fduty` **older than v1.4.2**, which |
| 209 | + makes clearing a silent no-op on those builds. Check `fduty --version` first; if it is |
| 210 | + older, clear via `--data` with the field spelled out — `--data '{"template_id":"…", |
| 211 | + "template_name":"…","dingtalk_app":""}'` — and confirm with `info --json` that the |
| 212 | + channel actually went empty. |
| 213 | +- **Never move a template body through `"$(cat …)"` or `"$(jq -r …)"`.** Bash command |
| 214 | + substitution strips *all* trailing newlines, so a body that legitimately ends in a blank |
| 215 | + line is written back shortened — and a check that only asks which fields are non-empty |
| 216 | + cannot see it, because the field is still non-empty. Carry bodies with `jq --rawfile` |
| 217 | + and write with `--data -`, as the hot flow does. |
| 218 | +- **`--feishu-app-card-v2-table-enabled` uses pointer semantics on `update`** — unlike the plain string channel-content flags, it patches the table-rendering setting only when the flag is explicitly passed; omit it to leave the existing setting untouched. It is a plain bool on `create` (no prior setting to preserve). |
| 219 | +- **`list` returns every channel's full template source for every row** — a few dozen |
| 220 | + templates blow past a tool-output cap in one call. Never render it directly: go to a |
| 221 | + file and project. `fduty template list --limit 100 --json > /tmp/tpl_list.json && jq -r |
| 222 | + '.items[] | [.template_id, .template_name, .team_id] | @tsv' /tmp/tpl_list.json`. |
171 | 223 | - **`delete` is permanent.** The built-in preset (`template_id = 000000000000000000000001`) can be addressed by that sentinel ID in `info` and `delete` — don't delete it. |
172 | 224 | - **`validate` reads from a local `--file`; `preview` takes inline `--content`.** They are complementary: `validate` gives size-vs-limit diagnostics; `preview` renders against real or mock incident data. |
173 | 225 | - **`email` uses `html/template` syntax; `sms` and `voice` use `text/template`** — auto-escaping rules differ. Don't mix them. |
|
0 commit comments