@tikoci/centrs 0.1.8 → 0.1.9-next.22
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 +216 -31
- package/GLOSSARY.txt +8 -0
- package/README.md +16 -8
- package/SECURITY.md +11 -3
- package/commands/api/README.md +44 -15
- package/commands/api/examples.md +44 -6
- package/commands/execute/README.md +218 -7
- package/commands/execute/examples.md +278 -1
- package/commands/explain/README.md +2 -1
- package/commands/mcp/README.md +11 -0
- package/commands/mcp/examples.md +6 -0
- package/commands/retrieve/README.md +32 -14
- package/commands/retrieve/examples.md +10 -6
- package/commands/transfer/examples.md +9 -0
- package/docs/CLI.md +10 -8
- package/docs/CONSTITUTION.md +54 -3
- package/docs/MATRIX.md +20 -3
- package/docs/errors/cdb/empty-group.md +19 -3
- package/docs/errors/cdb/empty-selection.md +9 -3
- package/docs/errors/cmr/caller-policy-not-enforced.md +46 -0
- package/docs/errors/cmr/cancelled.md +15 -0
- package/docs/errors/cmr/client-disconnected.md +19 -0
- package/docs/errors/cmr/client-script-not-validated.md +22 -0
- package/docs/errors/cmr/console-only.md +29 -0
- package/docs/errors/cmr/empty-selection.md +33 -0
- package/docs/errors/cmr/membership-changed.md +20 -0
- package/docs/errors/cmr/not-a-controller.md +26 -0
- package/docs/errors/cmr/outcome-unknown.md +29 -0
- package/docs/errors/cmr/output-truncated.md +16 -0
- package/docs/errors/cmr/policy-denied.md +31 -0
- package/docs/errors/cmr/rule-selects-every-device.md +32 -0
- package/docs/errors/cmr/selection-includes-controller.md +27 -0
- package/docs/errors/cmr/selection-incomplete.md +18 -0
- package/docs/errors/cmr/validation-incomplete.md +23 -0
- package/docs/errors/routeros/version-unsupported.md +4 -0
- package/docs/errors/transport/write-outcome-unknown.md +24 -0
- package/docs/errors/usage/flag-after-separator.md +34 -0
- package/docs/errors/usage/missing-labels.md +19 -0
- package/package.json +7 -5
- package/src/api-fanout.ts +18 -2
- package/src/api.ts +230 -71
- package/src/btest.ts +7 -2
- package/src/cli/api.ts +30 -16
- package/src/cli/common.ts +180 -1
- package/src/cli/devices.ts +0 -2
- package/src/cli/discover.ts +0 -1
- package/src/cli/execute.ts +176 -21
- package/src/cli/explain.ts +2 -0
- package/src/cli/mcp.ts +0 -1
- package/src/cli/retrieve.ts +40 -18
- package/src/cli/settings.ts +0 -3
- package/src/cli/terminal.ts +0 -2
- package/src/cli/transfer.ts +10 -8
- package/src/cli/version.ts +87 -0
- package/src/cli.ts +5 -3
- package/src/core/cmr-fleet.ts +486 -0
- package/src/core/cmr.ts +182 -0
- package/src/core/envelope.ts +46 -1
- package/src/core/error-catalog.ts +95 -2
- package/src/core/execute-command.ts +144 -19
- package/src/core/fanout.ts +207 -14
- package/src/errors.ts +3 -1
- package/src/execute-cmr.ts +1442 -0
- package/src/execute-fanout.ts +15 -1
- package/src/execute.ts +173 -40
- package/src/index.ts +35 -1
- package/src/mcp/safety.ts +11 -0
- package/src/mcp/tools.ts +4 -1
- package/src/protocols/adapter.ts +54 -12
- package/src/resolver/cdb.ts +13 -1
- package/src/resolver/index.ts +4 -0
- package/src/resolver/selection.ts +37 -1
- package/src/resolver/target.ts +96 -8
- package/src/retrieve-fanout.ts +4 -1
- package/src/retrieve-follow.ts +29 -33
- package/src/retrieve-sample.ts +14 -8
- package/src/retrieve-stream.ts +38 -6
- package/src/retrieve.ts +73 -59
- package/src/transfer-fanout.ts +8 -1
- package/src/transfer.ts +15 -19
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# `cmr/outcome-unknown`
|
|
2
|
+
|
|
3
|
+
`execute --cmr` sent the command to a CMR client, but no final status came back
|
|
4
|
+
for it. The command **may have run, may still be running, or may not have
|
|
5
|
+
run**.
|
|
6
|
+
|
|
7
|
+
## Typical trigger
|
|
8
|
+
|
|
9
|
+
- CMR reported `failed` with `timed out (6)` (the `timeout=` centrs passes, from
|
|
10
|
+
`--timeout`) or `std failure: timeout (13)` (CMR's own ~60 s ceiling per call).
|
|
11
|
+
- centrs stopped waiting: its local deadline passed, the operation was
|
|
12
|
+
cancelled, or the controller connection dropped after the request was sent.
|
|
13
|
+
- The client changed its CMR key (`ids`) during the call, or the controller's
|
|
14
|
+
inventory could not be re-read afterwards to confirm which client answered.
|
|
15
|
+
|
|
16
|
+
On RouterOS 7.26beta1, a timed-out or cancelled `run-script` **kept running on
|
|
17
|
+
the client**, and its side effects landed afterwards (#441, #450). A timeout is
|
|
18
|
+
not a rollback.
|
|
19
|
+
|
|
20
|
+
## Fix
|
|
21
|
+
|
|
22
|
+
- Check the client's state before doing anything else. Read the menu the
|
|
23
|
+
command changed, directly or through the controller.
|
|
24
|
+
- Do not re-run a command that is not idempotent until you know it did not run.
|
|
25
|
+
centrs never retries a CMR dispatch.
|
|
26
|
+
- For long work, raise `--timeout` (up to 55 s per call). Anything needing more
|
|
27
|
+
than CMR's ~60 s ceiling belongs in a script or scheduler entry on the client.
|
|
28
|
+
|
|
29
|
+
See [`commands/execute/README.md`](../../../commands/execute/README.md) → CMR.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# `cmr/output-truncated`
|
|
2
|
+
|
|
3
|
+
A warning: CMR clipped a client's command output at its cap (about 60 KiB on
|
|
4
|
+
7.26beta1) and appended `...(+N B)`. The command **completed**; only the
|
|
5
|
+
returned text is incomplete. The envelope's `meta.truncated` records it.
|
|
6
|
+
|
|
7
|
+
## Typical trigger
|
|
8
|
+
|
|
9
|
+
A command that prints a lot, such as a large `print` or `/export`, run with
|
|
10
|
+
`execute --cmr`.
|
|
11
|
+
|
|
12
|
+
## Fix
|
|
13
|
+
|
|
14
|
+
Narrow the output on the client: `print` with `where` and `proplist=`, or
|
|
15
|
+
`:put` only the fields you need. For a full configuration, save it to a file on
|
|
16
|
+
the client instead of printing it.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# `cmr/policy-denied`
|
|
2
|
+
|
|
3
|
+
A CMR client ran the command and refused one of its statements with
|
|
4
|
+
`not enough permissions (9)`.
|
|
5
|
+
|
|
6
|
+
CMR runs `run-script` scripts on each client with only the `read`, `write`,
|
|
7
|
+
`test` and `reboot` policies, and without a user session. Validation cannot
|
|
8
|
+
catch this: the client's `:parse` accepts the command, and the policy check
|
|
9
|
+
happens only when it runs.
|
|
10
|
+
|
|
11
|
+
The refused statement did not run, and the script stopped there. Statements
|
|
12
|
+
before it **did** run: CMR does not roll them back. Their output, followed by
|
|
13
|
+
the diagnostic, is in `context.output`.
|
|
14
|
+
|
|
15
|
+
## Typical trigger
|
|
16
|
+
|
|
17
|
+
- `/user add`, `/user group add` (needs `policy`).
|
|
18
|
+
- `/tool sniffer` (needs `sniff`).
|
|
19
|
+
|
|
20
|
+
Reading a sensitive value is not this error: it succeeds and returns `*` or an
|
|
21
|
+
empty value.
|
|
22
|
+
|
|
23
|
+
## Fix
|
|
24
|
+
|
|
25
|
+
Check what the earlier statements changed. Then run only the refused
|
|
26
|
+
statement, and the ones after it, on the device directly with a user that has
|
|
27
|
+
the policy:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
centrs execute <device> -- '<command>'
|
|
31
|
+
```
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# `cmr/rule-selects-every-device`
|
|
2
|
+
|
|
3
|
+
A warning on `execute` or `api` to a CMR controller: a `/cmr/vlan`,
|
|
4
|
+
`/cmr/wifi`, `/cmr/wifi/radio` or `/cmr/upgrade` rule command has no device
|
|
5
|
+
`labels`, so the rule applies to every device the controller manages, the
|
|
6
|
+
controller included. For `/cmr/vlan` it fires only when `port-labels` is
|
|
7
|
+
missing too.
|
|
8
|
+
|
|
9
|
+
## Typical trigger
|
|
10
|
+
|
|
11
|
+
- `/cmr/vlan/add vlan-ids=10` with no `labels` and no `port-labels`. The
|
|
12
|
+
manual warns that such a rule moves every port of every connected device
|
|
13
|
+
into `cmr-bridge`, the controller included, which cuts the whole fleet's
|
|
14
|
+
management access.
|
|
15
|
+
- `/cmr/wifi/add ssid=Office` with no `labels`, or labels that are only band
|
|
16
|
+
labels (`+5ghz`). The network takes over the radios of every device.
|
|
17
|
+
- `set`, `remove`, `enable` or `disable` of an existing rule that has no
|
|
18
|
+
`labels`. Its reach is every device too.
|
|
19
|
+
|
|
20
|
+
centrs reads the rule's `labels` from the command (`add`) or from the rule
|
|
21
|
+
itself (the other verbs). A rule added without `labels` stores none (7.26beta1
|
|
22
|
+
CHR). The command still runs as typed; the warning rides the envelope, and
|
|
23
|
+
the write gate's prompt and `usage/confirmation-required` summary repeat it.
|
|
24
|
+
|
|
25
|
+
## Fix
|
|
26
|
+
|
|
27
|
+
Give the rule explicit selectors, for example
|
|
28
|
+
`/cmr/vlan/add labels=office-ap port-labels=ether1 vlan-ids=10 role=trunk`.
|
|
29
|
+
Check which devices a label selects with
|
|
30
|
+
`centrs retrieve <controller> /cmr/device` (`labels`, `auto-labels`). Answer
|
|
31
|
+
`yes` or pass `--yes` only if every device, the controller included, is
|
|
32
|
+
intended.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# `cmr/selection-includes-controller`
|
|
2
|
+
|
|
3
|
+
A warning on `execute` or `api` to a CMR controller: a fleet command such as
|
|
4
|
+
`/cmr/device/reboot` or `/cmr/device/run-script` selects the controller's own
|
|
5
|
+
`/cmr/device` row (`controller=true`) as well as its clients. The command acts
|
|
6
|
+
on the controller too.
|
|
7
|
+
|
|
8
|
+
## Typical trigger
|
|
9
|
+
|
|
10
|
+
- `labels=all`: per the manual, `all` selects every device, the CMR server
|
|
11
|
+
included.
|
|
12
|
+
- An exclusion-only expression such as `labels=-core`, which starts from every
|
|
13
|
+
device.
|
|
14
|
+
- A label the controller also carries, such as its architecture (`x86`) or
|
|
15
|
+
RouterOS version, from `auto-labels`.
|
|
16
|
+
|
|
17
|
+
centrs reads the controller's inventory and evaluates the manual's label
|
|
18
|
+
rules over each row's `labels` and `auto-labels`. On a 7.26beta1 lab this
|
|
19
|
+
matched the device's own selection for every expression tried. The result is
|
|
20
|
+
the controller's current view: a disconnected device's `auto-labels` can be
|
|
21
|
+
stale. `context` names the controller and the number of devices selected.
|
|
22
|
+
|
|
23
|
+
## Fix
|
|
24
|
+
|
|
25
|
+
Nothing failed; this is information. To leave the controller out, exclude it
|
|
26
|
+
explicitly, for example `labels=all,-<controller identity>` (its identity is
|
|
27
|
+
an automatic label while `/cmr auto-labels` includes identity).
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# `cmr/selection-incomplete`
|
|
2
|
+
|
|
3
|
+
centrs asked the controller, for every `/cmr/device` row, whether it matches
|
|
4
|
+
`--labels`. At least one row got no answer, so the selection is not known and
|
|
5
|
+
**nothing was run**.
|
|
6
|
+
|
|
7
|
+
## Typical trigger
|
|
8
|
+
|
|
9
|
+
A per-device preflight call timed out, was cancelled, or lost its connection
|
|
10
|
+
before the controller either refused it (`no devices selected`, so not a
|
|
11
|
+
member) or returned a row (a member).
|
|
12
|
+
|
|
13
|
+
## Fix
|
|
14
|
+
|
|
15
|
+
- Check that the controller is reachable and responsive, then retry.
|
|
16
|
+
- Raise `--timeout` if the controller is slow.
|
|
17
|
+
- centrs never runs a partial selection, because a smaller set that reports
|
|
18
|
+
success would hide the clients it skipped.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# `cmr/validation-incomplete`
|
|
2
|
+
|
|
3
|
+
The validation result from a CMR client was incomplete, so the command was
|
|
4
|
+
**not** sent to it. The other members are unaffected.
|
|
5
|
+
|
|
6
|
+
## Typical trigger
|
|
7
|
+
|
|
8
|
+
centrs validates on each client with `:put [:parse <command>]; :put "<nonce>"`
|
|
9
|
+
through `run-script`. The result is rejected before it is classified when:
|
|
10
|
+
|
|
11
|
+
- the status was not `success` (for example `timed out (6)`);
|
|
12
|
+
- the output was clipped by CMR's ~60 KiB cap (`...(+N B)`);
|
|
13
|
+
- the nonce line that proves the output is complete is missing;
|
|
14
|
+
- the `:parse` return was empty or was not parse output (IL).
|
|
15
|
+
|
|
16
|
+
An empty or clipped result is not a pass. Without the completeness check,
|
|
17
|
+
missing output would have read as "no diagnostic".
|
|
18
|
+
|
|
19
|
+
## Fix
|
|
20
|
+
|
|
21
|
+
- Make sure the client is connected and responsive, then retry.
|
|
22
|
+
- Very large commands can exceed the output cap once `:parse` echoes them back.
|
|
23
|
+
Split the work into smaller commands.
|
|
@@ -9,6 +9,10 @@ error.
|
|
|
9
9
|
Distinct from `routeros/unsupported-capability`, where the chosen *protocol*
|
|
10
10
|
cannot carry the operation at all regardless of device version.
|
|
11
11
|
|
|
12
|
+
`execute --cmr` raises it with `capability: cmr-controller` when the target has
|
|
13
|
+
no `/cmr/device` and runs RouterOS older than 7.26beta1, where CMR first
|
|
14
|
+
shipped. That check reads the version only after the missing menu, not before.
|
|
15
|
+
|
|
12
16
|
## Context fields
|
|
13
17
|
|
|
14
18
|
- `capability` — stable slug of the missing operation.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# `transport/write-outcome-unknown`
|
|
2
|
+
|
|
3
|
+
A warning on a failed write. The request had already been sent when the
|
|
4
|
+
transport failed (a dropped connection, a REST 5xx, a timeout), so the router
|
|
5
|
+
may have run it. The error beside this warning says how the call ended, not
|
|
6
|
+
whether the write happened.
|
|
7
|
+
|
|
8
|
+
## Typical trigger
|
|
9
|
+
|
|
10
|
+
- A connection closed or reset after an `execute` or `api` write was sent.
|
|
11
|
+
- A REST 5xx or a timeout on a write.
|
|
12
|
+
|
|
13
|
+
centrs never retries a write that it has sent, in single-target or fan-out
|
|
14
|
+
mode, because replaying an `add`, a script run or a counter change would apply
|
|
15
|
+
it twice (#448). In fan-out, a write is retried only when a retryable
|
|
16
|
+
transport failure (`transport/network` or `transport/connection-closed`) ends
|
|
17
|
+
it *before* it was sent, for example during remote validation or a native API
|
|
18
|
+
login. Any other failure, sent or not, is not retried.
|
|
19
|
+
|
|
20
|
+
## Fix
|
|
21
|
+
|
|
22
|
+
Check the router's state before running the write again. For example, read
|
|
23
|
+
back the menu you changed with `centrs retrieve`. Re-run only what did not
|
|
24
|
+
apply.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# `usage/flag-after-separator`
|
|
2
|
+
|
|
3
|
+
A centrs flag was written after `--`, where every token is RouterOS input.
|
|
4
|
+
|
|
5
|
+
## Fix
|
|
6
|
+
|
|
7
|
+
Move the flag before `--`. The error's remediation prints the corrected
|
|
8
|
+
command line:
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
centrs execute r1 -- '/system/identity/print' --json # refused
|
|
12
|
+
centrs execute r1 --json -- '/system/identity/print' # fixed
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
A bare `--` ends centrs option parsing, so without this check a trailing
|
|
16
|
+
`--json` was sent to RouterOS and failed there as `unknown-attribute`. The check
|
|
17
|
+
covers `execute` (including `--cmr`) and `explain`. It matches only a token that
|
|
18
|
+
is exactly one of the command's long flags (or its `--flag=value` spelling);
|
|
19
|
+
short aliases such as `-u` pass through as RouterOS input. If RouterOS really
|
|
20
|
+
needs such a token, put it inside the quoted command. Password values are
|
|
21
|
+
replaced by `<password>` in the printed command line.
|
|
22
|
+
Credentials embedded in a positional target or a `--host` value are redacted
|
|
23
|
+
to `***@host`, including when `--host` is moved before the separator.
|
|
24
|
+
Credentials in an explicit URL are redacted even when its colon is percent-encoded;
|
|
25
|
+
bare CDB identities such as `rtr@site` remain unchanged. A bare target with
|
|
26
|
+
a percent-encoded colon is masked in diagnostics while remaining eligible
|
|
27
|
+
for CDB identity lookup.
|
|
28
|
+
|
|
29
|
+
For boolean flags that accept no value (such as `--json` and `--yes`), the
|
|
30
|
+
error explains how to remove an unsupported `=value` rather than printing
|
|
31
|
+
a command that the parser would reject.
|
|
32
|
+
|
|
33
|
+
See [`docs/CONSTITUTION.md`](../../CONSTITUTION.md) for the centrs error
|
|
34
|
+
contract.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# `usage/missing-labels`
|
|
2
|
+
|
|
3
|
+
`centrs execute --cmr` was given no `--labels` expression.
|
|
4
|
+
|
|
5
|
+
## Typical trigger
|
|
6
|
+
|
|
7
|
+
`--cmr` runs the command on the CMR clients a label expression selects, and the
|
|
8
|
+
expression is required so the blast radius is always explicit.
|
|
9
|
+
|
|
10
|
+
## Fix
|
|
11
|
+
|
|
12
|
+
Pass RouterOS label syntax: a comma list where plain labels are OR, `+label`
|
|
13
|
+
must match, `-label` excludes, and `all` selects every device, including the
|
|
14
|
+
controller:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
centrs execute ctl --cmr --labels all -- ':put [/system/identity/get name]'
|
|
18
|
+
centrs execute ctl --cmr --labels site-a,+edge,-ctl --yes -- '/ip/dns/set servers=192.0.2.53'
|
|
19
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tikoci/centrs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9-next.22",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"module": "src/index.ts",
|
|
6
6
|
"repository": {
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
},
|
|
10
10
|
"homepage": "https://github.com/tikoci/centrs#readme",
|
|
11
11
|
"bugs": "https://github.com/tikoci/centrs/issues",
|
|
12
|
-
"packageManager": "bun@1.
|
|
12
|
+
"packageManager": "bun@1.4.2",
|
|
13
13
|
"license": "MIT",
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"@biomejs/biome": "2.5.15",
|
|
16
16
|
"@secretlint/secretlint-rule-preset-recommend": "^13.0.7",
|
|
17
|
-
"@types/bun": "1.
|
|
17
|
+
"@types/bun": "1.4.2",
|
|
18
18
|
"cloc": "^2.6.0-cloc",
|
|
19
19
|
"cspell": "^10.3.6",
|
|
20
20
|
"has-flag": "4.0.0",
|
|
@@ -74,12 +74,14 @@
|
|
|
74
74
|
"lint:biome": "biome check .",
|
|
75
75
|
"lint:fix": "biome check --write .",
|
|
76
76
|
"lint:secretlint": "secretlint \"**/*\"",
|
|
77
|
+
"lint:actions": "uvx zizmor --offline .",
|
|
77
78
|
"test": "bun test",
|
|
78
79
|
"test:smoke": "bun test test/integration/cli-smoke.test.ts",
|
|
79
80
|
"test:integration:fast": "bun test test/integration/devices.test.ts test/integration/settings.test.ts",
|
|
80
81
|
"test:integration:settings": "bun test test/integration/settings.test.ts",
|
|
81
|
-
"
|
|
82
|
-
"test:integration": "CENTRS_RUN_FAST_INTEGRATION=1 bun test test/integration/",
|
|
82
|
+
"chr:prefetch": "bun run scripts/chr-prefetch.ts",
|
|
83
|
+
"test:integration:smoke": "bun run chr:prefetch && CENTRS_RUN_FAST_INTEGRATION=1 bun test test/integration/chr-smoke.test.ts",
|
|
84
|
+
"test:integration": "bun run chr:prefetch && CENTRS_RUN_FAST_INTEGRATION=1 bun test test/integration/",
|
|
83
85
|
"test:integration:stable": "CENTRS_CHR_CHANNEL=stable bun run test:integration",
|
|
84
86
|
"test:integration:long-term": "CENTRS_CHR_CHANNEL=long-term bun run test:integration",
|
|
85
87
|
"test:integration:testing": "CENTRS_CHR_CHANNEL=testing bun run test:integration",
|
package/src/api-fanout.ts
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
buildFanoutResolveFailure,
|
|
48
48
|
commonVia,
|
|
49
49
|
defaultFanoutSleep,
|
|
50
|
+
mayRetryUnsentWrite,
|
|
50
51
|
resolveFanoutConcurrency,
|
|
51
52
|
runFanout,
|
|
52
53
|
summarizeFanout,
|
|
@@ -61,9 +62,11 @@ import {
|
|
|
61
62
|
import { promptForWriteConfirmation } from "./execute.ts";
|
|
62
63
|
import { plannedProtocols, type RouterOsProtocol } from "./protocols/index.ts";
|
|
63
64
|
import {
|
|
65
|
+
emptyWriteSelectionError,
|
|
64
66
|
expandSelection,
|
|
65
67
|
isDefaultRecordTarget,
|
|
66
68
|
loadEnvFileDefaults,
|
|
69
|
+
redactTargetInput,
|
|
67
70
|
type SelectionExpansion,
|
|
68
71
|
type SelectionMember,
|
|
69
72
|
type SelectionResolveInput,
|
|
@@ -86,6 +89,8 @@ export interface ApiFanoutOperationMeta {
|
|
|
86
89
|
concurrency: number;
|
|
87
90
|
summary: FanoutSummary;
|
|
88
91
|
request: ApiRequestSummary;
|
|
92
|
+
/** Hoisted from the targets when every target used the same login (#461). */
|
|
93
|
+
auth?: { username?: string; passwordProvided: boolean };
|
|
89
94
|
selection: ApiSelectionSummary;
|
|
90
95
|
}
|
|
91
96
|
|
|
@@ -105,7 +110,10 @@ export interface ApiFanoutInternals {
|
|
|
105
110
|
env: Record<string, string | undefined>,
|
|
106
111
|
config?: Record<string, string | undefined>,
|
|
107
112
|
) => Promise<SelectionExpansion>;
|
|
108
|
-
execute?: (
|
|
113
|
+
execute?: (
|
|
114
|
+
resolved: ResolvedApiRequest,
|
|
115
|
+
trace: ValidationTrace,
|
|
116
|
+
) => Promise<ApiSuccessEnvelope>;
|
|
109
117
|
sleep?: (ms: number) => Promise<void>;
|
|
110
118
|
}
|
|
111
119
|
|
|
@@ -121,7 +129,7 @@ function summarizeSelection(selection: TargetSelection): ApiSelectionSummary {
|
|
|
121
129
|
where: selection.where.map((clause) => `${clause.key}=${clause.value}`),
|
|
122
130
|
all: selection.all,
|
|
123
131
|
default: selection.default,
|
|
124
|
-
positionals: selection.positionals,
|
|
132
|
+
positionals: selection.positionals.map((value) => redactTargetInput(value)),
|
|
125
133
|
quickchr: selection.quickchr,
|
|
126
134
|
};
|
|
127
135
|
}
|
|
@@ -238,6 +246,12 @@ export async function apiFanout(
|
|
|
238
246
|
const concurrency = resolveFanoutConcurrency(options.concurrency, via);
|
|
239
247
|
|
|
240
248
|
if (expansion.empty) {
|
|
249
|
+
if (requestSummary.write) {
|
|
250
|
+
throw emptyWriteSelectionError(
|
|
251
|
+
expansion,
|
|
252
|
+
`${method} ${requestSummary.path}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
241
255
|
return buildApiFanoutSuccess({
|
|
242
256
|
summary: { total: 0, ok: 0, failed: 0 },
|
|
243
257
|
targets: [],
|
|
@@ -318,6 +332,8 @@ export async function apiFanout(
|
|
|
318
332
|
),
|
|
319
333
|
member,
|
|
320
334
|
),
|
|
335
|
+
mayRetry: (resolved) =>
|
|
336
|
+
mayRetryUnsentWrite(requestSummary.write, traces.get(resolved)),
|
|
321
337
|
sleep,
|
|
322
338
|
});
|
|
323
339
|
|