diff options
Diffstat (limited to 'cli')
-rw-r--r-- | cli/Cargo.toml | 24 | ||||
-rw-r--r-- | cli/src/lib.rs | 129 | ||||
-rw-r--r-- | cli/src/main.rs | 20 |
3 files changed, 173 insertions, 0 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..462d8ee --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "zone" +version = "0.1.0" +edition = "2021" +authors = [ + "Elijah Moore <egmoore2000@gmail.com>", + "Neil Kollack <nkollack@gmail.com>", + "Toby Vincent <tobyv13@gmail.com>", + "Anthony Schneider <tonyschneider3@gmail.com>", +] +description = "Manages containers using systemd-nspawn and ZFS" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +zone_core = { path = "../core" } +clap = { version = "3.0.5", features = ["derive", "env"] } +clap_complete = "3.0.2" +reqwest = { version = "0.11.8", features = ["blocking", "json"] } +serde = "1.0.132" +anyhow = "1.0.51" +log = "0.4.14" +simplelog = "0.11.2" +tabled = "0.4.2" diff --git a/cli/src/lib.rs b/cli/src/lib.rs new file mode 100644 index 0000000..a087b87 --- /dev/null +++ b/cli/src/lib.rs @@ -0,0 +1,129 @@ +use anyhow::{Context, Result}; +use clap::{ArgEnum, ErrorKind, IntoApp, Parser, Subcommand, ValueHint}; +use clap_complete::{generate, Shell}; +use log::LevelFilter; +use reqwest::Url; +use std::{ffi::OsString, io, process::Command}; +use tabled::{Style, Table}; +use zone_core::{Container, DEFAULT_ENDPOINT}; + +#[derive(Debug, Parser)] +#[clap(about, version)] +pub struct Cli { + /// Enable debugging mode + #[clap(short, long)] + pub debug: bool, + + /// Set the level of verbosity (-v, -vv, -vvv, etc.) + #[clap(short, long, parse(from_occurrences))] + pub verbose: i8, + + /// Disable all output + #[clap(short, long, conflicts_with = "verbose")] + pub quiet: bool, + + /// Endpoint of a zone daemon to use + #[clap( + long, + global = true, + env = "ZONE_ENDPOINT", + value_name = "ZONE_ENDPOINT", + default_value = DEFAULT_ENDPOINT, + value_hint = ValueHint::Url, + )] + endpoint: String, + + #[clap(subcommand)] + pub command: Commands, +} + +#[derive(Debug, Subcommand)] +pub enum Commands { + /// Generates shell completion scripts + Completion { + /// The shell to generate completion scripts for + #[clap(default_value_t = Shell::Bash, arg_enum)] + shell: Shell, + }, + + /// List existing containers + List { + /// Filter the list of containers + filter: Option<String>, + }, + + #[clap(external_subcommand)] + External(Vec<OsString>), +} + +impl Cli { + pub fn run(self) -> Result<Option<String>> { + match self.command { + Commands::Completion { shell } => self.completion(shell), + Commands::List { ref filter } => self.list(filter), + Commands::External(args) => Cli::external(args), + } + } + + pub fn get_log_level(&self) -> LevelFilter { + match 2 + self.verbose + self.debug as i8 { + _ if self.quiet => LevelFilter::Off, + 0 => LevelFilter::Error, + 1 => LevelFilter::Warn, + 2 => LevelFilter::Info, + 3 => LevelFilter::Debug, + _ => LevelFilter::Trace, + } + } + + fn external(args: Vec<OsString>) -> Result<Option<String>> { + Command::new(format!("zone-{:?}", &args[0])) + .args(&args[1..]) + .spawn() + .unwrap_or_else(|_| { + let mut app = Cli::into_app(); + app.error( + ErrorKind::UnrecognizedSubcommand, + format!("Unrecognized subcommand '{:?}'", &args[0]), + ) + .exit() + }); + Ok(None) + } + + fn completion(&self, gen: Shell) -> Result<Option<String>> { + eprintln!("Generating completion file for {:?}...", gen); + + let mut app = Cli::into_app(); + let bin_name = app.get_name().to_string(); + let buf = &mut io::stdout(); + generate(gen, &mut app, bin_name, buf); + + Ok(None) + } + + fn list(&self, query: &Option<String>) -> Result<Option<String>> { + let mut url = Url::parse(&self.endpoint).context("Invalid zone endpoint")?; + url.set_path("containers/list"); + url.set_query(query.as_deref()); + + let containers = reqwest::blocking::get(url) + .context("Daemon is not running")? + .json::<Vec<Container>>() + .context("Failed to parse json")?; + + Ok(Some( + Table::new(containers).with(Style::NO_BORDER).to_string(), + )) + } +} + +#[cfg(test)] +mod tests { + #[test] + fn verify_app() { + use super::Cli; + use clap::IntoApp; + Cli::into_app().debug_assert() + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..054633a --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,20 @@ +use clap::Parser; +use log::error; +use zone::Cli; + +fn main() { + let cli = Cli::parse(); + + simplelog::TermLogger::init( + cli.get_log_level(), + simplelog::Config::default(), + simplelog::TerminalMode::Mixed, + simplelog::ColorChoice::Auto, + ) + .unwrap(); + + if let Err(err) = Cli::parse().run() { + error!("{}", err); + std::process::exit(1); + } +} |