diff options
author | Lars Wirzenius <liw@liw.fi> | 2021-03-01 10:13:15 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2021-03-01 12:28:31 +0200 |
commit | a4e59f7f6363c2bc62b91298f41b5b645842385e (patch) | |
tree | 46659f0fa296fe2128d1a7bcb929008d034f51c1 /src/bin | |
parent | 05dbbc46d7b3fad9e7a1b85e10469022e07e0b40 (diff) | |
download | vmadm-a4e59f7f6363c2bc62b91298f41b5b645842385e.tar.gz |
feat: change how command line interface works
Easier to use now. --config before subcommand was annoying.
Diffstat (limited to 'src/bin')
-rw-r--r-- | src/bin/vmadm.rs | 262 |
1 files changed, 118 insertions, 144 deletions
diff --git a/src/bin/vmadm.rs b/src/bin/vmadm.rs index a2717ee..4cc2248 100644 --- a/src/bin/vmadm.rs +++ b/src/bin/vmadm.rs @@ -1,38 +1,67 @@ -use bytesize::GIB; -use log::{debug, info}; -use std::fs; -use std::net::TcpStream; +use anyhow::Context; +use directories_next::ProjectDirs; +use vmadm::cmd; +use vmadm::config::Configuration; +use vmadm::spec::Specification; +//use bytesize::GIB; +use log::debug; +//use std::fs; use std::path::{Path, PathBuf}; -use std::thread; -use std::time::Duration; +//use std::thread; use structopt::StructOpt; -use virt::connect::Connect; -use vmadm::cloudinit::CloudInitConfig; -use vmadm::image::VirtualMachineImage; -use vmadm::install::{virt_install, VirtInstallArgs}; -use vmadm::spec::Specification; +//use vmadm::cloudinit::CloudInitConfig; +//use vmadm::image::VirtualMachineImage; +//use vmadm::install::{virt_install, VirtInstallArgs}; +//use vmadm::spec::Specification; + +const QUALIFIER: &str = ""; +const ORG: &str = ""; +const APP: &str = "vmadm"; -const SSH_PORT: i32 = 22; +#[derive(StructOpt, Debug)] +struct Cli { + #[structopt(subcommand)] + cmd: Command, +} #[derive(StructOpt, Debug)] -enum Cli { +enum Command { New { + #[structopt(flatten)] + common: CommonOptions, + #[structopt(parse(from_os_str))] spec: PathBuf, }, - List, + + List { + #[structopt(flatten)] + common: CommonOptions, + }, + Delete { + #[structopt(flatten)] + common: CommonOptions, + #[structopt(parse(from_os_str))] spec: PathBuf, }, + CloudInit { + #[structopt(flatten)] + common: CommonOptions, + #[structopt(parse(from_os_str))] spec: PathBuf, #[structopt(parse(from_os_str))] dirname: PathBuf, }, + CloudInitIso { + #[structopt(flatten)] + common: CommonOptions, + #[structopt(parse(from_os_str))] spec: PathBuf, @@ -41,158 +70,103 @@ enum Cli { }, } -fn main() -> anyhow::Result<()> { - pretty_env_logger::init(); - match Cli::from_args() { - Cli::New { spec } => new(&spec)?, - Cli::List => list()?, - Cli::Delete { spec } => delete(&spec)?, - Cli::CloudInit { spec, dirname } => cloud_init(&spec, &dirname)?, - Cli::CloudInitIso { spec, iso } => cloud_init_iso(&spec, &iso)?, - } - Ok(()) +#[derive(StructOpt, Debug)] +struct CommonOptions { + #[structopt(short, long, parse(from_os_str))] + config: Option<PathBuf>, } -fn new(spec: &Path) -> anyhow::Result<()> { - info!("creating new VM"); - - debug!("reading specification from {}", spec.display()); - let spec = fs::read(spec)?; - let spec: Specification = serde_yaml::from_slice(&spec)?; - - info!("creating cloud-init config"); - let init = CloudInitConfig::from(&spec)?; - - info!( - "creating VM image {} from {}", - spec.image.display(), - spec.base.display() - ); - let image = VirtualMachineImage::new_from_base(&spec.base, &spec.image)?; - - info!("resizing image to {} GiB", spec.image_size_gib); - image.resize(spec.image_size_gib * GIB)?; - - info!("creating VM"); - let mut args = VirtInstallArgs::new(&spec.name, &image, &init); - args.set_memory(spec.memory_mib); - args.set_vcpus(spec.cpus); - virt_install(&args)?; +fn main() -> anyhow::Result<()> { + pretty_env_logger::init_custom_env("VMADM_LOG"); + let cli = Cli::from_args(); + debug!("{:#?}", cli); + + match cli.cmd { + Command::New { common, spec } => { + let spec = get_spec(&common, &spec)?; + cmd::new(&spec)?; + } - info!("waiting for {} to open its SSH port", spec.name); - wait_for_port(&spec.name, SSH_PORT)?; + Command::List { common } => { + let config = config(&common)?; + cmd::list(&config)?; + } - Ok(()) -} + Command::Delete { common, spec } => { + let spec = get_spec(&common, &spec)?; + cmd::delete(&spec)?; + } -fn wait_for_port(name: &str, port: i32) -> anyhow::Result<()> { - let addr = format!("{}:{}", name, port); - loop { - if TcpStream::connect(&addr).is_ok() { - return Ok(()); + Command::CloudInit { + common, + spec, + dirname, + } => { + let spec = get_spec(&common, &spec)?; + cmd::cloud_init(&spec, &dirname)?; + } + Command::CloudInitIso { common, spec, iso } => { + let spec = get_spec(&common, &spec)?; + cmd::cloud_init_iso(&spec, &iso)?; } } + Ok(()) } -fn list() -> anyhow::Result<()> { - let conn = Connect::open("qemu:///system")?; - let domains = conn.list_all_domains(0)?; - for domain in domains { - let name = domain.get_name()?; - let (state, _) = domain.get_state()?; - let state = state_name(state); - println!("{} {}", name, state); - } - - Ok(()) +fn get_spec(common: &CommonOptions, spec: &Path) -> anyhow::Result<Specification> { + let config = config(&common)?; + let spec = Specification::from_file(&config, &spec)?; + Ok(spec) } -fn state_name(state: virt::domain::DomainState) -> String { - let name = match state { - virt::domain::VIR_DOMAIN_NOSTATE => "none", - virt::domain::VIR_DOMAIN_RUNNING => "running", - virt::domain::VIR_DOMAIN_BLOCKED => "blocked", - virt::domain::VIR_DOMAIN_PAUSED => "paused", - virt::domain::VIR_DOMAIN_SHUTDOWN => "shutdown", - virt::domain::VIR_DOMAIN_SHUTOFF => "shutoff", - virt::domain::VIR_DOMAIN_CRASHED => "crashed", - virt::domain::VIR_DOMAIN_PMSUSPENDED => "power management suspended", - _ => "unknown", - }; - name.to_string() +fn config(common: &CommonOptions) -> anyhow::Result<Configuration> { + let filename = config_filename(common); + let config = Configuration::from_file(&filename) + .with_context(|| format!("reading configuration file {}", filename.display()))?; + Ok(config) } -fn delete(spec: &Path) -> anyhow::Result<()> { - info!("deleting virtual machine specified in {}", spec.display()); - - debug!("reading specification from {}", spec.display()); - let spec = fs::read(spec)?; - let spec: Specification = serde_yaml::from_slice(&spec)?; - - debug!("connecting to libvirtd"); - let conn = Connect::open("qemu:///system")?; - - debug!("listing all domains"); - let domains = conn.list_all_domains(0)?; - - for domain in domains { - debug!("considering {}", domain.get_name()?); - if domain.get_name()? == spec.name { - debug!("shutdown {}", spec.name); - domain.shutdown().ok(); - - let briefly = Duration::from_millis(1000); - loop { - thread::sleep(briefly); - match domain.is_active() { - Ok(true) => (), - Ok(false) => break, - Err(err) => { - debug!("is_active: {}", err); - } - } - debug!("{} is still running", spec.name); - } - - debug!("undefine {}", spec.name); - domain.undefine()?; - - debug!("removing image file {}", spec.image.display()); - std::fs::remove_file(&spec.image)?; +fn config_filename(common: &CommonOptions) -> PathBuf { + if let Some(ref filename) = common.config { + filename.to_path_buf() + } else { + if let Some(dirs) = ProjectDirs::from(QUALIFIER, ORG, APP) { + dirs.config_dir().join("config.yaml") + } else { + PathBuf::from("xxx") } } - Ok(()) } -fn cloud_init(spec: &Path, dirname: &Path) -> anyhow::Result<()> { - info!("generating cloud-init configuration"); +// fn cloud_init(spec: &Path, dirname: &Path) -> anyhow::Result<()> { +// info!("generating cloud-init configuration"); - debug!("reading specification from {}", spec.display()); - let spec = fs::read(spec)?; - let spec: Specification = serde_yaml::from_slice(&spec)?; - debug!("spec:\n{:#?}", spec); +// debug!("reading specification from {}", spec.display()); +// let spec = fs::read(spec)?; +// let spec: Specification = serde_yaml::from_slice(&spec)?; +// debug!("spec:\n{:#?}", spec); - info!("creating cloud-init config"); - let init = CloudInitConfig::from(&spec)?; +// info!("creating cloud-init config"); +// let init = CloudInitConfig::from(&spec)?; - debug!("creating directory {}", dirname.display()); - std::fs::create_dir_all(dirname)?; - init.create_dir(dirname)?; +// debug!("creating directory {}", dirname.display()); +// std::fs::create_dir_all(dirname)?; +// init.create_dir(dirname)?; - Ok(()) -} +// Ok(()) +// } -fn cloud_init_iso(spec: &Path, iso: &Path) -> anyhow::Result<()> { - info!("generating cloud-init ISO"); +// fn cloud_init_iso(spec: &Path, iso: &Path) -> anyhow::Result<()> { +// info!("generating cloud-init ISO"); - debug!("reading specification from {}", spec.display()); - let spec = fs::read(spec)?; - let spec: Specification = serde_yaml::from_slice(&spec)?; - debug!("spec:\n{:#?}", spec); +// debug!("reading specification from {}", spec.display()); +// let spec = fs::read(spec)?; +// let spec: Specification = serde_yaml::from_slice(&spec)?; +// debug!("spec:\n{:#?}", spec); - info!("creating cloud-init config"); - let init = CloudInitConfig::from(&spec)?; - init.create_iso(iso)?; +// info!("creating cloud-init config"); +// let init = CloudInitConfig::from(&spec)?; +// init.create_iso(iso)?; - Ok(()) -} +// Ok(()) +// } |