Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Destination option for agama logs store #823

Merged
merged 18 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 65 additions & 17 deletions rust/agama-cli/src/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<PathBuf>,
},
/// 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()
};

Expand All @@ -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<PathBuf>) -> Result<PathBuf, io::Error> {
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 <directory> or <directory>/<file_name> here
// and we know that directory tree which leads to the <file_name> is valid.
// <file_name> creation can still fail later on.
Ok(buffer)
}

const DEFAULT_COMMANDS: [(&str, &str); 3] = [
// (<command to be executed>, <file name used for storing result of the command>)
("journalctl -u agama", "agama"),
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<String>,
commands: Vec<(String, String)>,
verbose: bool,
destination: PathBuf,
}

impl Default for LogOptions {
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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<String>, tmp_dir: &TempDir) -> Vec<Box<dyn LogItem>> {
let mut log_sources: Vec<Box<dyn LogItem>> = Vec::new();

Expand All @@ -249,7 +292,7 @@ fn paths_to_log_sources(paths: &Vec<String>, tmp_dir: &TempDir) -> Vec<Box<dyn L
log_sources
}

// Some info can be collected via particular commands only, turn it into log sources
/// Some info can be collected via particular commands only, turn it into log sources
fn cmds_to_log_sources(
commands: &Vec<(String, String)>,
tmp_dir: &TempDir,
Expand All @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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.");
Expand All @@ -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:");

Expand Down Expand Up @@ -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),
Expand Down
6 changes: 6 additions & 0 deletions rust/package/agama-cli.changes
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
-------------------------------------------------------------------
Wed Nov 15 11:27:10 UTC 2023 - Michal Filka <mfilka@suse.com>

- 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 <jorik.cronenberg@suse.com>

Expand Down