@tikoci/centrs 0.1.6 → 0.1.8-next.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +342 -0
- package/GLOSSARY.txt +32 -0
- package/README.md +10 -6
- package/SECURITY.md +1 -1
- package/commands/AGENTS.md +2 -1
- package/commands/api/AGENTS.md +55 -30
- package/commands/api/README.md +135 -42
- package/commands/api/examples.md +275 -19
- package/commands/devices/README.md +21 -4
- package/commands/devices/examples.md +33 -2
- package/commands/execute/README.md +60 -18
- package/commands/execute/examples.md +224 -0
- package/commands/explain/README.md +42 -5
- package/commands/mcp/README.md +2 -1
- package/commands/retrieve/README.md +348 -4
- package/commands/retrieve/examples.md +442 -5
- package/commands/terminal/README.md +14 -4
- package/commands/terminal/examples.md +22 -3
- package/commands/transfer/README.md +55 -9
- package/commands/transfer/examples.md +106 -7
- package/docs/CLI.md +29 -21
- package/docs/CONSTITUTION.md +41 -16
- package/docs/MATRIX.md +38 -1
- package/docs/errors/README.md +3 -1
- package/docs/errors/cdb/create-failed.md +20 -0
- package/docs/errors/cdb/file-exists.md +23 -0
- package/docs/errors/input/invalid-query.md +36 -0
- package/docs/errors/input/invalid-target.md +25 -3
- package/docs/errors/input/local-destination.md +20 -0
- package/docs/errors/input/unsupported-query.md +19 -0
- package/docs/errors/internal/byte-string.md +13 -0
- package/docs/errors/routeros/invalid-value.md +3 -2
- package/docs/errors/settings/invalid-timeout.md +6 -0
- package/docs/errors/transport/cancel-unacknowledged.md +24 -0
- package/docs/errors/transport/stream-overflow.md +21 -0
- package/docs/errors/usage/removed-flag.md +20 -0
- package/docs/errors/validation/menu-unavailable.md +22 -0
- package/docs/errors/validation/not-followable.md +23 -0
- package/docs/errors/validation/package-missing.md +29 -0
- package/docs/errors/validation/unknown-path.md +16 -4
- package/docs/errors/wait/deadline-exceeded.md +14 -0
- package/docs/errors/wait/interrupted.md +10 -0
- package/package.json +14 -11
- package/src/absent-menu.ts +280 -0
- package/src/api-fanout.ts +22 -12
- package/src/api.ts +603 -194
- package/src/btest.ts +19 -17
- package/src/cli/api.ts +126 -34
- package/src/cli/devices.ts +16 -3
- package/src/cli/execute.ts +18 -5
- package/src/cli/retrieve.ts +216 -22
- package/src/cli/terminal.ts +10 -5
- package/src/cli/transfer.ts +23 -8
- package/src/core/envelope.ts +14 -0
- package/src/core/error-catalog.ts +70 -1
- package/src/core/execute-command.ts +313 -6
- package/src/core/inspect.ts +65 -27
- package/src/core/query.ts +508 -0
- package/src/core/routeros-errors.ts +67 -1
- package/src/data/winbox-cdb-write.ts +22 -8
- package/src/devices.ts +148 -37
- package/src/discover.ts +7 -8
- package/src/errors.ts +116 -7
- package/src/execute-fanout.ts +14 -9
- package/src/execute.ts +128 -116
- package/src/explain/catalog.ts +64 -16
- package/src/explain/menus.ts +27 -6
- package/src/explain.ts +6 -6
- package/src/index.ts +31 -1
- package/src/mcp/tools.ts +10 -15
- package/src/protocols/adapter.ts +235 -118
- package/src/protocols/btest-session.ts +3 -2
- package/src/protocols/endpoint.ts +10 -0
- package/src/protocols/mac-telnet-console.ts +12 -7
- package/src/protocols/native-api.ts +252 -73
- package/src/protocols/sftp.ts +44 -14
- package/src/protocols/ssh.ts +42 -14
- package/src/resolver/cdb.ts +28 -7
- package/src/resolver/index.ts +1 -0
- package/src/resolver/target.ts +84 -18
- package/src/retrieve-fanout.ts +51 -12
- package/src/retrieve-follow.ts +862 -0
- package/src/retrieve-sample.ts +244 -0
- package/src/retrieve-stream.ts +115 -0
- package/src/retrieve-wait.ts +286 -0
- package/src/retrieve.ts +741 -109
- package/src/settings.ts +15 -15
- package/src/transfer-fanout.ts +40 -15
- package/src/transfer.ts +234 -32
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# retrieve
|
|
2
2
|
|
|
3
3
|
Status: `CHR-passed` over `rest-api` and `native-api`, including multi-target
|
|
4
|
-
fan-out (see **Target selection**)
|
|
5
|
-
`
|
|
4
|
+
fan-out (see **Target selection**), `--query` row filters (see **Query**),
|
|
5
|
+
`--follow` over `native-api` (see **Follow**) and `--sample` over both (see
|
|
6
|
+
**Sample**) and bounded readiness/conditions (see **Wait**). `snmp` is
|
|
7
|
+
`not-started`. Matches `docs/MATRIX.md`.
|
|
6
8
|
|
|
7
9
|
Read RouterOS state. RouterOS menu reads model `<path>/<verb>` where the verb
|
|
8
10
|
is `print`-style (`print`, `get`, and async POST-shaped reads as they're
|
|
@@ -50,6 +52,8 @@ duplicate that table. Behavior notes the generated reference cannot carry:
|
|
|
50
52
|
- `--format json` (shortcut `--json`): set `CENTRS_FORMAT=json` to make the
|
|
51
53
|
structured envelope the default. `--format yaml` renders the same envelope.
|
|
52
54
|
- `--port`: native-api defaults to 8728; `--port 8729` selects TLS `api-ssl`.
|
|
55
|
+
A port typed into the target (`host:port`) is honored too, below the port
|
|
56
|
+
settings ladder (`--port`, `CENTRS_PORT`, a CDB `port=` comment, config).
|
|
53
57
|
- `--query`/`--filter` are RouterOS **row** filters and always return an
|
|
54
58
|
array (see Synopsis); `--where` filters *which devices* fan out, not rows.
|
|
55
59
|
- `--all-attributes` conflicts with `--attribute(s)` (see Validation).
|
|
@@ -63,7 +67,7 @@ Spec-tier flags with no implementation yet — today they fail as unknown flags:
|
|
|
63
67
|
|
|
64
68
|
| Flag | Designed behavior |
|
|
65
69
|
| ----------------------------------- | ----------------------------------------------------------------------------------------- |
|
|
66
|
-
| `--once` | Bounded single read of a monitor-style menu (RouterOS `once`): returns **one** envelope and never follows. Open-ended follow is `api …
|
|
70
|
+
| `--once` | Bounded single read of a monitor-style menu (RouterOS `once`): returns **one** envelope and never follows. Open-ended follow is `--follow` (below), or `api … <menu>/listen` for the literal wire. See constitution: protocol selection. |
|
|
67
71
|
| `--max-bytes <n>` | Byte budget for the rendered payload. If the response would exceed it, centrs truncates to fit, keeps `ok: true`, and adds a warning + `meta.truncated`. Not an error. (Will replace the implemented `--max-results`, which fails instead of truncating.) |
|
|
68
72
|
| `--max-rows <n>` | Maximum row count for list reads. Excess rows are clipped; `ok: true` with a warning + `meta.truncated`. |
|
|
69
73
|
| `--yaml` | Shortcut for `--format yaml`. |
|
|
@@ -77,6 +81,15 @@ Default validator: `/console/inspect request=syntax path=<comma-joined>,print`
|
|
|
77
81
|
unknown attributes with `validation/*` codes that include the inspect-suggested
|
|
78
82
|
alternatives in the error envelope.
|
|
79
83
|
|
|
84
|
+
`meta.validation.result` follows
|
|
85
|
+
[the constitution](../../docs/CONSTITUTION.md#validation-is-the-product). It is
|
|
86
|
+
`passed` once inspect accepts the read, even if the read fails afterward. It is
|
|
87
|
+
`failed` when the gate refuses: `validation/*`, or `--query` on a singleton.
|
|
88
|
+
It is absent when a `transport/*`, `auth/*`, `routeros/*` or `internal/*` error
|
|
89
|
+
stopped inspect from answering; the device stage then says `not reached`
|
|
90
|
+
(examples 12 and 16b; 16c is the `passed` case). With `--validate=false` it
|
|
91
|
+
is `{enabled: false, source: "disabled"}`.
|
|
92
|
+
|
|
80
93
|
If `--all-attributes` is combined with `--attribute(s)`, fail with
|
|
81
94
|
`usage/conflicting-flags` *before* hitting the network.
|
|
82
95
|
|
|
@@ -140,6 +153,334 @@ warning entry — it is not an error. (Earlier text said a byte overflow returns
|
|
|
140
153
|
*error* with the needed size; superseded — truncation is an `ok: true` footnote so
|
|
141
154
|
partial data stays usable.)
|
|
142
155
|
|
|
156
|
+
A failure **after** target resolution keeps what resolution found. The error
|
|
157
|
+
envelope carries the resolved `meta.target` (host, port, base URL, record,
|
|
158
|
+
sources), `meta.via`, the setting sources in `meta.settings`, and
|
|
159
|
+
`meta.operation.auth` (`username`, `passwordProvided`; provenance only, never
|
|
160
|
+
the secret). This holds for one-shot reads, `--follow`, `--sample`, `--wait`,
|
|
161
|
+
each fan-out member and the MCP `centrs_retrieve` tool. A failure before
|
|
162
|
+
resolution (bad shape, unknown target) keeps `meta.target.input` and a valid
|
|
163
|
+
pinned `via`, with empty `meta.settings`. The
|
|
164
|
+
library exposes this as `retrieveEnvelope()`, which never throws (examples 13,
|
|
165
|
+
16b).
|
|
166
|
+
|
|
167
|
+
## Query (`--query`)
|
|
168
|
+
|
|
169
|
+
`retrieve <router> <menu> --query '<expr>'` returns only the rows that match,
|
|
170
|
+
filtered on the router. `<expr>` is what you would write after `print where`:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
centrs retrieve $R /interface --query 'type=ether and !disabled'
|
|
174
|
+
centrs retrieve $R /ip/route --query '(dst-address="0.0.0.0/0" or distance>=10) and active'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`--filter` is the same flag. Repeat either, and the expressions are AND-ed. A
|
|
178
|
+
list read with `--query` is always an array, `[]` when nothing matches. The
|
|
179
|
+
result works with `--attribute(s)`, `--sample`, `--follow` and fan-out.
|
|
180
|
+
A singleton (`/system/identity`) has no rows to filter and
|
|
181
|
+
fails `usage/conflicting-flags`.
|
|
182
|
+
|
|
183
|
+
**What it compiles to.** centrs parses the expression before connecting and
|
|
184
|
+
sends it as the API query (native `?` words, REST `.query`; one language,
|
|
185
|
+
`commands/api/AGENTS.md`). The router evaluates it, so comparisons use
|
|
186
|
+
RouterOS's own per-property typing:
|
|
187
|
+
|
|
188
|
+
| You write | RouterOS query words |
|
|
189
|
+
| --------- | -------------------- |
|
|
190
|
+
| `a=v`, `a!=v` | `a=v`; `a=v` `#!` |
|
|
191
|
+
| `a<v`, `a>v` | `<a=v` `-a` `#\|`; `>a=v` |
|
|
192
|
+
| `a<=v`, `a>=v` | `>a=v` `#!`; `<a=v` `-a` `#\|` `#!` |
|
|
193
|
+
| bare `a` | `a=yes` if `a` is a boolean, otherwise `a` (has a value) |
|
|
194
|
+
| `!x`, `x and y`, `x or y` | x `#!`; x y `#&`; x y `#\|` |
|
|
195
|
+
|
|
196
|
+
As in `print where`, a row without the property counts as smaller than any
|
|
197
|
+
value: `timeout<1d` includes static address-list entries (no `timeout`), and
|
|
198
|
+
`timeout>1h` does not. API `<`/`>` skip such rows on their own, hence the `-a`
|
|
199
|
+
("has no `a`") word. `and` (`&&`) binds tighter than `or` (`||`), as in RouterOS. Values run to the
|
|
200
|
+
next space or parenthesis, and start right after the operator (RouterOS
|
|
201
|
+
rejects `comment= x`). Quote a value with spaces or an `=` (`comment="uplink a"`,
|
|
202
|
+
`comment="x=yes"`), using RouterOS escapes: `\"`, `\\`, `\$`, `\?`, `\_`, `\a`
|
|
203
|
+
`\b` `\f` `\n` `\r` `\t` `\v`, and two uppercase hex digits (`\41`; `\ff` is `\f`
|
|
204
|
+
then `f`). A backslash before whitespace drops it and the whole whitespace run
|
|
205
|
+
after it, so `"x\<newline> y"` is `xy`. A hex
|
|
206
|
+
byte above `\7F` is refused: centrs sends values as UTF-8, so it could not match
|
|
207
|
+
the raw byte RouterOS stores.
|
|
208
|
+
|
|
209
|
+
**A bare name.** `where disabled` means `disabled=yes`, but `where comment`
|
|
210
|
+
means "comment is set" (RouterOS stores an empty comment as absent). Only the
|
|
211
|
+
router knows which kind a property is, so centrs asks it: `/console/inspect`
|
|
212
|
+
completion after `where <name>=` offers exactly `yes`/`no` for a boolean. With
|
|
213
|
+
`--validate=false` there is no one to ask, and a bare name fails
|
|
214
|
+
`input/invalid-query`; write `disabled=yes`.
|
|
215
|
+
|
|
216
|
+
**Validation.** Every property in the expression is checked against the
|
|
217
|
+
menu's attributes (as for `--attributes`, plus `.id`). RouterOS answers a query
|
|
218
|
+
on a misspelled property with no rows and no error, so a typo fails
|
|
219
|
+
`validation/unknown-attribute` instead.
|
|
220
|
+
|
|
221
|
+
**Same rows as `print where`, with these differences.** On CHR 7.23.7 and
|
|
222
|
+
7.24.5, 58 expressions over three menus return exactly the rows
|
|
223
|
+
`find where` selects (examples QY1, QY2). The exceptions come from the CLI
|
|
224
|
+
typing an unquoted literal, which the query does not do:
|
|
225
|
+
|
|
226
|
+
- `where dst-port=80` reads `80` as a number and matches nothing, and
|
|
227
|
+
`where protocol=tcp` likewise; the query matches the `"80"` and `"tcp"` that
|
|
228
|
+
`retrieve` shows. Quoted (`dst-port="80"`), the CLI agrees. This difference
|
|
229
|
+
is version-dependent: on CHR 7.25beta3, 7.25beta5, 7.25rc1 and 7.26beta1,
|
|
230
|
+
both unquoted CLI expressions also match. QY2 keeps exact expectations for
|
|
231
|
+
both behaviors; API query results stay the same over both transports.
|
|
232
|
+
- Ordering (`<`, `>`, `<=`, `>=`) on a property RouterOS returns as text
|
|
233
|
+
(address-list `address`, `dst-port`) is text order: `192.0.2.10` sorts
|
|
234
|
+
before `192.0.2.9`. Numbers (`mtu`, `vlan-id`, `distance`) compare as
|
|
235
|
+
numbers.
|
|
236
|
+
- The query accepts `disabled=true` as `yes`; the CLI rejects it.
|
|
237
|
+
|
|
238
|
+
**Refused, never approximated.** `~` (regex) and `in` (prefix membership) have
|
|
239
|
+
no query word, and a `$variable`, `[command]` or `{array}` value needs the
|
|
240
|
+
script evaluator: these fail `input/unsupported-query` with a tip. Four
|
|
241
|
+
spellings RouterOS reads differently from how they look fail
|
|
242
|
+
`input/invalid-query`: `!a=v` (RouterOS reads `(!a)=v`; write `!(a=v)` or
|
|
243
|
+
`a!=v`), `not` (not a RouterOS operator; use `!`), conditions separated
|
|
244
|
+
only by a space (join them with `and` or `or`), and an unquoted `=` inside a
|
|
245
|
+
value (RouterOS reads `comment=x=yes` as `(comment=x)=yes`, and `comment=x!=y`
|
|
246
|
+
matches nothing; quote it: `comment="x=yes"`).
|
|
247
|
+
|
|
248
|
+
## Follow (`--follow`)
|
|
249
|
+
|
|
250
|
+
`retrieve <router> <menu> --follow` keeps a menu's rows current: first the
|
|
251
|
+
rows that exist now, then a line for every change, as NDJSON. It is the
|
|
252
|
+
friendly wrapper over native-API `listen`; `api <router> <menu>/listen` is the
|
|
253
|
+
literal wire (`commands/api/README.md`). Decisions and CHR evidence: #396.
|
|
254
|
+
|
|
255
|
+
**What starts it.** `--follow` on a list menu whose `print` takes
|
|
256
|
+
`follow-only` in `/console/inspect` (checked live). A menu without it, a
|
|
257
|
+
singleton included, fails `validation/not-followable` before anything is
|
|
258
|
+
followed. Native-api only: an unset `via` infers native-api, and a `via` of
|
|
259
|
+
`rest-api` from any source (flag, env, CDB, settings) fails
|
|
260
|
+
`transport/capability-unsupported`; there is no polling fallback. Single
|
|
261
|
+
target only: a fan-out selector fails `usage/fanout-not-supported`. Follow is
|
|
262
|
+
filtered with the same `--query`/`--filter` grammar as a one-shot read (#397).
|
|
263
|
+
|
|
264
|
+
**What each line means.** Every line is one envelope; read
|
|
265
|
+
`meta.operation.stream`:
|
|
266
|
+
|
|
267
|
+
| `kind` | When | `data` | Fields |
|
|
268
|
+
| ------ | ---- | ------ | ------ |
|
|
269
|
+
| `frame` | One row change | the row (`upsert`), `null` (`removed`) | `index`, `phase` (`snapshot`/`live`), `change` (`upsert`/`removed`), `id`, `source` (`print`/`listen`/`sweep`/`membership`) |
|
|
270
|
+
| `synced` | Once, after the snapshot | `null` | `rows`: rows held at that point |
|
|
271
|
+
| `notice` | First, only when there is advice | `null` | — (`tips` carries it) |
|
|
272
|
+
| `summary` | Last, exactly once | the counts | `stopReason`, `frames`, `snapshot`, `changes`, `sweeps`, `synced`, `durationMs` |
|
|
273
|
+
|
|
274
|
+
- **Bootstrap (A1).** centrs starts the `listen`, then the `print`, on one
|
|
275
|
+
connection. It emits the `print` rows, then every `listen` change received
|
|
276
|
+
since the `listen` started, all as `phase: "snapshot"`, then `synced`.
|
|
277
|
+
Applying the lines in order gives the menu's state at `synced`. That order
|
|
278
|
+
had 0 errors over about 14.7k writes on 7.23.7 and 7.24.5; merging by arrival
|
|
279
|
+
order resurrects deleted rows (#396 round 3). `synced` means the bootstrap
|
|
280
|
+
was processed. It is not an atomic snapshot, a full event history, or
|
|
281
|
+
routing convergence.
|
|
282
|
+
- **`upsert`** carries the latest full row; replace what you hold for `id`.
|
|
283
|
+
**`removed`** carries `data: null`; drop `id`. The row identity is always
|
|
284
|
+
`meta.operation.stream.id`, even when `--attributes` leaves `.id` out of
|
|
285
|
+
`data`.
|
|
286
|
+
- **Changes are coalesced state, not an event log.** RouterOS sends a change
|
|
287
|
+
about 200 ms after a write and folds writes in between. A row created and
|
|
288
|
+
deleted inside that window may never appear, and a flap may arrive as one
|
|
289
|
+
line or none. A `.dead` for an `id` centrs never reported is dropped, so a
|
|
290
|
+
removal the sweep saw first is reported once, as `source: "sweep"`.
|
|
291
|
+
- **Removals RouterOS does not send.** View menus (`/interface/<type>`,
|
|
292
|
+
`/ip/route`, `/ipv6/route`) never send `.dead`. An `.id`-only sweep
|
|
293
|
+
(`print .proplist=.id`) runs every `--sweep` (default `10s`) on every followed
|
|
294
|
+
menu and reports an `id` that is gone as `removed` with `source: "sweep"`.
|
|
295
|
+
An `id` that changed since that sweep was sent is never removed by it. A
|
|
296
|
+
failed sweep ends the follow with an error; it never infers a removal. A
|
|
297
|
+
sweep only checks membership: it does not repair a missed field update or
|
|
298
|
+
report rows `listen` never announced. `--sweep` needs a unit (`10s`,
|
|
299
|
+
`500ms`); a bare number fails `settings/invalid-timeout`, as for `--sample`.
|
|
300
|
+
`--sweep 0` turns it off, with a notice tip saying view-menu removals will
|
|
301
|
+
not be reported.
|
|
302
|
+
- **What `listen` never reports.** Hot counters (`rx-byte`, firewall
|
|
303
|
+
`bytes`/`packets`) and some protocol state (BFD sessions accept `listen` but
|
|
304
|
+
stay silent). The follow is correct but quiet there; `--sample` (below)
|
|
305
|
+
is the answer for those.
|
|
306
|
+
- **Filtered membership.** The snapshot and sweep carry the predicate; the
|
|
307
|
+
listen stays unfiltered, retaining `.id,.dead`. For changed ids centrs reads
|
|
308
|
+
the current matching rows from RouterOS, in batches of at most 256 ids with
|
|
309
|
+
one membership read in flight. No client type coercion is involved, and
|
|
310
|
+
predicate fields need not appear in the caller's projection. These frames
|
|
311
|
+
have `source: "membership"`: a matching row is upserted, and a held row that
|
|
312
|
+
no longer matches (or no longer exists) is removed. This removal is a change
|
|
313
|
+
in the selected set, not a claim that RouterOS sent `.dead`. A real `.dead`
|
|
314
|
+
still has `source: "listen"` and only removes a held id. Delayed membership
|
|
315
|
+
results are discarded for ids with newer wire changes; sweeps and membership
|
|
316
|
+
reads are serialized. Bootstrap fixes its changed-id/version cutoff at the
|
|
317
|
+
snapshot's wire completion and reconciles those ids before `synced`. A
|
|
318
|
+
reply superseded by a newer change is discarded; that newer version stays
|
|
319
|
+
queued for live reconciliation. Continuous writes cannot extend bootstrap
|
|
320
|
+
indefinitely or postpone the first sweep and live `--count`. This may
|
|
321
|
+
coalesce more changes than unfiltered A1 replay.
|
|
322
|
+
The filtered sweep also detects silent exits from a predicate (e.g. a
|
|
323
|
+
decreasing address-list `timeout`). It does not discover silent entries or
|
|
324
|
+
repair silent field updates: use `--sample` for such state. With `--sweep 0`,
|
|
325
|
+
silent exits remain held until a listen notification arrives.
|
|
326
|
+
- **Projection.** `--attribute(s)` projects `data`; centrs still asks RouterOS
|
|
327
|
+
for `.id,.dead` and removes them from `data` unless you named them.
|
|
328
|
+
`--all-attributes` sends `detail` to both `listen` and `print`.
|
|
329
|
+
`--list-attributes` and `--max-results` conflict with `--follow`
|
|
330
|
+
(`usage/conflicting-flags`).
|
|
331
|
+
|
|
332
|
+
**What ends it.** `--count N` stops after N `live` frames; snapshot frames and
|
|
333
|
+
`synced` never count, so `--count 1` is "the next observed change" (coalesced,
|
|
334
|
+
so not a guaranteed transition). `--duration <d>` is a wall-clock bound that
|
|
335
|
+
includes the bootstrap. Ctrl-C (`SIGINT`) stops it. Each ends with a
|
|
336
|
+
successful summary whose `stopReason` is `count-reached`, `duration-elapsed`,
|
|
337
|
+
or `interrupted`. A RouterOS error, a lost connection, a failed sweep or a full
|
|
338
|
+
buffer ends it with a failed summary (`ok: false`, `stopReason`
|
|
339
|
+
`routeros-error` or `transport-error`), whose counts are partial.
|
|
340
|
+
`routeros-error` means RouterOS answered with an error; `transport-error`
|
|
341
|
+
covers every other failure, and `error.code` names the cause. Buffers are
|
|
342
|
+
bounded: if more than 50,000 changes are waiting (a consumer that stopped
|
|
343
|
+
reading, or a burst during a long snapshot), the follow ends with
|
|
344
|
+
`transport/stream-overflow`. The state you hold is stale; follow again to get a
|
|
345
|
+
new snapshot. `--sweep` without `--follow`, and `--count` or `--duration`
|
|
346
|
+
without `--follow` or `--sample`, fail `usage/conflicting-flags`.
|
|
347
|
+
|
|
348
|
+
**Format.** Under `--follow`, `json`, `yaml` and `ndjson` all print one compact
|
|
349
|
+
envelope per line (as `api --stream` does); `text` prints one short row per
|
|
350
|
+
line. Every envelope, errors included, goes to stdout, and the exit code is
|
|
351
|
+
nonzero for any failure, even after rows. Without `--follow`,
|
|
352
|
+
`--format ndjson` prints the ordinary envelope as one compact line; it never
|
|
353
|
+
starts a follow.
|
|
354
|
+
|
|
355
|
+
Recipes (bounded, so an agent can run them unattended):
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
# Watch: hold /ip/address for 30s, then stop.
|
|
359
|
+
centrs retrieve $R /ip/address --follow --duration 30s
|
|
360
|
+
# Wait for the next change, at most 60s.
|
|
361
|
+
centrs retrieve $R /ip/address --follow --count 1 --duration 60s
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
## Sample (`--sample`)
|
|
365
|
+
|
|
366
|
+
`retrieve <router> <menu> --sample <interval>` reads the menu again every
|
|
367
|
+
interval: one line per complete read, as NDJSON. It sees what `--follow`
|
|
368
|
+
cannot (hot counters such as `rx-byte`, and state `listen` never announces),
|
|
369
|
+
at the cost of missing whatever changes and changes back between two reads.
|
|
370
|
+
|
|
371
|
+
**What starts it.** `--sample` with an interval that has a unit (`5s`,
|
|
372
|
+
`500ms`, `1m`). A bare number fails `settings/invalid-timeout`: RouterOS reads
|
|
373
|
+
`interval=5` as seconds and centrs reads `5` as milliseconds, so centrs does
|
|
374
|
+
not guess. Any readable menu works, list or singleton. Each sample is the
|
|
375
|
+
ordinary one-shot read (`print` or `get`) with the same validation (once, before
|
|
376
|
+
the first read), projection and transport as `retrieve` without `--sample`:
|
|
377
|
+
`rest-api` by default, `native-api` when pinned. Single target only; a fan-out
|
|
378
|
+
selector fails `usage/fanout-not-supported`. `--follow` with `--sample` fails
|
|
379
|
+
`usage/conflicting-flags`.
|
|
380
|
+
|
|
381
|
+
Why reads and not RouterOS's own `print interval=`: on CHR 7.24.5 that feed has
|
|
382
|
+
no end-of-tick marker, so a tick is only known complete when the next tick (or
|
|
383
|
+
`!empty`) arrives, one whole interval later. A tick heavier than its interval
|
|
384
|
+
also stalls the connection. A one-shot read ends with its own `!done` or HTTP
|
|
385
|
+
response, so every sample is known complete when it arrives. The raw tick feed
|
|
386
|
+
stays available as `api <router> <menu>/print -X POST -f interval=1s --stream`
|
|
387
|
+
(`commands/api/README.md`).
|
|
388
|
+
|
|
389
|
+
**What each line means.** Every line is one envelope; read
|
|
390
|
+
`meta.operation.stream`:
|
|
391
|
+
|
|
392
|
+
| `kind` | When | `data` | Fields |
|
|
393
|
+
| ------ | ---- | ------ | ------ |
|
|
394
|
+
| `sample` | One complete read | the whole read: a row array (list), the record (singleton), or the bare value (one `--attribute` on a singleton) | `index` (1-based), `at` (when the read was sent, host clock, ISO 8601), `readMs` |
|
|
395
|
+
| `summary` | Last, exactly once | the counts | `stopReason`, `samples`, `durationMs` |
|
|
396
|
+
|
|
397
|
+
- **A sample is the whole state at that read.** A row missing from a sample
|
|
398
|
+
was gone when it was read; there is no separate removal line. A list read
|
|
399
|
+
with no rows is `data: []`, still a complete sample.
|
|
400
|
+
- **Cadence.** Reads start one interval apart, measured start to start. A
|
|
401
|
+
read slower than the interval delays the next read rather than overlapping
|
|
402
|
+
it, and missed slots are not made up in a burst. `at` gives the real spacing,
|
|
403
|
+
which is what a counter rate needs.
|
|
404
|
+
- **Not an event log.** A change shorter than the interval can fall between
|
|
405
|
+
two reads. Use `--follow` for object changes RouterOS announces.
|
|
406
|
+
|
|
407
|
+
**What ends it.** `--count N` stops after N samples. `--duration <d>` is a
|
|
408
|
+
wall-clock bound counted from the end of validation. Ctrl-C (`SIGINT`) stops
|
|
409
|
+
it. Each ends with a successful summary whose `stopReason` is `count-reached`,
|
|
410
|
+
`duration-elapsed` or `interrupted`. A read still in flight when the run stops
|
|
411
|
+
is not reported, because it is not complete. A failed read (a RouterOS error,
|
|
412
|
+
a lost connection, a timeout) ends the run with a failed summary (`ok: false`,
|
|
413
|
+
`stopReason` `routeros-error` or `transport-error`, as for `--follow`)
|
|
414
|
+
carrying the partial `samples` count; there is no retry. `--list-attributes`, `--max-results` and
|
|
415
|
+
`--sweep` conflict with `--sample` (`usage/conflicting-flags`).
|
|
416
|
+
|
|
417
|
+
**Format.** As for `--follow`: `json`, `yaml` and `ndjson` print one compact
|
|
418
|
+
envelope per line, and `text` prints `index`, `at` and the data on one line.
|
|
419
|
+
Every envelope, errors included, goes to stdout; the exit code is nonzero for
|
|
420
|
+
any failure, even after samples.
|
|
421
|
+
|
|
422
|
+
Recipes (bounded, so an agent can run them unattended):
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
# Interface counters every 5s, three samples.
|
|
426
|
+
centrs retrieve $R /interface --attributes name,rx-byte,tx-byte --sample 5s --count 3
|
|
427
|
+
# CPU load once a second for a minute.
|
|
428
|
+
centrs retrieve $R /system/resource --attribute cpu-load --sample 1s --duration 1m
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Wait (`--wait`, `--until`, `--until-empty`)
|
|
432
|
+
|
|
433
|
+
`retrieve <router> <menu> --wait <deadline>` waits for a successful read.
|
|
434
|
+
It retries refused, closed, reset and timed-out connections, and a host that is
|
|
435
|
+
down or unreachable, so it can start while a router or CHR is rebooting or its
|
|
436
|
+
services are coming up. Authentication,
|
|
437
|
+
TLS, DNS, RouterOS and validation errors fail immediately. Identity/CDB/quickchr
|
|
438
|
+
resolution errors also fail immediately; `--quickchr` must name a running VM.
|
|
439
|
+
The caller's pinned protocol is preserved.
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
centrs retrieve --quickchr sun /system/resource --wait 5m --json
|
|
443
|
+
centrs retrieve $R /routing/ospf/neighbor --wait 2m --until state=Full --json
|
|
444
|
+
centrs retrieve $R /routing/ospf/neighbor --wait 2m --until-empty --json
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
- **Deadline and cadence.** `--wait` is the overall deadline, including
|
|
448
|
+
validation and retries, and needs a unit (`30s`, `5m`): a bare number is
|
|
449
|
+
refused, as for `--sample`. `--timeout` bounds each request. Reads begin one
|
|
450
|
+
second apart by default; `--sample 500ms` selects another cadence, measured
|
|
451
|
+
start to start without overlapping reads. With `--wait`, `--sample` yields
|
|
452
|
+
one final result rather than a stream. Slow reads delay the next one and
|
|
453
|
+
unfinished reads at the deadline are abandoned.
|
|
454
|
+
- **Any matching row.** `--until '<expr>'` uses the same grammar, property
|
|
455
|
+
validation and device typing as `--query`. Repeat it to AND conditions.
|
|
456
|
+
`--query` selects the rows to consider; `--until` terminates the wait when
|
|
457
|
+
any of those rows match. The final `data` contains those matching rows,
|
|
458
|
+
projected as requested; predicate properties need not appear in `data`.
|
|
459
|
+
Already true succeeds on the initial read. Zero rows never satisfy it.
|
|
460
|
+
Predicates apply to list menus; singleton readiness uses `--wait` alone.
|
|
461
|
+
- **Zero rows.** `--until-empty` succeeds only on a completed read with zero
|
|
462
|
+
rows selected by `--query`. Silence, transport failure, unfinished reads and
|
|
463
|
+
cancellation do not prove emptiness. It needs a list menu and conflicts
|
|
464
|
+
with `--until`.
|
|
465
|
+
- **One result.** After request resolution, every terminal envelope carries `meta.operation.wait` with
|
|
466
|
+
`stopReason`, `elapsedMs`, `attempts` and `observations`. Attempts include
|
|
467
|
+
failed validation/connection attempts; observations count completed data
|
|
468
|
+
reads. `ready` and `condition-met` are successful. On deadline expiry,
|
|
469
|
+
`deadline-elapsed` is failed: it preserves the last transient transport error,
|
|
470
|
+
or reports `wait/deadline-exceeded` when the last completed observation did
|
|
471
|
+
not satisfy the condition. Ctrl-C or an aborted library signal fails with
|
|
472
|
+
`wait/interrupted`, `stopReason: "interrupted"`. Other errors have
|
|
473
|
+
`stopReason: "failed"`. CLI exit is 0 on success and 1 on any failure;
|
|
474
|
+
the one final envelope, including failures, is written to stdout.
|
|
475
|
+
|
|
476
|
+
Single target only. `--until` and `--until-empty` require `--wait`.
|
|
477
|
+
`--follow`, `--count`, `--duration` and `--list-attributes` conflict with it.
|
|
478
|
+
This waiter uses completed reads over REST or native-api, including counters
|
|
479
|
+
and protocol state that listen never reports. For open-ended change feeds use
|
|
480
|
+
`--follow`; `--duration` finishing a watch does not establish a wait condition.
|
|
481
|
+
The library entry is `retrieveWait(request, env?, { signal? })`, returning a
|
|
482
|
+
success or failure envelope; `retrieve()` remains the one-shot entry.
|
|
483
|
+
|
|
143
484
|
## Target selection
|
|
144
485
|
|
|
145
486
|
retrieve fans out over the **shared target-selection grammar** — multiple
|
|
@@ -155,7 +496,10 @@ stays the single-target envelope; a selector flag or a second positional target
|
|
|
155
496
|
switches to fan-out. `--quickchr <name>` (the named-live-provider) behaves like a
|
|
156
497
|
lone positional: one flag stays single-target, repeating it fans out, and mixing
|
|
157
498
|
it with positionals or CDB selectors is `usage/conflicting-flags` (constitution:
|
|
158
|
-
resolution providers).
|
|
499
|
+
resolution providers). The `--host` **flag** with no `<router>` positional
|
|
500
|
+
(`retrieve --host http://192.0.2.10:8080 /system/resource`) is a single target
|
|
501
|
+
too: no CDB lookup, so auth comes from flags, env or config. `CENTRS_HOST` never
|
|
502
|
+
stands in for `<router>`.
|
|
159
503
|
|
|
160
504
|
retrieve-specific behavior on top of the shared engine:
|
|
161
505
|
|