hyperhive/swarm-logs/src/main.rs
iris 2346bef3b1 docs: gate write-good.Passive on CI
Both blockers are done — #4548 (353 hits in hand-written docs) and
#4549 (46 hits in generated CLI reference docs, hivectl/swarmctl/hive-forge's
own clap help text) — and the previous commit suppresses the reviewed
false-positive remainder. vale --minAlertLevel=error docs now returns
0 errors, matching CI's existing prose-lint-errors job.

Closes #4546.
2026-09-20 16:24:11 +02:00

128 lines
4.9 KiB
Rust

//! `swarm-logs` — an agent's CLI for the swarm log store.
//!
//! An agent can reach VictoriaLogs only through the gateway, and until this
//! existed the way to read it was to hand-roll a `client_credentials` token
//! request and a curl by hand, per query. That is the gap this closes: one
//! verb, a LogsQL string in, matched log lines out, so the answer pipes into
//! `grep` like any other command's.
//!
//! **Read-only and query-only.** It posts to the store's LogsQL query
//! endpoint and nothing else — there is no ingest path here, and no `tail`.
//! Streaming is a different endpoint with a different response shape and is
//! deliberately left for a follow-up rather than folded in.
//!
//! ⚠️ **The route applies no scoping.** `swarm-victorialogs.nix` forwards the
//! query unmodified, so any authenticated caller reads the whole swarm's
//! logs — every hive's, not just its own. That is the rule in force rather
//! than an omission on this binary's part; when read permissions exist they
//! attach at that location, not here.
//!
//! Distinct from `hive-metric`, which is the other half of the same
//! observability surface from an agent's seat: that one *writes* a custom
//! metric, this one *reads* logs.
mod auth;
mod query;
use anyhow::Result;
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(
name = "swarm-logs",
version,
about = "query the swarm's log store from an agent"
)]
struct Cli {
#[command(subcommand)]
command: Verb,
}
#[derive(Subcommand)]
enum Verb {
/// Run a LogsQL query and print the matched log lines.
Query {
/// The LogsQL query. A bare word is a full-text search for it.
///
/// Quote it: LogsQL uses characters the shell also claims, and an
/// unquoted `|` pipes this command into the rest of your query
/// instead of sending it.
logsql: String,
/// Stop after this many records.
///
/// Unset means the store's own default. Worth setting on a broad
/// query — nothing between here and the store narrows one, so a bare
/// word matches across every hive in the swarm.
#[arg(long)]
limit: Option<u64>,
/// What to print for each matched record.
#[arg(long, value_enum, default_value = "message")]
format: query::Format,
},
/// Print this CLI's reference as markdown (source of `docs/tools/swarm-logs-cli.md`).
///
/// No `completions` sibling, unlike `hivectl` and `swarmctl`: those are
/// installed on an operator's interactive host, where a completion script
/// has a shell to be sourced into. This one is run by an agent through a
/// tool call, which has no line editor to complete against — the verb
/// would exist only to be listed.
#[command(hide = true)]
MarkdownDocs,
}
fn main() -> Result<()> {
match Cli::parse().command {
Verb::Query {
logsql,
limit,
format,
} => {
// Config resolved inside this arm and not up front: the two
// verbs below are pure functions of the command tree and must
// keep working on a machine that has no log store wired up at
// all — `markdown-docs` in particular runs in a nix check, where
// no credential exists and none should.
let cfg = auth::Config::from_env()?;
let stdout = std::io::stdout();
query::run(&cfg, &logsql, limit, format, &mut stdout.lock())
}
Verb::MarkdownDocs => {
// `show_footer(false)`: drop clap-markdown's own fixed
// "generated automatically by..." footer, same as
// hivectl/swarmctl/hive-forge — it's not our prose, and
// write-good.Passive has nothing to check it against.
let options = clap_markdown::MarkdownOptions::new().show_footer(false);
print!("{}", clap_markdown::help_markdown_custom::<Cli>(&options));
Ok(())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory as _;
/// clap's own consistency check over the command tree — catches a
/// duplicate long flag or a malformed `about` at test time rather than on
/// the first run of a verb nobody exercised.
#[test]
fn the_command_tree_is_well_formed() {
Cli::command().debug_assert();
}
/// The default matters: it is what an agent gets when it pipes into
/// `grep` without reading the flags first.
#[test]
fn query_defaults_to_projecting_the_message() {
let cli = Cli::try_parse_from(["swarm-logs", "query", "a-marker"]).expect("parses");
let Verb::Query { format, limit, .. } = cli.command else {
panic!("expected the query verb");
};
assert_eq!(format, query::Format::Message);
assert_eq!(limit, None, "no client-side default limit is imposed");
}
}