#![cfg_attr(feature = "pedantic", warn(clippy::pedantic))]
#![warn(clippy::use_self)]
#![warn(deprecated_in_future)]
#![warn(future_incompatible)]
#![warn(unreachable_pub)]
#![warn(missing_debug_implementations)]
#![warn(rust_2018_compatibility)]
#![warn(rust_2018_idioms)]
#![warn(unused)]
#![deny(warnings)]
use std::path::PathBuf;
use std::process;
use structopt::StructOpt;
use uuid::Uuid;
mod api;
mod error;
mod fmt;
mod output;
mod show;
mod status;
mod util;
pub mod v20201231;
pub mod v20210228;
use self::v20210228 as latest;
#[cfg(test)]
mod tests;
const REXCLI_ABOUT: &str = "Replix Admin CLI tool";
#[derive(Debug, StructOpt)]
#[structopt(about = REXCLI_ABOUT)]
struct RexCli {
#[structopt(
help = "Management server URL or address",
default_value = "https://api.replix.io",
short,
long,
env = "REX_MGMT"
)]
management: String,
#[structopt(help = "Authentication token", short, long, env = "REX_TOKEN")]
token: Option<String>,
#[structopt(
long,
global = true,
conflicts_with = "json",
help = "Show raw JSON output (different from '--json')"
)]
raw: bool,
#[structopt(
long,
global = true,
conflicts_with = "raw",
help = "Show results as JSON (different from '--raw')"
)]
json: bool,
#[structopt(short, long, global = true)]
verbose: bool,
#[structopt(
help = "API version to use",
long,
global = true,
default_value,
env = "REX_API"
)]
api: api::ApiEndpoint,
#[structopt(subcommand)]
command: Command,
}
#[derive(Debug, StructOpt)]
enum Command {
#[structopt(about = "Realm operations")]
Realm {
#[structopt(subcommand)]
command: RealmCommand,
},
#[structopt(about = "Volume operations")]
Volume {
#[structopt(subcommand)]
command: VolumeCommand,
},
#[structopt(about = "Snapshot operations", alias = "snap")]
Snapshot {
#[structopt(subcommand)]
command: SnapshotCommand,
},
#[structopt(about = "Job inspection")]
Job {
#[structopt(help = "Get all jobs", short, long)]
all: bool,
#[structopt(help = "Get this job", short, long)]
job_id: Option<Uuid>,
#[structopt(help = "Filter by job type", short, long)]
r#type: Option<String>,
#[structopt(
help = "Filter by the job status",
default_value = "in_progress",
short,
long,
parse(try_from_str = status::job),
)]
status: String,
},
#[structopt(about = "Query the version of the system")]
Version {
#[structopt(help = "Show crates metadata", long, short)]
crates: bool,
},
#[structopt(external_subcommand)]
External(Vec<String>),
}
#[derive(Debug, StructOpt)]
#[structopt(about = "Realm management")]
enum RealmCommand {
#[structopt(about = "Create new realm")]
Create {
#[structopt(help = "Realm name to create")]
realm: String,
#[structopt(subcommand)]
spec: RealmSpec,
},
#[structopt(about = "Delete existing realm")]
Delete {
#[structopt(help = "Realm to delete")]
realm: String,
},
#[structopt(about = "List all existing realms")]
List,
#[structopt(about = "Show realm details")]
Show {
#[structopt(help = "Realm to show")]
realm: String,
},
}
#[derive(Clone, Debug, StructOpt)]
#[structopt(about = "Realm Definition")]
enum RealmSpec {
#[structopt(about = "Create AWS realm")]
Aws {
#[structopt(help = "Role ARN")]
role_arn: String,
#[structopt(help = "External ID")]
external_id: String,
},
#[structopt(about = "Create Azure realm")]
Azure {
#[structopt(help = "Subscription ID")]
subscription_id: String,
#[structopt(help = "Tenant ID")]
tenant_id: String,
#[structopt(help = "Client ID")]
client_id: String,
#[structopt(help = "Client Secret")]
client_secret: String,
},
}
#[derive(Debug, StructOpt)]
#[structopt(about = "Snapshot management")]
enum SnapshotCommand {
#[structopt(about = "List all the snapshots of this volume")]
List {
#[structopt(help = "Volume to show")]
volume: String,
},
#[structopt(about = "Create snapshot")]
Create {
#[structopt(help = "Volume to snapshot")]
volume: String,
#[structopt(help = "Snapshot name")]
snapshot: String,
#[structopt(help = "Wait for snapshot creation to complete", short, long)]
wait: bool,
},
#[structopt(about = "Delete snapshot")]
Delete {
#[structopt(help = "Volume to delete snapshot from")]
volume: String,
#[structopt(help = "Snapshot name")]
snapshot: String,
#[structopt(help = "Wait for snapshot delete to complete", short, long)]
wait: bool,
},
#[structopt(about = "Export snapshot")]
Export {
#[structopt(help = "Volume to export snapshot from")]
volume: String,
#[structopt(help = "Snapshot name")]
snapshot: String,
#[structopt(help = "Realm name", long)]
realm: String,
#[structopt(help = "AWS Region name", long)]
region: String,
},
}
#[derive(Debug, StructOpt)]
#[structopt(about = "Volume management")]
enum VolumeCommand {
#[structopt(about = "Show volume details")]
Show {
#[structopt(help = "Volume to show")]
volume: String,
},
#[structopt(about = "Create new volume")]
Create {
#[structopt(flatten)]
create: VolumeCreate,
#[structopt(help = "Wait for volume creation to complete", short, long)]
wait: bool,
},
#[structopt(about = "Delete volume")]
Delete {
volume: String,
#[structopt(short, long)]
delete_native: bool,
#[structopt(short, long)]
force: bool,
#[structopt(help = "Wait for volume deletion to complete", short, long)]
wait: bool,
},
#[structopt(about = "List volumes")]
List,
#[structopt(about = "Set primary replica")]
SetPrimary { volume: String, replica: String },
#[structopt(about = "Add a replica")]
AddReplica {
#[structopt(help = "Volume to add replica to")]
volume: String,
#[structopt(help = "Use json file for request body", short, long)]
body: PathBuf,
#[structopt(help = "Wait for add replica to complete", short, long)]
wait: bool,
},
#[structopt(about = "Remove a replica")]
RemoveReplica {
#[structopt(help = "Volume to remove replica from")]
volume: String,
#[structopt(help = "Replica to remove")]
replica: String,
#[structopt(help = "Wait for replica removal to complete", short, long)]
wait: bool,
},
#[structopt(about = "Wait until volume reaches a given status")]
Wait {
volume: String,
#[structopt(short, long, help = "Wait for a given replica")]
replica: Option<String>,
#[structopt(short, long, help = "Make sure this replica is primary")]
primary: bool,
#[structopt(default_value = "online", short, long, parse(try_from_str = status::volume))]
status: String,
},
}
#[derive(Debug, StructOpt)]
enum VolumeCreate {
#[structopt(about = "Create volume using JSON file as VOLUME CREATE API call body")]
Body {
#[structopt(help = "file name")]
path: PathBuf,
},
#[structopt(about = "Generate body template for VOLUME CREATE API JSON file")]
Template {
#[structopt(help = "file name", default_value = "create.json")]
path: PathBuf,
},
}
impl RexCli {
fn api(&self) -> api::Api {
api::Api::new(
&self.management,
self.api,
&self.token,
self.json,
self.raw,
self.verbose,
)
}
fn execute(self) -> Result<(), anyhow::Error> {
let output = match self.command {
Command::Realm { ref command } => self.realm(command),
Command::Volume { ref command } => self.volume(command),
Command::Snapshot { ref command } => self.snapshot(command),
Command::Job {
all,
job_id,
ref r#type,
ref status,
} => self.api().job(all, job_id, r#type.as_deref(), status),
Command::Version { crates } => self.api().version(crates),
Command::External(_) => self.external(),
};
output.map(|text| println!("{}", text))
}
fn realm(&self, command: &RealmCommand) -> Result<String, anyhow::Error> {
match command {
RealmCommand::Create { realm, spec } => self.api().realm().create(realm, spec.to_api()),
RealmCommand::Delete { realm } => self.api().realm().delete(realm),
RealmCommand::List => self.api().realm().list(),
RealmCommand::Show { realm } => self.api().realm().show(realm),
}
}
fn volume(&self, command: &VolumeCommand) -> Result<String, anyhow::Error> {
match command {
VolumeCommand::Show { volume } => self.api().volume().show(volume),
VolumeCommand::Create { create, wait } => self.api().volume().create(create, *wait),
VolumeCommand::Delete {
volume,
delete_native,
force,
wait,
} => self
.api()
.volume()
.delete(volume, *delete_native, *force, *wait),
VolumeCommand::List => self.api().volume().list(),
VolumeCommand::SetPrimary { volume, replica } => {
self.api().volume().set_primary(volume, replica)
}
VolumeCommand::AddReplica { volume, body, wait } => self
.api()
.volume()
.add_replica_from_file(volume, body, *wait),
VolumeCommand::RemoveReplica {
volume,
replica,
wait,
} => self.api().volume().remove_replica(volume, replica, *wait),
VolumeCommand::Wait {
volume,
replica,
primary,
status,
} => self.api().volume().wait_for_volume_status(
volume,
replica.as_deref(),
*primary,
status,
),
}
}
fn snapshot(&self, command: &SnapshotCommand) -> Result<String, anyhow::Error> {
match command {
SnapshotCommand::List { volume } => self.api().volume().snapshot_list(volume),
SnapshotCommand::Create {
volume,
snapshot,
wait,
} => self.api().volume().snapshot_create(volume, snapshot, *wait),
SnapshotCommand::Delete {
volume,
snapshot,
wait,
} => self.api().volume().snapshot_delete(volume, snapshot, *wait),
SnapshotCommand::Export {
volume,
snapshot,
realm,
region,
} => self
.api()
.volume()
.snapshot_export_aws(volume, snapshot, realm, region),
}
}
fn external(&self) -> Result<String, anyhow::Error> {
if let Command::External(ref args) = self.command {
let plugin = format!("rexcli-{}", args[0]);
if let Ok(mut command) = which::which(plugin).map(process::Command::new) {
command.args(&args[1..]).spawn()?.wait()?;
} else {
Self::clap().print_long_help()?;
}
}
Ok(String::new())
}
}
fn main() -> Result<(), anyhow::Error> {
dotenv::dotenv().ok();
env_logger::init();
let about = format!(
"{}\nAPI version {}",
env!("CARGO_PKG_VERSION"),
latest::VERSION
);
let matches = RexCli::clap().long_version(about.as_str()).get_matches();
RexCli::from_clap(&matches).execute()
}