refactor(#1474): group journalctl read args into a journalquery struct

This commit is contained in:
damocles 2026-06-08 20:26:15 +02:00 committed by mara
commit f751c4495f
7 changed files with 77 additions and 116 deletions

View file

@ -579,14 +579,15 @@ pub async fn dispatch_host_journal(
tracing::info!(%agent, machine = %c, %n, "get_host_journal (container)"); tracing::info!(%agent, machine = %c, %n, "get_host_journal (container)");
return match crate::priv_client::read_container_journal( return match crate::priv_client::read_container_journal(
c, c,
n, hive_sh4re::priv_proto::JournalQuery {
false, lines: n,
hive_sh4re::priv_proto::JournalOutput::Short, unit: unit.clone(),
unit.clone(), priority: priority.as_ref().map(|p| p.as_str().to_owned()),
priority.as_ref().map(|p| p.as_str().to_owned()), grep: grep.clone(),
grep.clone(), since: since.clone(),
since.clone(), until: until.clone(),
until.clone(), ..Default::default()
},
) )
.await .await
{ {

View file

@ -1225,14 +1225,13 @@ async fn get_journal(
}; };
match crate::priv_client::read_container_journal( match crate::priv_client::read_container_journal(
&prefixed, &prefixed,
lines, hive_sh4re::priv_proto::JournalQuery {
true, lines,
hive_sh4re::priv_proto::JournalOutput::ShortIso, boot: true,
unit, output: hive_sh4re::priv_proto::JournalOutput::ShortIso,
None, unit,
None, ..Default::default()
None, },
None,
) )
.await .await
{ {

View file

@ -1450,14 +1450,10 @@ async fn container_journal_tail(container: &str) -> String {
// is delegated to hive-priv (hive-c0re itself runs unprivileged). // is delegated to hive-priv (hive-c0re itself runs unprivileged).
let res = crate::priv_client::read_container_journal( let res = crate::priv_client::read_container_journal(
container, container,
40, hive_sh4re::priv_proto::JournalQuery {
false, lines: 40,
hive_sh4re::priv_proto::JournalOutput::Short, ..Default::default()
None, },
None,
None,
None,
None,
) )
.await; .await;
match res { match res {

View file

@ -233,14 +233,10 @@ async fn dispatch(req: &ManagerRequest, coord: &Arc<Coordinator>) -> ManagerResp
tracing::info!(%agent, %machine, %n, "manager: get_logs"); tracing::info!(%agent, %machine, %n, "manager: get_logs");
match crate::priv_client::read_container_journal( match crate::priv_client::read_container_journal(
&machine, &machine,
n, hive_sh4re::priv_proto::JournalQuery {
false, lines: n,
hive_sh4re::priv_proto::JournalOutput::Short, ..Default::default()
None, },
None,
None,
None,
None,
) )
.await .await
{ {

View file

@ -8,7 +8,7 @@
use anyhow::{Context as _, Result, bail}; use anyhow::{Context as _, Result, bail};
use hive_sh4re::priv_proto::{ use hive_sh4re::priv_proto::{
BindMount, JournalOutput, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, BindMount, JournalQuery, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse,
PrivStream, PrivStream,
}; };
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader}; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
@ -162,29 +162,14 @@ pub async fn list_containers() -> Result<String> {
/// Read a container's journal via the root helper (`journalctl -M`). /// Read a container's journal via the root helper (`journalctl -M`).
/// Returns `(stdout, stderr)`; a non-zero journalctl exit is reported in /// Returns `(stdout, stderr)`; a non-zero journalctl exit is reported in
/// `stderr` rather than as an `Err`, so callers can surface either. /// `stderr` rather than as an `Err`, so callers can surface either.
#[allow(clippy::too_many_arguments)]
pub async fn read_container_journal( pub async fn read_container_journal(
container: &str, container: &str,
lines: u32, query: JournalQuery,
boot: bool,
output: JournalOutput,
unit: Option<String>,
priority: Option<String>,
grep: Option<String>,
since: Option<String>,
until: Option<String>,
) -> Result<(String, String)> { ) -> Result<(String, String)> {
check( check(
call(&PrivRequest::ReadContainerJournal { call(&PrivRequest::ReadContainerJournal {
container: container.to_owned(), container: container.to_owned(),
lines, query,
boot,
output,
unit,
priority,
grep,
since,
until,
}) })
.await?, .await?,
) )

View file

@ -21,7 +21,7 @@ use std::path::{Path, PathBuf};
use anyhow::{Context as _, Result, bail}; use anyhow::{Context as _, Result, bail};
use hive_sh4re::priv_proto::{ use hive_sh4re::priv_proto::{
AGENT_PREFIX, AGENT_STATE_ROOT, BindMount, JournalOutput, MANAGER_NAME, META_DIR, AGENT_PREFIX, AGENT_STATE_ROOT, BindMount, JournalQuery, MANAGER_NAME, META_DIR,
NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream, PrivStreamLine, NetworkIsolation, PRIV_SOCK, PrivEvent, PrivRequest, PrivResponse, PrivStream, PrivStreamLine,
SIBLING_CONTAINERS, SIBLING_CONTAINERS,
}; };
@ -222,28 +222,10 @@ async fn exec(req: PrivRequest, writer: &mut OwnedWriteHalf) -> Result<(String,
PrivRequest::ReadContainerJournal { PrivRequest::ReadContainerJournal {
ref container, ref container,
lines, ref query,
boot,
output,
ref unit,
ref priority,
ref grep,
ref since,
ref until,
} => { } => {
validate_container_system_name(container)?; validate_container_system_name(container)?;
read_container_journal( read_container_journal(container, query).await
container,
lines,
boot,
output,
unit.as_deref(),
priority.as_deref(),
grep.as_deref(),
since.as_deref(),
until.as_deref(),
)
.await
} }
PrivRequest::WriteNspawnFlags { PrivRequest::WriteNspawnFlags {
@ -606,46 +588,35 @@ async fn container_run_streaming(
/// hard error — journalctl's own diagnostic (folded into `stderr` with /// hard error — journalctl's own diagnostic (folded into `stderr` with
/// the exit status) is what the caller surfaces to the operator, so the /// the exit status) is what the caller surfaces to the operator, so the
/// helper never bails. /// helper never bails.
#[allow(clippy::too_many_arguments)] async fn read_container_journal(container: &str, query: &JournalQuery) -> Result<(String, String)> {
async fn read_container_journal(
container: &str,
lines: u32,
boot: bool,
output: JournalOutput,
unit: Option<&str>,
priority: Option<&str>,
grep: Option<&str>,
since: Option<&str>,
until: Option<&str>,
) -> Result<(String, String)> {
let mut args: Vec<String> = vec![ let mut args: Vec<String> = vec![
"-M".to_owned(), "-M".to_owned(),
container.to_owned(), container.to_owned(),
"--no-pager".to_owned(), "--no-pager".to_owned(),
format!("--output={}", output.as_journalctl()), format!("--output={}", query.output.as_journalctl()),
"-n".to_owned(), "-n".to_owned(),
lines.to_string(), query.lines.to_string(),
]; ];
if boot { if query.boot {
args.push("-b".to_owned()); args.push("-b".to_owned());
} }
if let Some(u) = unit { if let Some(u) = &query.unit {
args.push("-u".to_owned()); args.push("-u".to_owned());
args.push(u.to_owned()); args.push(u.clone());
} }
if let Some(p) = priority { if let Some(p) = &query.priority {
args.push("-p".to_owned()); args.push("-p".to_owned());
args.push(p.to_owned()); args.push(p.clone());
} }
// `--grep=`/`--since=`/`--until=` use the `=`-joined form so a value // `--grep=`/`--since=`/`--until=` use the `=`-joined form so a value
// can never be parsed as a separate journalctl flag. // can never be parsed as a separate journalctl flag.
if let Some(g) = grep { if let Some(g) = &query.grep {
args.push(format!("--grep={g}")); args.push(format!("--grep={g}"));
} }
if let Some(s) = since { if let Some(s) = &query.since {
args.push(format!("--since={s}")); args.push(format!("--since={s}"));
} }
if let Some(u) = until { if let Some(u) = &query.until {
args.push(format!("--until={u}")); args.push(format!("--until={u}"));
} }
let out = Command::new("journalctl") let out = Command::new("journalctl")

View file

@ -52,6 +52,40 @@ impl JournalOutput {
} }
} }
/// journalctl knobs for `ReadContainerJournal`. Grouped into one value so
/// the read-journal call chain (`priv_client::read_container_journal` →
/// hive-priv's executor) and its several hive-c0re callers pass a single
/// struct instead of eight positional args that travelled together 1:1.
/// `Default` is the common case (last N lines, short format, no filters);
/// callers fill `lines` and override only the knobs they need via
/// `..Default::default()`.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct JournalQuery {
/// `-n <lines>`.
pub lines: u32,
/// `-b` — restrict to the current boot.
#[serde(default)]
pub boot: bool,
/// `--output=<...>`.
#[serde(default)]
pub output: JournalOutput,
/// `-u <unit>`.
#[serde(default)]
pub unit: Option<String>,
/// `-p <priority>`.
#[serde(default)]
pub priority: Option<String>,
/// `--grep=<regex>`.
#[serde(default)]
pub grep: Option<String>,
/// `--since=<ts>`.
#[serde(default)]
pub since: Option<String>,
/// `--until=<ts>`.
#[serde(default)]
pub until: Option<String>,
}
/// One bind-mount entry for `WriteNspawnFlags`. /// One bind-mount entry for `WriteNspawnFlags`.
/// hive-priv constructs `--bind=<host_path>:<container_path>` (or `--bind-ro=`) /// hive-priv constructs `--bind=<host_path>:<container_path>` (or `--bind-ro=`)
/// and validates both paths before writing the conf file. /// and validates both paths before writing the conf file.
@ -132,29 +166,8 @@ pub enum PrivRequest {
ReadContainerJournal { ReadContainerJournal {
/// System container name (`h-<agent>` or a sibling service). /// System container name (`h-<agent>` or a sibling service).
container: String, container: String,
/// `-n <lines>`. /// journalctl knobs (see [`JournalQuery`]).
lines: u32, query: JournalQuery,
/// `-b` — restrict to the current boot.
#[serde(default)]
boot: bool,
/// `--output=<...>`.
#[serde(default)]
output: JournalOutput,
/// `-u <unit>`.
#[serde(default)]
unit: Option<String>,
/// `-p <priority>`.
#[serde(default)]
priority: Option<String>,
/// `--grep=<regex>`.
#[serde(default)]
grep: Option<String>,
/// `--since=<ts>`.
#[serde(default)]
since: Option<String>,
/// `--until=<ts>`.
#[serde(default)]
until: Option<String>,
}, },
// --- Config file writes --- // --- Config file writes ---