mirror of
https://github.com/vhaudiquet/ecrou.git
synced 2026-11-12 22:22:44 +00:00
refactor(vm): the engine owns a LogLevel, not UI booleans
RunOptions carried quiet/verbose, which are flags, not engine state. They are now a LogLevel enum (Quiet/Normal/Verbose) mapped onto libkrun's filter; the CLI maps its flags onto it. The forked child's last-gasp boot diagnostics also stop going straight to stderr: they land in the console file the parent streams, with stderr kept for the case where the console was never configured. lib.rs now states the crate contract: no UI in the library, progress through the sink, logging through the facade, typed errors out.
This commit is contained in:
1 parent
a27879dbe7
commit
a91c1b6b51
4 files changed
+60
-23
No files matched your search
@@ -14,9 +14,17 @@ keep whatever crossed the workspace share, throw the machine away.
|
|||||||
The project is library-first like ecr: `crates/ecrou` is the library
|
The project is library-first like ecr: `crates/ecrou` is the library
|
||||||
(package `ecrou`), `crates/ecrou-cli` is the thin front-end (binary
|
(package `ecrou`), `crates/ecrou-cli` is the thin front-end (binary
|
||||||
`ecrou`) that parses flags, maps them onto the library API, and
|
`ecrou`) that parses flags, maps them onto the library API, and
|
||||||
propagates exit codes. The library never writes to the terminal:
|
propagates exit codes. The split is enforced by the dependency edges:
|
||||||
diagnostics go through the `log` facade, long operations report through
|
the library links no clap and no rendering crate. It never writes to
|
||||||
the `Progress` sink, all output is the CLI's.
|
the terminal: diagnostics go through the `log` facade, filtered by
|
||||||
|
`RunOptions::log_level` (an engine-owned enum, not UI booleans), long
|
||||||
|
operations report through the `Progress` sink, all output is the
|
||||||
|
CLI's. In-process failures come back as typed errors; the one
|
||||||
|
exception is the forked machine child's last-gasp diagnostics, which
|
||||||
|
land in the console file the parent streams. The CLI owns clap, the
|
||||||
|
indicatif rendering of progress events, the log sink, and exit codes.
|
||||||
|
A consumer embeds the library exactly the way the CLI does: a
|
||||||
|
`RootfsCache` to prepare a rootfs, then `run` to boot it.
|
||||||
|
|
||||||
The first consumer is rhema's sandbox seam, which shells out to the
|
The first consumer is rhema's sandbox seam, which shells out to the
|
||||||
binary the way it shells out to `docker` and `lxc` today. The library
|
binary the way it shells out to `docker` and `lxc` today. The library
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ mod progress;
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use ecrou::vm::RunOptions;
|
use ecrou::vm::RunOptions;
|
||||||
use ecrou::{NoProgress, Progress};
|
use ecrou::{LogLevel, NoProgress, Progress};
|
||||||
|
|
||||||
use cli::{parse_env, parse_share, Cli, Command, RunArgs};
|
use cli::{parse_env, parse_share, Cli, Command, RunArgs};
|
||||||
use progress::TermProgress;
|
use progress::TermProgress;
|
||||||
@@ -28,8 +28,13 @@ fn cmd_run(args: &RunArgs, cli_verbose: bool) -> std::process::ExitCode {
|
|||||||
command: args.command.clone(),
|
command: args.command.clone(),
|
||||||
memory: args.memory.clone(),
|
memory: args.memory.clone(),
|
||||||
workdir: args.workdir.clone(),
|
workdir: args.workdir.clone(),
|
||||||
quiet: args.quiet,
|
log_level: if args.quiet {
|
||||||
verbose: cli_verbose,
|
LogLevel::Quiet
|
||||||
|
} else if cli_verbose {
|
||||||
|
LogLevel::Verbose
|
||||||
|
} else {
|
||||||
|
LogLevel::Normal
|
||||||
|
},
|
||||||
no_cache: args.no_cache,
|
no_cache: args.no_cache,
|
||||||
..RunOptions::default()
|
..RunOptions::default()
|
||||||
};
|
};
|
||||||
|
|||||||
+11
-1
@@ -20,6 +20,16 @@
|
|||||||
//! ([`Error::KvmUnavailable`], [`Error::ArchNotAvailable`]) versus
|
//! ([`Error::KvmUnavailable`], [`Error::ArchNotAvailable`]) versus
|
||||||
//! ordinary failures ([`Error::Other`], which preserves the full context
|
//! ordinary failures ([`Error::Other`], which preserves the full context
|
||||||
//! chain).
|
//! chain).
|
||||||
|
//!
|
||||||
|
//! ## Crate split
|
||||||
|
//!
|
||||||
|
//! `ecrou` is the library and carries no UI: progress flows through
|
||||||
|
//! the [`Progress`] sink, engine logging through the `log` facade
|
||||||
|
//! filtered by [`LogLevel`], and results come back as typed errors.
|
||||||
|
//! `ecrou-cli` owns everything a human sees: flags, the indicatif
|
||||||
|
//! rendering of progress events, the log sink, exit codes. A consumer
|
||||||
|
//! embeds the library exactly the way the CLI does - a [`RootfsCache`]
|
||||||
|
//! to prepare a rootfs, then [`run`] to boot it.
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod distro;
|
pub mod distro;
|
||||||
@@ -36,4 +46,4 @@ pub mod vm;
|
|||||||
pub use error::{Error, Result};
|
pub use error::{Error, Result};
|
||||||
pub use progress::{NoProgress, Progress, Unit};
|
pub use progress::{NoProgress, Progress, Unit};
|
||||||
pub use rootfs::{PrepareRequest, PreparedRootfs, RootfsCache};
|
pub use rootfs::{PrepareRequest, PreparedRootfs, RootfsCache};
|
||||||
pub use vm::{run, RunOptions, Share};
|
pub use vm::{run, LogLevel, RunOptions, Share};
|
||||||
+30
-16
@@ -42,12 +42,25 @@ pub struct RunOptions {
|
|||||||
pub workdir: String,
|
pub workdir: String,
|
||||||
pub env: Vec<(String, String)>,
|
pub env: Vec<(String, String)>,
|
||||||
pub hostname: String,
|
pub hostname: String,
|
||||||
pub quiet: bool,
|
/// How much of the engine's own logging reaches stderr
|
||||||
/// More library diagnostics on stderr
|
pub log_level: LogLevel,
|
||||||
pub verbose: bool,
|
|
||||||
pub no_cache: bool,
|
pub no_cache: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// How much of the engine's own logging reaches stderr. The engine
|
||||||
|
/// logs through the `log` facade, so the consumer's sink decides where
|
||||||
|
/// lines land; this only picks the filter.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||||
|
pub enum LogLevel {
|
||||||
|
/// Nothing from the engine; the consumer owns all output.
|
||||||
|
Quiet,
|
||||||
|
/// Warnings and errors. The default.
|
||||||
|
#[default]
|
||||||
|
Normal,
|
||||||
|
/// Engine info as well: lifecycle lines, boot chatter.
|
||||||
|
Verbose,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for RunOptions {
|
impl Default for RunOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
RunOptions {
|
RunOptions {
|
||||||
@@ -60,18 +73,12 @@ impl Default for RunOptions {
|
|||||||
workdir: "/".to_string(),
|
workdir: "/".to_string(),
|
||||||
env: Vec::new(),
|
env: Vec::new(),
|
||||||
hostname: String::new(),
|
hostname: String::new(),
|
||||||
quiet: false,
|
log_level: LogLevel::Normal,
|
||||||
verbose: false,
|
|
||||||
no_cache: false,
|
no_cache: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// libkrun log levels (0 off, 1 error, 2 warn, 3 info, 4 debug).
|
|
||||||
const LOG_LEVEL_WARN: u32 = 2;
|
|
||||||
const LOG_LEVEL_INFO: u32 = 3;
|
|
||||||
const LOG_LEVEL_QUIET: u32 = 0;
|
|
||||||
|
|
||||||
/// Run one machine to completion and return the payload's exit code.
|
/// Run one machine to completion and return the payload's exit code.
|
||||||
pub fn run(options: &RunOptions, progress: &dyn Progress) -> Result<i32> {
|
pub fn run(options: &RunOptions, progress: &dyn Progress) -> Result<i32> {
|
||||||
if options.image.is_empty() {
|
if options.image.is_empty() {
|
||||||
@@ -122,12 +129,10 @@ pub fn run(options: &RunOptions, progress: &dyn Progress) -> Result<i32> {
|
|||||||
);
|
);
|
||||||
let _ = std::fs::remove_file(&exit_file_host);
|
let _ = std::fs::remove_file(&exit_file_host);
|
||||||
|
|
||||||
krun.set_log_level(if options.quiet {
|
krun.set_log_level(match options.log_level {
|
||||||
LOG_LEVEL_QUIET
|
LogLevel::Quiet => 0, // off
|
||||||
} else if options.verbose {
|
LogLevel::Normal => 2, // warn
|
||||||
LOG_LEVEL_INFO
|
LogLevel::Verbose => 3, // info
|
||||||
} else {
|
|
||||||
LOG_LEVEL_WARN
|
|
||||||
})?;
|
})?;
|
||||||
let ctx = krun.create_ctx()?;
|
let ctx = krun.create_ctx()?;
|
||||||
krun.set_vm_config(ctx, vcpus, ram_mib)?;
|
krun.set_vm_config(ctx, vcpus, ram_mib)?;
|
||||||
@@ -165,6 +170,15 @@ pub fn run(options: &RunOptions, progress: &dyn Progress) -> Result<i32> {
|
|||||||
// Safety: after fork the child runs no threads of its own; the
|
// Safety: after fork the child runs no threads of its own; the
|
||||||
// prctl/exit pair has no unwinding preconditions.
|
// prctl/exit pair has no unwinding preconditions.
|
||||||
let bail = |msg: String, code: i32| -> ! {
|
let bail = |msg: String, code: i32| -> ! {
|
||||||
|
// The console file is the channel the parent streams: a
|
||||||
|
// boot failure belongs in it, next to whatever the guest
|
||||||
|
// printed. stderr carries the last-gasp case where the
|
||||||
|
// console was never configured; the child is process
|
||||||
|
// teardown, not library logging.
|
||||||
|
use std::io::Write as _;
|
||||||
|
if let Ok(mut f) = std::fs::OpenOptions::new().append(true).open(&console_path) {
|
||||||
|
let _ = f.write_all(format!("ecrou: {msg}\n").as_bytes());
|
||||||
|
}
|
||||||
eprintln!("Error: {msg}");
|
eprintln!("Error: {msg}");
|
||||||
unsafe { libc::_exit(code) }
|
unsafe { libc::_exit(code) }
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user