diff --git a/rust/agama-cli/src/logs.rs b/rust/agama-cli/src/logs.rs index 18f7e246cb..73e1b3086e 100644 --- a/rust/agama-cli/src/logs.rs +++ b/rust/agama-cli/src/logs.rs @@ -19,19 +19,28 @@ pub enum LogsCommands { #[clap(long, short = 'v')] /// Verbose output verbose: bool, + #[clap(long, short = 'd')] + /// Path to destination directory. Optionally with the archive file name at the end. + /// An extension will be appended automatically depending on used compression. + destination: Option, }, /// List logs which will be collected List, } -// main entry point called from agama CLI main loop +/// Main entry point called from agama CLI main loop pub async fn run(subcommand: LogsCommands) -> anyhow::Result<()> { match subcommand { - LogsCommands::Store { verbose } => { + LogsCommands::Store { + verbose, + destination, + } => { // feed internal options structure by what was received from user // for now we always use / add defaults if any + let destination = parse_destination(destination)?; let options = LogOptions { verbose, + destination, ..Default::default() }; @@ -45,6 +54,38 @@ pub async fn run(subcommand: LogsCommands) -> anyhow::Result<()> { } } +/// Whatewer passed in destination formed into an absolute path with archive name +/// +/// # Arguments: +/// * destination +/// - if None then a default is returned +/// - if a path to a directory then a default file name for the archive will be appended to the +/// path +/// - if path with a file name then it is used as is for resulting archive, just extension will +/// be appended later on (depends on used compression) +fn parse_destination(destination: Option) -> Result { + let err = io::Error::new(io::ErrorKind::InvalidInput, "Invalid destination path"); + let mut buffer = destination.unwrap_or(PathBuf::from(DEFAULT_RESULT)); + let path = buffer.as_path(); + + // existing directory -> append an archive name + if path.is_dir() { + buffer.push("agama-logs"); + // a path with file name + // sadly, is_some_and is unstable + } else if path.parent().is_some() { + // validate if parent directory realy exists + if !path.parent().unwrap().is_dir() { + return Err(err); + } + } + + // buffer is or / here + // and we know that directory tree which leads to the is valid. + // creation can still fail later on. + Ok(buffer) +} + const DEFAULT_COMMANDS: [(&str, &str); 3] = [ // (, ) ("journalctl -u agama", "agama"), @@ -77,7 +118,7 @@ const DEFAULT_RESULT: &str = "/tmp/agama_logs"; const DEFAULT_COMPRESSION: (&str, &str) = ("bzip2", "tar.bz2"); const DEFAULT_TMP_DIR: &str = "agama-logs"; -// A wrapper around println which shows (or not) the text depending on the boolean variable +/// A wrapper around println which shows (or not) the text depending on the boolean variable fn showln(show: bool, text: &str) { if !show { return; @@ -86,7 +127,7 @@ fn showln(show: bool, text: &str) { println!("{}", text); } -// A wrapper around println which shows (or not) the text depending on the boolean variable +/// A wrapper around println which shows (or not) the text depending on the boolean variable fn show(show: bool, text: &str) { if !show { return; @@ -95,12 +136,13 @@ fn show(show: bool, text: &str) { print!("{}", text); } -// Configurable parameters of the "agama logs" which can be -// set by user when calling a (sub)command +/// Configurable parameters of the "agama logs" which can be +/// set by user when calling a (sub)command struct LogOptions { paths: Vec, commands: Vec<(String, String)>, verbose: bool, + destination: PathBuf, } impl Default for LogOptions { @@ -112,11 +154,12 @@ impl Default for LogOptions { .map(|(cmd, name)| (cmd.to_string(), name.to_string())) .collect(), verbose: false, + destination: PathBuf::from(DEFAULT_RESULT), } } } -// Struct for log represented by a file +/// Struct for log represented by a file struct LogPath { // log source src_path: String, @@ -134,7 +177,7 @@ impl LogPath { } } -// Struct for log created on demand by a command +/// Struct for log created on demand by a command struct LogCmd { // command which stdout / stderr is logged cmd: String, @@ -234,8 +277,8 @@ impl LogItem for LogCmd { } } -// Collect existing / requested paths which should already exist in the system. -// Turns them into list of log sources +/// Collect existing / requested paths which should already exist in the system. +/// Turns them into list of log sources fn paths_to_log_sources(paths: &Vec, tmp_dir: &TempDir) -> Vec> { let mut log_sources: Vec> = Vec::new(); @@ -249,7 +292,7 @@ fn paths_to_log_sources(paths: &Vec, tmp_dir: &TempDir) -> Vec, tmp_dir: &TempDir, @@ -267,7 +310,7 @@ fn cmds_to_log_sources( log_sources } -// Compress given directory into a tar archive +/// Compress given directory into a tar archive fn compress_logs(tmp_dir: &TempDir, result: &String) -> io::Result<()> { let compression = DEFAULT_COMPRESSION.0; let tmp_path = tmp_dir @@ -305,8 +348,8 @@ fn compress_logs(tmp_dir: &TempDir, result: &String) -> io::Result<()> { } } -// Sets the archive owner to root:root. Also sets the file permissions to read/write for the -// owner only. +/// Sets the archive owner to root:root. Also sets the file permissions to read/write for the +/// owner only. fn set_archive_permissions(archive: &String) -> io::Result<()> { let attr = fs::metadata(archive)?; let mut permissions = attr.permissions(); @@ -324,7 +367,7 @@ fn set_archive_permissions(archive: &String) -> io::Result<()> { Ok(()) } -// Handler for the "agama logs store" subcommand +/// Handler for the "agama logs store" subcommand fn store(options: LogOptions) -> Result<(), io::Error> { if !Uid::effective().is_root() { panic!("No Root, no logs. Sorry."); @@ -334,7 +377,12 @@ fn store(options: LogOptions) -> Result<(), io::Error> { let commands = options.commands; let paths = options.paths; let verbose = options.verbose; - let result = format!("{}.{}", DEFAULT_RESULT, DEFAULT_COMPRESSION.1); + let opt_dest = options.destination.into_os_string(); + let destination = opt_dest.to_str().ok_or(io::Error::new( + io::ErrorKind::InvalidInput, + "Malformed destination path", + ))?; + let result = format!("{}.{}", destination, DEFAULT_COMPRESSION.1); showln(verbose, "Collecting Agama logs:"); @@ -374,7 +422,7 @@ fn store(options: LogOptions) -> Result<(), io::Error> { compress_logs(&tmp_dir, &result) } -// Handler for the "agama logs list" subcommand +/// Handler for the "agama logs list" subcommand fn list(options: LogOptions) { for list in [ ("Log paths: ", options.paths), diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes index 44ccbf8642..75e95ad9f3 100644 --- a/rust/package/agama-cli.changes +++ b/rust/package/agama-cli.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Wed Nov 15 11:27:10 UTC 2023 - Michal Filka + +- Improved "agama logs store" (gh#openSUSE/agama#823) + - added an option which allows to define the archive destination + ------------------------------------------------------------------- Tue Nov 14 15:44:15 UTC 2023 - Jorik Cronenberg