summaryrefslogtreecommitdiff
path: root/src/bin
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-03-01 10:13:15 +0200
committerLars Wirzenius <liw@liw.fi>2021-03-01 12:28:31 +0200
commita4e59f7f6363c2bc62b91298f41b5b645842385e (patch)
tree46659f0fa296fe2128d1a7bcb929008d034f51c1 /src/bin
parent05dbbc46d7b3fad9e7a1b85e10469022e07e0b40 (diff)
downloadvmadm-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.rs262
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(())
+// }