shep_core/protocol/request/verbs.rs
1//! [`Request`], one variant per verb, and the selector a verb names a sheep with.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::config::{AppConfig, DeclaredApp, DogTable, ResetDepth};
8
9use super::{DogSectionToml, DogSource, EnvValue, SelectorSpec, Smit};
10
11// Named by intra-doc links and by nothing rustc compiles, so the
12// import is behind `cfg(doc)` rather than flagged unused.
13#[cfg(doc)]
14use super::{Response, RpcErrorCode, SheepApplied, SheepDrift};
15
16/// One RPC request
17// wire format: changing existing variants is a breaking change
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19#[serde(tag = "kind", rename_all = "snake_case")]
20#[non_exhaustive]
21pub enum Request {
22 /// Liveness check
23 Ping,
24 /// Full flock listing
25 ListFlock,
26 /// What the machine the flock runs on is doing
27 ///
28 /// Answers [`Response::HostUsage`]. Its own request rather than a field
29 /// on [`Response::Flock`]: that variant serializes as an array, and
30 /// giving it a second field would retype it into an object that no
31 /// shipped client can read. A daemon that has never heard of this one
32 /// decodes [`Self::Unrecognized`] and refuses by name, which costs the
33 /// caller the host block and nothing else.
34 HostUsage,
35 /// Detailed info for matching sheep
36 Describe {
37 /// Which sheep
38 selector: SelectorSpec,
39 },
40 /// Register + start apps
41 Start {
42 /// App configs. The daemon must re-normalize them, since peer input
43 /// is untrusted; failures return [`RpcErrorCode::InvalidConfig`]
44 apps: Vec<AppConfig>,
45 },
46 /// Register apps as flock members without starting any of them
47 ///
48 /// Each app lands `Stopped` and holds no pid; `shep add` is the verb.
49 ///
50 /// Idempotent by name: an app the flock already has is answered as it
51 /// stands, running or not, and nothing about it changes.
52 /// [`Self::ApplyConfig`] merges a template into one the flock already
53 /// has, and `shep add` sends both.
54 ///
55 /// Answers [`Response::Added`].
56 Add {
57 /// App configs, carried exactly as [`Self::Start`] carries them. The
58 /// daemon must re-normalize them, since peer input is untrusted;
59 /// failures return [`RpcErrorCode::InvalidConfig`]
60 apps: Vec<AppConfig>,
61 },
62 /// Ask which of `apps` name a sheep the flock already has under a
63 /// different config
64 ///
65 /// Read-only. [`Self::Start`] on an already-registered name adds
66 /// instances rather than reconciling config.
67 ///
68 /// Answers [`Response::Drifted`] with one [`SheepDrift`] per app that is
69 /// both registered and different. An app the flock does not have is
70 /// absent from the answer, not reported as unchanged.
71 ConfigDrift {
72 /// The configs to compare against, exactly as [`Self::Start`] would
73 /// carry them. The daemon must re-normalize them: peer input is
74 /// untrusted, and an unnormalized config would report every default
75 /// it has not spelled out as a difference. Failures return
76 /// [`RpcErrorCode::InvalidConfig`].
77 apps: Vec<AppConfig>,
78 },
79 /// Merge each declared app into the sheep of the same name, applying
80 /// what can be applied and parking the rest for that sheep's next spawn
81 ///
82 /// Nothing is registered, nothing is pruned and nothing running is
83 /// killed: an app the flock does not have is refused by name, and a
84 /// field the running child was spawned from waits for a `shep reload`.
85 /// Additive by default; `reset` widens it.
86 ///
87 /// Answers [`Response::Applied`] with one [`SheepApplied`] per entry in
88 /// `apps`, in the order given, found or not and changed or not. One
89 /// app's refusal rides in [`SheepApplied::refused`] and does not cost
90 /// the rest of the file its load.
91 ApplyConfig {
92 /// The apps to merge in, each carrying the keys its document
93 /// literally wrote. The daemon must re-normalize the merge result,
94 /// since peer input is untrusted, and refuses the whole request with
95 /// [`RpcErrorCode::InvalidConfig`] when two entries share a name:
96 /// the second would be merged against a store the first has not
97 /// written yet.
98 apps: Vec<DeclaredApp>,
99 /// How much of what the operator has set since a template last
100 /// loaded this request may overwrite. Default [`ResetDepth::None`],
101 /// which overwrites nothing.
102 ///
103 /// Spelled `none`/`file`/`env`/`policy` on the wire.
104 reset: ResetDepth,
105 },
106 /// One sheep's effective config, for a pane that is about to edit it.
107 ///
108 /// `env` comes back emptied and its key names ride separately, so a
109 /// value never crosses the wire. Read-only: nothing about the sheep
110 /// changes.
111 ///
112 /// Answers [`Response::SheepConfig`], or
113 /// [`RpcErrorCode::NotFound`] when no sheep has that name.
114 SheepConfig {
115 /// The sheep's name, not a selector: a pane edits one sheep, for
116 /// the reason [`Self::Scale`] states at length.
117 name: String,
118 },
119 /// Sets, replaces, or with `None` removes one env key on one sheep,
120 /// recorded as an operator override. Never reads it back.
121 ///
122 /// Its own request rather than a [`Self::ApplyConfig`] depth, because
123 /// no depth does this: `ResetDepth::None` appends only, `File` and
124 /// `Policy` leave env alone, and `Env`/`All` replace the whole map with
125 /// the template's. A pane cannot send the whole map, since it is never
126 /// told the values it would have to send back.
127 ///
128 /// The running child holds the env it was spawned from, so the change
129 /// parks for the next spawn exactly as `ApplyConfig` parks a
130 /// respawn-only field, and `shep reload`/`shep restart` promote it.
131 ///
132 /// Answers [`Response::SheepEnvSet`], or
133 /// [`RpcErrorCode::NotFound`] when no sheep has that name.
134 SetSheepEnv {
135 /// The sheep's name, not a selector, for [`Self::SheepConfig`]'s
136 /// reason.
137 name: String,
138 /// The env key.
139 key: String,
140 /// The value, or `None` to remove the key.
141 ///
142 /// [`EnvValue`], not a bare `String`, for the reason that type's
143 /// own doc gives: this is the most secret-dense field on the wire
144 /// and a derived `Debug` on [`Request`] would print it (IR-41).
145 value: Option<EnvValue>,
146 },
147 /// Sets several env keys on one sheep in a single write.
148 ///
149 /// [`Self::SetSheepEnv`]'s doc says a request taking a map would need
150 /// per-key reporting back "for no caller that wants it". `shep import
151 /// env` is that caller: it writes twenty keys at once and has to refuse
152 /// the whole set rather than leave eleven of them applied.
153 ///
154 /// The daemon applies this as one read-modify-write of the override
155 /// store, so every key lands or none does. No removal arm: a pane
156 /// deletes rows one at a time through [`Self::SetSheepEnv`], and an
157 /// import never removes anything.
158 ///
159 /// A key already holding a different value is a collision. Without
160 /// `force` any collision refuses the whole request and writes nothing;
161 /// with it, the collisions are overwritten and named in the reply as
162 /// well as counted in `set`. A key already holding the same value is
163 /// `unchanged`, never a collision, so re-running an unchanged import
164 /// needs no flag.
165 ///
166 /// `dry_run` computes the three lists and writes nothing.
167 ///
168 /// Parks for the next spawn, exactly as [`Self::SetSheepEnv`] does.
169 ///
170 /// Answers [`Response::SheepEnvBatch`], or
171 /// [`RpcErrorCode::NotFound`] when no sheep has that name.
172 SetSheepEnvBatch {
173 /// The sheep's name.
174 name: String,
175 /// The keys and their values.
176 ///
177 /// [`EnvValue`], not `String`, for [`Self::SetSheepEnv`]'s reason:
178 /// `Request` derives `Debug` and this map is the densest run of
179 /// secrets on the wire (IR-41).
180 entries: BTreeMap<String, EnvValue>,
181 /// Overwrite colliding keys instead of refusing.
182 force: bool,
183 /// Report what would happen and write nothing.
184 dry_run: bool,
185 },
186 /// Sets one config field on one sheep, recorded as an operator
187 /// override.
188 ///
189 /// [`Self::SetSheepEnv`]'s twin for everything that is not `env`, and
190 /// it exists for the reason that one does rather than by symmetry.
191 /// [`Self::ApplyConfig`] can move a single field (one [`DeclaredApp`]
192 /// declaring one key, at [`ResetDepth::File`]), but it moves it as a
193 /// template and spends the operator's override for it. That reasoning
194 /// does not hold here: a pane's value is the operator's, and the sheep
195 /// still differs from its file. Routed through `ApplyConfig`, the `*`
196 /// marker would never appear for that edit.
197 ///
198 /// One field, not a map: a pane edits one row at a time, and a request
199 /// that took several would need [`Response::Applied`]'s per-field
200 /// reporting back again for no caller that wants it.
201 ///
202 /// `env` is refused here and goes through [`Self::SetSheepEnv`]. So are
203 /// `name` and `instances`, which are
204 /// [`ApplyGroup::Structural`](crate::config::ApplyGroup::Structural):
205 /// identity and flock shape rather than runtime knobs, and the count
206 /// moves through [`Self::Scale`].
207 ///
208 /// The four-way apply classification governs exactly as it does for a
209 /// load. A `Live` field is in force at the daemon's next decision, a
210 /// `NextSpawn` field reaches the stored spec, and a `NeedsRespawn`
211 /// field parks for `shep reload` to promote.
212 ///
213 /// Answers [`Response::SheepFieldSet`], or
214 /// [`RpcErrorCode::NotFound`] when no sheep has that name.
215 SetSheepField {
216 /// The sheep's name, not a selector, for [`Self::SheepConfig`]'s
217 /// reason.
218 name: String,
219 /// The [`AppConfig`] field to set. A key that type has no such
220 /// field is refused with [`RpcErrorCode::InvalidConfig`] rather
221 /// than ignored.
222 key: String,
223 /// The new value, in the shape that field serializes as. The daemon
224 /// must re-validate the resulting config (peer input is untrusted)
225 /// and refuses with [`RpcErrorCode::InvalidConfig`] when it does not
226 /// deserialize or does not normalize; nothing is written in either
227 /// case.
228 ///
229 /// A bare [`serde_json::Value`] and not a redacting newtype, unlike
230 /// [`Self::SetSheepEnv`]'s [`EnvValue`], and the asymmetry is
231 /// deliberate. `env` is the one field [`AppConfig`]'s own manual
232 /// `Debug` redacts; `cwd`, `script` and `args` are printed in the
233 /// clear by every request that already carries a whole config
234 /// ([`Self::Start`], [`Self::Add`], [`Self::ApplyConfig`]). A
235 /// newtype here would protect one copy of a value this enum prints
236 /// three other ways, which reads as a guarantee the wire does not
237 /// make. Widening that protection is a change to [`AppConfig`]'s
238 /// `Debug`, not to this field.
239 value: serde_json::Value,
240 },
241 /// One dog's `[app.dogs.<name>]` table, for every sheep carrying one.
242 ///
243 /// It reads the stored spec, which is in force because `dogs` is
244 /// [`ApplyGroup::Live`](crate::config::ApplyGroup::Live). A sheep with
245 /// no table for `dog` is absent. `dog` is self-declared, as it is for
246 /// [`Self::DogConfig`], so the scoping is a convenience: the boundary
247 /// is the socket.
248 ///
249 /// Answers [`Response::DogSheepSettings`] with an empty map when no
250 /// sheep names `dog`, never [`RpcErrorCode::NotFound`]: unlike
251 /// [`Self::SheepConfig`], this does not name one sheep that could be
252 /// missing.
253 DogSheepSettings {
254 /// The dog's own name, the config key.
255 dog: String,
256 },
257 /// Sets, replaces, or with `None` removes one dog's table on one sheep,
258 /// recorded as an operator override of the whole `dogs` field so the
259 /// `*` marker shows, and publishes `config.sheep.<dog>` so a running
260 /// dog re-reads its tables.
261 ///
262 /// A single dog's table, not the whole `dogs` map:
263 /// [`Self::SetSheepField`] refuses the key `dogs` and names this
264 /// request, the way it refuses `env`, since a whole-map write from a
265 /// pane editing one dog would overwrite a concurrent edit to another
266 /// dog's table on the same sheep.
267 ///
268 /// Answers [`Response::SheepDogSettingsSet`], [`RpcErrorCode::NotFound`]
269 /// when no sheep has `name`, and refuses a dog's own name the way
270 /// [`Self::SetSheepField`] does.
271 SetSheepDogSettings {
272 /// The sheep's name, not a selector, for [`Self::SheepConfig`]'s
273 /// reason.
274 name: String,
275 /// Which dog's table to write.
276 dog: String,
277 /// The new table, or `None` to remove it.
278 table: Option<DogTable>,
279 },
280 /// Replaces one dog's `[<name>]` section in `dogs.toml` and publishes
281 /// `config.dog.<name>` so a running dog re-reads it.
282 ///
283 /// The writing twin of [`Self::DogConfig`], which reads the same
284 /// section.
285 ///
286 /// Answers [`Response::DogConfigSet`].
287 SetDogConfig {
288 /// The dog's name, the config key.
289 name: String,
290 /// The whole section, as TOML text.
291 ///
292 /// [`DogSectionToml`], not a bare `String`, for the reason that
293 /// type's own doc gives: a section can hold a dog's credentials and
294 /// this is what keeps them out of a `{:?}` (IR-41).
295 toml: DogSectionToml,
296 },
297 /// A provider dog's values for one namespace and one environment.
298 ///
299 /// Replaces that pair rather than merging into it, so a key deleted at
300 /// the provider disappears here on the next push instead of lingering.
301 ///
302 /// `namespace` is the dog's own registered name. It is bookkeeping, not
303 /// authorization: `Hello::dog_name` is self-declared and nothing checks
304 /// it against the spawn. The boundary is the socket itself, which lives
305 /// under `$SHEP_HOME` at `0700`.
306 ///
307 /// The two names and every entry key are checked against
308 /// [`crate::secrets::is_name`], and a value against
309 /// [`crate::secrets::MAX_VALUE_BYTES`], the same cap the operator's own
310 /// store enforces. One offender refuses the whole push rather than
311 /// dropping its own entry, so a dog never reads `accepted` for a set
312 /// that was stored in part.
313 ///
314 /// Answers [`Response::SecretsPut`].
315 PutSecrets {
316 /// The dog's registered name.
317 namespace: String,
318 /// Which environment these values are for.
319 environment: String,
320 /// The values, keyed by secret name. [`EnvValue`] so a `{:?}` of
321 /// this request cannot print them.
322 entries: BTreeMap<String, EnvValue>,
323 },
324 /// Stop matching sheep (stay registered)
325 Stop {
326 /// Which sheep
327 selector: SelectorSpec,
328 },
329 /// Restart matching sheep
330 Restart {
331 /// Which sheep
332 selector: SelectorSpec,
333 },
334 /// Replace each matching sheep with a fresh instance of the same app, one
335 /// instance of an app at a time, so the app has a window in which it can
336 /// stay reachable across the swap
337 Reload {
338 /// Which sheep. No default: a reload replaces running processes.
339 selector: SelectorSpec,
340 },
341 /// Stop matching sheep and refuse to spawn them again until `Enable`.
342 ///
343 /// The mark belongs to the app, so a selector naming an instance (an id
344 /// or a slot) is widened to that instance's app. Answers
345 /// [`Response::Disabled`].
346 Disable {
347 /// Which sheep
348 selector: SelectorSpec,
349 },
350 /// Clear the disabled mark on matching sheep and start them.
351 ///
352 /// A selector matching a sheep that is not disabled changes nothing.
353 /// Answers [`Response::Enabled`].
354 Enable {
355 /// Which sheep
356 selector: SelectorSpec,
357 },
358 /// Stop + deregister matching sheep
359 Delete {
360 /// Which sheep
361 selector: SelectorSpec,
362 },
363 /// Set how many instances one app runs (see `shep stock`).
364 ///
365 /// Takes a name where every other verb takes a [`SelectorSpec`]:
366 /// `instances` is a per-app number and slots are allocated against the
367 /// same-name group, so a selector matching two apps could mean four of
368 /// each or four in total.
369 ///
370 /// The count is absolute: two operators sending `+2` against the same
371 /// app would get a number neither asked for.
372 Scale {
373 /// The app's name, exactly as its config spells it. Not a selector: no
374 /// `all`, no regex, no `fold:`.
375 name: String,
376 /// How many instances the app has when this returns. `0` is refused
377 /// with [`RpcErrorCode::InvalidConfig`]: `shep delete` is the verb
378 /// for removing an app.
379 count: u32,
380 },
381 /// Attach a short marker to `sheep` for `shep flock` to paint, or clear
382 /// it with `None`.
383 ///
384 /// By name, not a selector: a smit belongs to a sheep, and every
385 /// instance of that name shows it, one spawned after the paint included.
386 ///
387 /// Held in memory and scoped to the connection that sent it, so a
388 /// publisher republishes rather than publishing on change.
389 SetSmit {
390 /// Which sheep.
391 sheep: String,
392 /// The marker, or `None` to clear it.
393 smit: Option<Smit>,
394 },
395 /// Reopen every matched sheep's log files, for an external rotator that
396 /// has renamed them (`create`-mode rotation)
397 Reopen {
398 /// Which sheep
399 selector: SelectorSpec,
400 },
401 /// Empty every matched sheep's log files: flush what is still pending,
402 /// then truncate the recorded paths
403 Flush {
404 /// Which sheep. No default: this destroys log data.
405 selector: SelectorSpec,
406 },
407 /// Send a named action to every matched sheep over its shepherd channel
408 /// and report what each app says back (see `shep trigger`).
409 Trigger {
410 /// Which sheep. No default, matching every other verb that reaches
411 /// a running process.
412 selector: SelectorSpec,
413 /// The action name. Free-form: the daemon never declares, parses, or
414 /// validates it, and an app that does not recognize the name is
415 /// expected to say so in its own reply.
416 action: String,
417 /// Argument text, passed through to the app verbatim. One opaque
418 /// string, matching the shepherd channel's own `action` message this
419 /// becomes.
420 params: Option<String>,
421 },
422 /// Deliver one signal to every matched sheep's own process, never its
423 /// process group (see `shep signal`).
424 Signal {
425 /// Which sheep. No default, matching every other verb that reaches
426 /// a running process.
427 selector: SelectorSpec,
428 /// The signal's name, as
429 /// [`OperatorSignal`](crate::signals::OperatorSignal) spells it. The
430 /// `SIG` prefix and the case are both optional; a name outside the
431 /// grammar answers [`RpcErrorCode::InvalidConfig`].
432 signal: String,
433 },
434 /// Write one line to every matched sheep's stdin (see `shep whisper`).
435 SendLine {
436 /// Which sheep. No default, matching every other verb that reaches a
437 /// running process.
438 selector: SelectorSpec,
439 /// The line, without its terminator: the shepherd appends exactly
440 /// one `\n` when it writes.
441 ///
442 /// A line containing an embedded newline is refused
443 /// ([`RpcErrorCode::InvalidConfig`]): it would deliver two commands
444 /// where the operator typed one.
445 line: String,
446 },
447 /// Write the muster roll now, bypassing the snapshot writer's debounce
448 SaveRoll,
449 /// Assemble the flock from the muster roll on disk: start every app the
450 /// roll recorded running, leaving every app the flock already has exactly
451 /// as it stands
452 Muster,
453 /// Ask for one dog's `[dog.<name>]` section, as the dog itself parses it
454 ///
455 /// The text as the operator wrote it: a `{{secret:...}}` reference comes
456 /// back as a reference. An editor must use this one. Lookout reads the
457 /// section and writes the whole text back through [`Self::SetDogConfig`],
458 /// so a resolved read would put the credential into `dogs.toml`.
459 ///
460 /// Answers [`Response::DogSection`].
461 DogConfig {
462 /// The dog's name: the config key, not a selector
463 name: String,
464 },
465 /// Ask for one dog's section with every `{{secret:...}}` reference
466 /// replaced by its value, resolved in the shepherd's own environment
467 ///
468 /// For a dog that consumes its configuration. [`Self::DogConfig`] is
469 /// unchanged and stays the raw read. A reference that cannot resolve
470 /// fails the request with [`RpcErrorCode::InvalidConfig`], naming the
471 /// key path and the reference and never a value. `dogs.toml` is never
472 /// rewritten.
473 ///
474 /// Answers [`Response::DogSection`]. Not a protocol bump, as
475 /// [`Self::HostUsage`] was not: a shepherd that has never heard of this
476 /// one decodes [`Self::Unrecognized`] and refuses by name.
477 DogConfigResolved {
478 /// The dog's name: the config key, not a selector
479 name: String,
480 },
481 /// Start one dog now, marking it as coming from `source`
482 EnableDog {
483 /// The dog's name
484 name: String,
485 /// Where its binary comes from
486 source: DogSource,
487 },
488 /// Stop and deregister one dog
489 ///
490 /// Answers [`Response::Deleted`]: disabling deregisters exactly as
491 /// `Delete` does.
492 DisableDog {
493 /// The dog's name
494 name: String,
495 },
496 /// Ask which dogs this daemon has given up on, and which it is still
497 /// waiting to hear from (`shep daemon reload`).
498 ///
499 /// Read-only, and about this daemon's own handshakes: take the reading
500 /// after a reload, not before one. Never sent to an older daemon, on
501 /// [`Self::HandoverFitness`]'s terms.
502 ///
503 /// Answers [`Response::DogStaleness`].
504 DogStaleness,
505 /// Ask whether this daemon could hand its flock to a successor in place,
506 /// rather than stopping it and starting it again (`shep daemon reload`).
507 ///
508 /// Read-only: the handover itself is triggered by a signal, which reaches
509 /// a daemon that refuses the client at the handshake.
510 ///
511 /// Answers [`Response::HandoverFitness`]. A refusal is a feature the
512 /// running daemon cannot carry, not an error: the caller falls back to a
513 /// stop-and-start and prints the reason. Never sent to an older daemon:
514 /// shep-cli's `commands::daemon` gates it on the crate version the
515 /// handshake reported.
516 HandoverFitness,
517 /// Graceful daemon shutdown
518 KillDaemon,
519 /// Subscribe this connection to bus topics (glob patterns)
520 Subscribe {
521 /// Topic globs, e.g. `process.*`
522 topics: Vec<String>,
523 },
524 /// A request kind this build has not been taught.
525 ///
526 /// `#[serde(other)]`, which serde allows here because `Request` is
527 /// internally tagged and this variant carries nothing. The unknown
528 /// body's own fields are discarded: the only thing to do with a
529 /// request we cannot name is refuse it, and the refusal needs the
530 /// envelope's id rather than the body.
531 #[serde(other)]
532 Unrecognized,
533}
534
535#[cfg(test)]
536mod tests;