use std::{convert::TryFrom, error::Error, path::PathBuf};
use clap::{App, Arg, ArgGroup, ArgMatches, SubCommand};
use serde_json::{json, Map, Value};
use libstratis::{
engine::{EncryptionInfo, KeyDescription, PoolUuid, UnlockMethod, CLEVIS_TANG_TRUST_URL},
jsonrpc::client::{filesystem, key, pool, report},
stratis::{StratisError, VERSION},
};
fn parse_args() -> App<'static, 'static> {
App::new("stratis-min")
.version(VERSION)
.arg(
Arg::with_name("debug")
.long("--debug")
.takes_value(false)
.required(false),
)
.subcommands(vec![
SubCommand::with_name("key").subcommands(vec![
SubCommand::with_name("set")
.group(
ArgGroup::with_name("key_method")
.arg("capture_key")
.arg("keyfile_path")
.required(true),
)
.arg(
Arg::with_name("capture_key")
.long("--capture-key")
.takes_value(false),
)
.arg(
Arg::with_name("keyfile_path")
.long("--keyfile-path")
.takes_value(true),
)
.arg(Arg::with_name("key_desc").required(true)),
SubCommand::with_name("list"),
SubCommand::with_name("unset").arg(Arg::with_name("key_desc").required(true)),
]),
SubCommand::with_name("pool").subcommands(vec![
SubCommand::with_name("unlock")
.arg(Arg::with_name("unlock_method").required(true))
.arg(Arg::with_name("pool_uuid").required(false))
.arg(
Arg::with_name("prompt")
.long("--prompt")
.takes_value(false)
.requires("pool_uuid"),
),
SubCommand::with_name("create")
.arg(Arg::with_name("name").required(true))
.arg(Arg::with_name("blockdevs").multiple(true).required(true))
.arg(
Arg::with_name("key_desc")
.long("--key-desc")
.takes_value(true),
)
.arg(
Arg::with_name("clevis")
.long("--clevis")
.takes_value(true)
.possible_values(&["nbde", "tang", "tpm2"])
.requires_if("nbde", "tang_args")
.requires_if("tang", "tang_args"),
)
.arg(
Arg::with_name("tang_url")
.long("--tang-url")
.takes_value(true)
.required_if("clevis", "nbde")
.required_if("clevis", "tang"),
)
.arg(
Arg::with_name("thumbprint")
.long("--thumbprint")
.takes_value(true),
)
.arg(
Arg::with_name("trust_url")
.long("--trust-url")
.takes_value(false),
)
.group(
ArgGroup::with_name("tang_args")
.arg("thumbprint")
.arg("trust_url"),
),
SubCommand::with_name("init-cache")
.arg(Arg::with_name("name").required(true))
.arg(Arg::with_name("blockdevs").multiple(true).required(true)),
SubCommand::with_name("rename")
.arg(Arg::with_name("current_name").required(true))
.arg(Arg::with_name("new_name").required(true)),
SubCommand::with_name("add-data")
.arg(Arg::with_name("name").required(true))
.arg(Arg::with_name("blockdevs").multiple(true).required(true)),
SubCommand::with_name("destroy").arg(Arg::with_name("name").required(true)),
SubCommand::with_name("is-encrypted")
.arg(Arg::with_name("pool_uuid").required(true)),
SubCommand::with_name("is-locked").arg(Arg::with_name("pool_uuid").required(true)),
SubCommand::with_name("is-bound").arg(Arg::with_name("pool_uuid").required(true)),
SubCommand::with_name("has-passphrase")
.arg(Arg::with_name("pool_uuid").required(true)),
SubCommand::with_name("clevis-pin").arg(Arg::with_name("pool_uuid").required(true)),
]),
SubCommand::with_name("filesystem").subcommands(vec![
SubCommand::with_name("create")
.arg(Arg::with_name("pool_name").required(true))
.arg(Arg::with_name("fs_name").required(true)),
SubCommand::with_name("destroy")
.arg(Arg::with_name("pool_name").required(true))
.arg(Arg::with_name("fs_name").required(true)),
SubCommand::with_name("rename")
.arg(Arg::with_name("pool_name").required(true))
.arg(Arg::with_name("fs_name").required(true))
.arg(Arg::with_name("new_fs_name").required(true)),
]),
SubCommand::with_name("report"),
])
}
fn get_paths_from_args<'a>(args: &'a ArgMatches<'a>) -> Vec<PathBuf> {
args.values_of("blockdevs")
.expect("required")
.map(PathBuf::from)
.collect::<Vec<_>>()
}
fn get_long_help(app: &mut App) -> Result<String, Box<dyn Error>> {
let mut help = Vec::new();
app.write_long_help(&mut help)?;
Ok(String::from_utf8(help)?)
}
fn main() -> Result<(), Box<dyn Error>> {
let mut app = parse_args();
let long_help = get_long_help(&mut app)?;
let args = app.get_matches();
if let Some(subcommand) = args.subcommand_matches("key") {
if let Some(args) = subcommand.subcommand_matches("set") {
key::key_set(
KeyDescription::try_from(args.value_of("key_desc").expect("required").to_string())?,
args.value_of("keyfile_path"),
)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("unset") {
key::key_unset(KeyDescription::try_from(
args.value_of("key_desc").expect("required").to_string(),
)?)?;
Ok(())
} else {
key::key_list()?;
Ok(())
}
} else if let Some(subcommand) = args.subcommand_matches("pool") {
if let Some(args) = subcommand.subcommand_matches("unlock") {
let unlock_method =
UnlockMethod::try_from(args.value_of("unlock_method").expect("required"))?;
let uuid = match args.value_of("pool_uuid") {
Some(u) => Some(PoolUuid::parse_str(u)?),
None => None,
};
let prompt = args.is_present("prompt");
if prompt && unlock_method == UnlockMethod::Clevis {
return Err(Box::new(StratisError::Error(
"--prompt and an unlock_method of clevis are mutally exclusive".to_string(),
)));
}
pool::pool_unlock(unlock_method, uuid, prompt)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("create") {
let paths = get_paths_from_args(args);
let key_description = match args.value_of("key_desc").map(|s| s.to_owned()) {
Some(string) => Some(KeyDescription::try_from(string)?),
None => None,
};
let pin = args.value_of("clevis");
let clevis_info = match pin {
Some("nbde") | Some("tang") => {
let mut json = Map::new();
json.insert(
"url".to_string(),
Value::from(args.value_of("tang_url").expect("Required")),
);
if args.is_present("trust_url") {
json.insert(CLEVIS_TANG_TRUST_URL.to_string(), Value::from(true));
} else if let Some(thp) = args.value_of("thumbprint") {
json.insert("thp".to_string(), Value::from(thp));
}
pin.map(|p| (p.to_string(), Value::from(json)))
}
Some("tpm2") => Some(("tpm2".to_string(), json!({}))),
Some(_) => unreachable!("Validated by parser"),
None => None,
};
pool::pool_create(
args.value_of("name").expect("required").to_string(),
paths,
EncryptionInfo {
key_description,
clevis_info,
},
)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("destroy") {
pool::pool_destroy(args.value_of("name").expect("required").to_string())?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("init-cache") {
let paths = get_paths_from_args(args);
pool::pool_init_cache(args.value_of("name").expect("required").to_string(), paths)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("rename") {
pool::pool_rename(
args.value_of("current_name").expect("required").to_string(),
args.value_of("new_name").expect("required").to_string(),
)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("add-data") {
let paths = get_paths_from_args(args);
pool::pool_add_data(args.value_of("name").expect("required").to_string(), paths)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("add-cache") {
let paths = get_paths_from_args(args);
pool::pool_add_cache(args.value_of("name").expect("required").to_string(), paths)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("is-encrypted") {
let uuid_str = args.value_of("pool_uuid").expect("required");
let uuid = PoolUuid::parse_str(uuid_str)?;
println!("{}", pool::pool_is_encrypted(uuid)?,);
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("is-locked") {
let uuid_str = args.value_of("pool_uuid").expect("required");
let uuid = PoolUuid::parse_str(uuid_str)?;
println!("{}", pool::pool_is_locked(uuid)?,);
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("is-bound") {
let uuid_str = args.value_of("pool_uuid").expect("required");
let uuid = PoolUuid::parse_str(uuid_str)?;
println!("{}", pool::pool_is_bound(uuid)?,);
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("has-passphrase") {
let uuid_str = args.value_of("pool_uuid").expect("required");
let uuid = PoolUuid::parse_str(uuid_str)?;
println!("{}", pool::pool_has_passphrase(uuid)?,);
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("clevis-pin") {
let uuid_str = args.value_of("pool_uuid").expect("required");
let uuid = PoolUuid::parse_str(uuid_str)?;
println!("{}", pool::pool_clevis_pin(uuid)?,);
Ok(())
} else {
pool::pool_list()?;
Ok(())
}
} else if let Some(subcommand) = args.subcommand_matches("filesystem") {
if let Some(args) = subcommand.subcommand_matches("create") {
filesystem::filesystem_create(
args.value_of("pool_name").expect("required").to_string(),
args.value_of("fs_name").expect("required").to_string(),
)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("destroy") {
filesystem::filesystem_destroy(
args.value_of("pool_name").expect("required").to_string(),
args.value_of("fs_name").expect("required").to_string(),
)?;
Ok(())
} else if let Some(args) = subcommand.subcommand_matches("rename") {
filesystem::filesystem_rename(
args.value_of("pool_name").expect("required").to_string(),
args.value_of("fs_name").expect("required").to_string(),
args.value_of("new_fs_name").expect("required").to_string(),
)?;
Ok(())
} else {
filesystem::filesystem_list()?;
Ok(())
}
} else if let Some("report") = args.subcommand_name() {
report::report().and_then(|j| {
println!("{}", serde_json::to_string_pretty(&j)?);
Ok(())
})?;
Ok(())
} else {
println!("{}", long_help);
Ok(())
}
}