From a806734a1b29d8a6199507785c74af6650f087a0 Mon Sep 17 00:00:00 2001 From: sandorex Date: Thu, 3 Oct 2024 17:54:53 +0200 Subject: [PATCH] feat: add config options subcommand to show all config properties with help --- Cargo.lock | 6 + src/cli/cli_config.rs | 3 + src/commands/cmd_config.rs | 2 + src/commands/cmd_config/cmd_options_config.rs | 10 ++ src/config.rs | 156 +++++++++--------- src/main.rs | 73 ++++---- 6 files changed, 143 insertions(+), 107 deletions(-) create mode 100644 src/commands/cmd_config/cmd_options_config.rs diff --git a/Cargo.lock b/Cargo.lock index 6692a13..6648a00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,7 @@ dependencies = [ "bson", "chrono", "clap", + "code-docs-rs", "ctrlc", "serde", "shellexpand", @@ -247,6 +248,11 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "code-docs-rs" +version = "0.1.0" +source = "git+https://github.com/sandorex/code-docs-rs.git?rev=94b08b23c623658f5bf94979167fccab11ff7b5c#94b08b23c623658f5bf94979167fccab11ff7b5c" + [[package]] name = "colorchoice" version = "1.0.2" diff --git a/src/cli/cli_config.rs b/src/cli/cli_config.rs index ba344f0..2f488ef 100644 --- a/src/cli/cli_config.rs +++ b/src/cli/cli_config.rs @@ -19,5 +19,8 @@ pub enum ConfigCommands { /// Inspect a config, can be used to check if syntax is correct Inspect(CmdConfigInspectArgs), + + /// Show all options useable in a config + Options, } diff --git a/src/commands/cmd_config.rs b/src/commands/cmd_config.rs index c9e23e7..49a1617 100644 --- a/src/commands/cmd_config.rs +++ b/src/commands/cmd_config.rs @@ -1,5 +1,7 @@ mod cmd_extract_config; mod cmd_inspect_config; +mod cmd_options_config; pub use cmd_extract_config::extract_config; pub use cmd_inspect_config::inspect_config; +pub use cmd_options_config::show_config_options; diff --git a/src/commands/cmd_config/cmd_options_config.rs b/src/commands/cmd_config/cmd_options_config.rs new file mode 100644 index 0000000..1414362 --- /dev/null +++ b/src/commands/cmd_config/cmd_options_config.rs @@ -0,0 +1,10 @@ +use code_docs::DocumentedStruct; +use crate::config::Config; + +pub fn show_config_options() { + let docstring = Config::commented_fields().unwrap(); + + println!("Configuration options for each config:\n"); + println!("{}", docstring); +} + diff --git a/src/config.rs b/src/config.rs index 82cc4d7..a3e0885 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ -/// Contains everything related to container configuration +//! Contains everything related to container configuration -use serde::Deserialize; +use code_docs::{code_docs_struct, DocumentedStruct}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::error::Error; use std::fmt; @@ -56,6 +57,8 @@ const fn default_version() -> u64 { 1 } impl ConfigFile { /// Loads config from str, path is just for error message and can be anything pub fn load_from_str(text: &str) -> Result { + // TODO load a table first and get the version then try parsing appropriate struct + let obj = toml::from_str::(text) .map_err(|err| ConfigError::Generic(Box::new(err)) )?; @@ -74,81 +77,80 @@ impl ConfigFile { } } -/// Single configuration for a container, contains default settings and optional settings per -/// engine that get applied over the default settings -#[derive(Debug, Clone, Default, PartialEq, Deserialize)] -#[serde(deny_unknown_fields)] -pub struct Config { - // TODO figure out rules for local containers that need to be built - /// Name of the configuration - pub name: String, - - /// Image used for the container - pub image: String, - - /// Optional name to set for the container, otherwise randomly generated - pub container_name: Option, - - /// Dotfiles directory to use as /etc/skel - /// - /// Environ vars are expanded - pub skel: Option, - - /// Should the container have access to internet - #[serde(default)] - pub network: bool, - - /// Try to pass audio into the the container, security impact is unknown - #[serde(default)] - pub audio: bool, - - /// Passes wayland compositor through, pokes holes in sandbox, allows r/w access to clipboard - #[serde(default)] - pub wayland: bool, - - /// Pass through ssh-agent socket - #[serde(default)] - pub ssh_agent: bool, - - /// Pass through session dbus socket - #[serde(default)] - pub session_bus: bool, - - /// Run command on init (ran using `/bin/sh`) - #[serde(default)] - pub on_init: Vec, - - /// Copies files to container as init scripts (places them in `/init.d/`) - #[serde(default)] - pub on_init_file: Vec, - - /// Paths to persist between container invocation by mounting a volume - #[serde(default)] - pub persist: Vec<(String, String)>, - - /// Environment variables to set - /// - /// Environ vars are expanded - #[serde(default)] - pub env: HashMap, - - /// Args passed to the engine - /// - /// Environ vars are expanded - #[serde(default)] - pub engine_args: Vec, - - /// Args passed to the engine, if its podman - /// - /// Environ vars are expanded - #[serde(default)] - pub engine_args_podman: Vec, - - /// Args passed to the engine, if its docker - /// - /// Environ vars are expanded - #[serde(default)] - pub engine_args_docker: Vec, +// save all the fields and docs so they can be printed as always up-to-date documentation +code_docs_struct! { + /// Single configuration for a container, contains default settings and optional settings per + /// engine that get applied over the default settings + #[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] + #[serde(deny_unknown_fields)] + pub struct Config { + // TODO redo these comments so they are easy to understand even for non-rust programmers + /// Name of the configuration + pub name: String, + + /// Image used for the container + pub image: String, + + /// Optional name to set for the container, otherwise randomly generated + pub container_name: Option, + + /// Dotfiles directory to use as /etc/skel + /// + /// Environ vars are expanded + pub skel: Option, + + /// Should the container have access to internet + #[serde(default)] + pub network: bool, + + /// Try to pass audio into the the container, security impact is unknown + #[serde(default)] + pub audio: bool, + + /// Passes wayland compositor through, pokes holes in sandbox, allows r/w access to clipboard + #[serde(default)] + pub wayland: bool, + + /// Pass through ssh-agent socket + #[serde(default)] + pub ssh_agent: bool, + + /// Pass through session dbus socket + #[serde(default)] + pub session_bus: bool, + + /// Run command on init (ran using `/bin/sh`) + #[serde(default)] + pub on_init: Vec, + + /// Copies files to container as init scripts (places them in `/init.d/`) + #[serde(default)] + pub on_init_file: Vec, + + /// Environment variables to set + /// + /// Environ vars are expanded + #[serde(default)] + pub env: HashMap, + + /// Args passed to the engine + /// + /// Environ vars are expanded + #[serde(default)] + pub engine_args: Vec, + + /// Args passed to the engine, if its podman + /// + /// Environ vars are expanded + #[serde(default)] + pub engine_args_podman: Vec, + + /// Args passed to the engine, if its docker + /// + /// Environ vars are expanded + #[serde(default)] + pub engine_args_docker: Vec, + } } impl Config { diff --git a/src/main.rs b/src/main.rs index 83a5c6b..be6cd30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,53 +27,66 @@ macro_rules! ENV_VAR_PREFIX { pub use util::ExitResult; -fn main() -> ExitCode { - let args = cli::Cli::parse(); - - // init does not need engine, just get it from environment if needed - if let CliCommands::Init(x) = args.cmd { - if !util::is_in_container() { - eprintln!("Running init outside a container is dangerous, qutting.."); - return ExitCode::FAILURE; - } - - return commands::container_init(x).to_exitcode(); - } - +fn get_engine(args: &cli::Cli) -> Result { // find and detect engine - let engine: Engine = if let Some(chosen) = args.engine { + let engine: Engine = if let Some(chosen) = &args.engine { // test if engine exists in PATH or as a literal path - if !(util::executable_exists(&chosen) || std::path::Path::new(&chosen).exists()) { - eprintln!("Engine '{}' not found in PATH or filesystem", &chosen); - return ExitCode::FAILURE; + if !(util::executable_exists(chosen) || std::path::Path::new(chosen).exists()) { + eprintln!("Engine '{}' not found in PATH or filesystem", chosen); + return Err(ExitCode::FAILURE); } - Engine::detect(&chosen).expect("Failed to detect engine kind") + Engine::detect(chosen).expect("Failed to detect engine kind") } else if let Some(found) = Engine::find_available_engine() { found } else { eprintln!("No compatible container engine found in PATH"); - return ExitCode::FAILURE; + return Err(ExitCode::FAILURE); }; // prevent running with docker for now if let util::EngineKind::Docker = engine.kind { eprintln!("Docker is not supported at the moment"); - return ExitCode::FAILURE + return Err(ExitCode::FAILURE); + } + + Ok(engine) +} + +fn main() -> ExitCode { + let args = cli::Cli::parse(); + + // init does not need engine, just get it from environment if needed + if let CliCommands::Init(x) = args.cmd { + if !util::is_in_container() { + eprintln!("Running init outside a container is dangerous, qutting.."); + return ExitCode::FAILURE; + } + + return commands::container_init(x).to_exitcode(); + } + + match command(&args) { + Ok(_) => ExitCode::SUCCESS, + Err(x) => x, } +} - match args.cmd { - CliCommands::Start(x) => commands::start_container(engine, args.dry_run, x), - CliCommands::Shell(x) => commands::open_shell(engine, args.dry_run, x), - CliCommands::Exec(x) => commands::container_exec(engine, args.dry_run, x), - CliCommands::Exists(x) => commands::container_exists(engine, x), +fn command(args: &cli::Cli) -> Result<(), ExitCode> { + // TODO there is a bit too much cloning here, not that it matters much + match &args.cmd { + CliCommands::Start(x) => commands::start_container(get_engine(&args)?, args.dry_run, x.clone()), + CliCommands::Shell(x) => commands::open_shell(get_engine(&args)?, args.dry_run, x.clone()), + CliCommands::Exec(x) => commands::container_exec(get_engine(&args)?, args.dry_run, x.clone()), + CliCommands::Exists(x) => commands::container_exists(get_engine(&args)?, x.clone()), CliCommands::Config(subcmd) => match subcmd { - ConfigCommands::Extract(x) => commands::extract_config(engine, args.dry_run, &x), + ConfigCommands::Extract(x) => commands::extract_config(get_engine(&args)?, args.dry_run, &x), ConfigCommands::Inspect(x) => commands::inspect_config(&x), + ConfigCommands::Options => { commands::show_config_options(); Ok(()) }, }, - CliCommands::List => commands::print_containers(engine, args.dry_run), - CliCommands::Logs(x) => commands::print_logs(engine, x), - CliCommands::Kill(x) => commands::kill_container(engine, args.dry_run, x), + CliCommands::List => commands::print_containers(get_engine(&args)?, args.dry_run), + CliCommands::Logs(x) => commands::print_logs(get_engine(&args)?, x.clone()), + CliCommands::Kill(x) => commands::kill_container(get_engine(&args)?, args.dry_run, x.clone()), CliCommands::Init(_) => unreachable!(), // this is handled before - }.to_exitcode() + }.map_err(|x| ExitCode::from(x)) }