client: make more commands automation-friendly

Fixes #190
pull/192/head
Jake McGinty 2022-01-31 06:10:45 +00:00
parent e11b73972c
commit ddac328ae5
3 changed files with 123 additions and 56 deletions

View File

@ -9,8 +9,8 @@ use shared::{
interface_config::InterfaceConfig, interface_config::InterfaceConfig,
prompts, prompts,
wg::{DeviceExt, PeerInfoExt}, wg::{DeviceExt, PeerInfoExt},
AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree, AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, AssociationContents, Cidr,
DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext, CidrTree, DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext,
ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer, RedeemContents, ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer, RedeemContents,
RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT, RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT,
}; };
@ -149,7 +149,13 @@ enum Command {
}, },
/// Uninstall an innernet network. /// Uninstall an innernet network.
Uninstall { interface: Interface }, Uninstall {
interface: Interface,
/// Bypass confirmation
#[clap(long)]
yes: bool,
},
/// Bring down the interface (equivalent to 'wg-quick down <interface>') /// Bring down the interface (equivalent to 'wg-quick down <interface>')
Down { interface: Interface }, Down { interface: Interface },
@ -216,11 +222,16 @@ enum Command {
interface: Interface, interface: Interface,
#[clap(flatten)] #[clap(flatten)]
sub_opts: AddAssociationOpts, sub_opts: AddDeleteAssociationOpts,
}, },
/// Delete an association between CIDRs /// Delete an association between CIDRs
DeleteAssociation { interface: Interface }, DeleteAssociation {
interface: Interface,
#[clap(flatten)]
sub_opts: AddDeleteAssociationOpts,
},
/// List existing assocations between CIDRs /// List existing assocations between CIDRs
ListAssociations { interface: Interface }, ListAssociations { interface: Interface },
@ -628,7 +639,7 @@ fn fetch(
Ok(()) Ok(())
} }
fn uninstall(interface: &InterfaceName, opts: &Opts) -> Result<(), Error> { fn uninstall(interface: &InterfaceName, opts: &Opts, yes: bool) -> Result<(), Error> {
let config = InterfaceConfig::get_path(&opts.config_dir, interface); let config = InterfaceConfig::get_path(&opts.config_dir, interface);
let data = DataStore::get_path(&opts.data_dir, interface); let data = DataStore::get_path(&opts.data_dir, interface);
@ -639,7 +650,8 @@ fn uninstall(interface: &InterfaceName, opts: &Opts) -> Result<(), Error> {
); );
} }
if Confirm::with_theme(&*prompts::THEME) if yes
|| Confirm::with_theme(&*prompts::THEME)
.with_prompt(&format!( .with_prompt(&format!(
"Permanently delete network \"{}\"?", "Permanently delete network \"{}\"?",
interface.as_str_lossy().yellow() interface.as_str_lossy().yellow()
@ -821,7 +833,7 @@ fn enable_or_disable_peer(
fn add_association( fn add_association(
interface: &InterfaceName, interface: &InterfaceName,
opts: &Opts, opts: &Opts,
sub_opts: AddAssociationOpts, sub_opts: AddDeleteAssociationOpts,
) -> Result<(), Error> { ) -> Result<(), Error> {
let InterfaceConfig { server, .. } = let InterfaceConfig { server, .. } =
InterfaceConfig::from_interface(&opts.config_dir, interface)?; InterfaceConfig::from_interface(&opts.config_dir, interface)?;
@ -830,7 +842,8 @@ fn add_association(
log::info!("Fetching CIDRs"); log::info!("Fetching CIDRs");
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?; let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
let association = if let (Some(ref cidr1), Some(ref cidr2)) = (sub_opts.cidr1, sub_opts.cidr2) { let association = if let (Some(ref cidr1), Some(ref cidr2)) = (&sub_opts.cidr1, &sub_opts.cidr2)
{
let cidr1 = cidrs let cidr1 = cidrs
.iter() .iter()
.find(|c| &c.name == cidr1) .find(|c| &c.name == cidr1)
@ -840,7 +853,7 @@ fn add_association(
.find(|c| &c.name == cidr2) .find(|c| &c.name == cidr2)
.ok_or_else(|| anyhow!("can't find cidr '{}'", cidr2))?; .ok_or_else(|| anyhow!("can't find cidr '{}'", cidr2))?;
(cidr1, cidr2) (cidr1, cidr2)
} else if let Some((cidr1, cidr2)) = prompts::add_association(&cidrs[..])? { } else if let Some((cidr1, cidr2)) = prompts::add_association(&cidrs[..], &sub_opts)? {
(cidr1, cidr2) (cidr1, cidr2)
} else { } else {
log::info!("exiting without adding association."); log::info!("exiting without adding association.");
@ -859,7 +872,11 @@ fn add_association(
Ok(()) Ok(())
} }
fn delete_association(interface: &InterfaceName, opts: &Opts) -> Result<(), Error> { fn delete_association(
interface: &InterfaceName,
opts: &Opts,
sub_opts: AddDeleteAssociationOpts,
) -> Result<(), Error> {
let InterfaceConfig { server, .. } = let InterfaceConfig { server, .. } =
InterfaceConfig::from_interface(&opts.config_dir, interface)?; InterfaceConfig::from_interface(&opts.config_dir, interface)?;
let api = Api::new(&server); let api = Api::new(&server);
@ -869,7 +886,9 @@ fn delete_association(interface: &InterfaceName, opts: &Opts) -> Result<(), Erro
log::info!("Fetching associations"); log::info!("Fetching associations");
let associations: Vec<Association> = api.http("GET", "/admin/associations")?; let associations: Vec<Association> = api.http("GET", "/admin/associations")?;
if let Some(association) = prompts::delete_association(&associations[..], &cidrs[..])? { if let Some(association) =
prompts::delete_association(&associations[..], &cidrs[..], &sub_opts)?
{
api.http("DELETE", &format!("/admin/associations/{}", association.id))?; api.http("DELETE", &format!("/admin/associations/{}", association.id))?;
} else { } else {
log::info!("exiting without adding association."); log::info!("exiting without adding association.");
@ -1211,7 +1230,7 @@ fn run(opts: &Opts) -> Result<(), Error> {
&nat, &nat,
)?, )?,
Command::Down { interface } => wg::down(&interface, opts.network.backend)?, Command::Down { interface } => wg::down(&interface, opts.network.backend)?,
Command::Uninstall { interface } => uninstall(&interface, opts)?, Command::Uninstall { interface, yes } => uninstall(&interface, opts, yes)?,
Command::AddPeer { Command::AddPeer {
interface, interface,
sub_opts, sub_opts,
@ -1235,7 +1254,10 @@ fn run(opts: &Opts) -> Result<(), Error> {
interface, interface,
sub_opts, sub_opts,
} => add_association(&interface, opts, sub_opts)?, } => add_association(&interface, opts, sub_opts)?,
Command::DeleteAssociation { interface } => delete_association(&interface, opts)?, Command::DeleteAssociation {
interface,
sub_opts,
} => delete_association(&interface, opts, sub_opts)?,
Command::ListAssociations { interface } => list_associations(&interface, opts)?, Command::ListAssociations { interface } => list_associations(&interface, opts)?,
Command::SetListenPort { Command::SetListenPort {
interface, interface,

View File

@ -1,8 +1,8 @@
use crate::{ use crate::{
interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo}, interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo},
AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint, AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree,
Error, Hostname, ListenPortOpts, OverrideEndpointOpts, Peer, PeerContents, RenamePeerOpts, DeleteCidrOpts, Endpoint, Error, Hostname, ListenPortOpts, OverrideEndpointOpts, Peer,
PERSISTENT_KEEPALIVE_INTERVAL_SECS, PeerContents, RenamePeerOpts, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
}; };
use anyhow::anyhow; use anyhow::anyhow;
use colored::*; use colored::*;
@ -151,7 +151,21 @@ pub fn choose_cidr<'a>(cidrs: &'a [Cidr], text: &'static str) -> Result<&'a Cidr
pub fn choose_association<'a>( pub fn choose_association<'a>(
associations: &'a [Association], associations: &'a [Association],
cidrs: &'a [Cidr], cidrs: &'a [Cidr],
args: &AddDeleteAssociationOpts,
) -> Result<&'a Association, Error> { ) -> Result<&'a Association, Error> {
match (&args.cidr1, &args.cidr2) {
(Some(cidr1_name), Some(cidr2_name)) => {
let cidr1 = find_cidr(cidrs, cidr1_name)?;
let cidr2 = find_cidr(cidrs, cidr2_name)?;
associations
.iter()
.find(|association| {
(association.cidr_id_1 == cidr1.id && association.cidr_id_2 == cidr2.id)
|| (association.cidr_id_1 == cidr2.id && association.cidr_id_2 == cidr1.id)
})
.ok_or_else(|| anyhow!("CIDR association does not exist"))
},
_ => {
let names: Vec<_> = associations let names: Vec<_> = associations
.iter() .iter()
.map(|association| { .map(|association| {
@ -174,18 +188,44 @@ pub fn choose_association<'a>(
let (index, _) = select("Association", &names)?; let (index, _) = select("Association", &names)?;
Ok(&associations[index]) Ok(&associations[index])
},
}
} }
pub fn add_association(cidrs: &[Cidr]) -> Result<Option<(&Cidr, &Cidr)>, Error> { fn find_cidr<'a>(cidrs: &'a [Cidr], name: &str) -> Result<&'a Cidr, Error> {
let cidr1 = choose_cidr(cidrs, "First CIDR")?; cidrs
let cidr2 = choose_cidr(cidrs, "Second CIDR")?; .iter()
.find(|c| c.name == name)
.ok_or_else(|| anyhow!("can't find cidr '{}'", name))
}
fn find_or_prompt_cidr<'a>(
cidrs: &'a [Cidr],
sub_opt: &Option<String>,
prompt: &'static str,
) -> Result<&'a Cidr, Error> {
if let Some(name) = sub_opt {
find_cidr(cidrs, name)
} else {
choose_cidr(cidrs, prompt)
}
}
pub fn add_association<'a>(
cidrs: &'a [Cidr],
args: &AddDeleteAssociationOpts,
) -> Result<Option<(&'a Cidr, &'a Cidr)>, Error> {
let cidr1 = find_or_prompt_cidr(cidrs, &args.cidr1, "First CIDR")?;
let cidr2 = find_or_prompt_cidr(cidrs, &args.cidr2, "Second CIDR")?;
Ok( Ok(
if confirm(&format!( if args.yes
|| confirm(&format!(
"Add association: {} <=> {}?", "Add association: {} <=> {}?",
cidr1.name.yellow().bold(), cidr1.name.yellow().bold(),
cidr2.name.yellow().bold() cidr2.name.yellow().bold()
))? { ))?
{
Some((cidr1, cidr2)) Some((cidr1, cidr2))
} else { } else {
None None
@ -196,11 +236,12 @@ pub fn add_association(cidrs: &[Cidr]) -> Result<Option<(&Cidr, &Cidr)>, Error>
pub fn delete_association<'a>( pub fn delete_association<'a>(
associations: &'a [Association], associations: &'a [Association],
cidrs: &'a [Cidr], cidrs: &'a [Cidr],
args: &AddDeleteAssociationOpts,
) -> Result<Option<&'a Association>, Error> { ) -> Result<Option<&'a Association>, Error> {
let association = choose_association(associations, cidrs)?; let association = choose_association(associations, cidrs, args)?;
Ok( Ok(
if confirm(&format!("Delete association #{}?", association.id))? { if args.yes || confirm(&format!("Delete association #{}?", association.id))? {
Some(association) Some(association)
} else { } else {
None None

View File

@ -379,12 +379,16 @@ pub struct DeleteCidrOpts {
} }
#[derive(Debug, Clone, PartialEq, Args)] #[derive(Debug, Clone, PartialEq, Args)]
pub struct AddAssociationOpts { pub struct AddDeleteAssociationOpts {
/// The first cidr to associate /// The first cidr to associate
pub cidr1: Option<String>, pub cidr1: Option<String>,
/// The second cidr to associate /// The second cidr to associate
pub cidr2: Option<String>, pub cidr2: Option<String>,
/// Bypass confirmation
#[clap(long)]
pub yes: bool,
} }
#[derive(Debug, Clone, PartialEq, Args)] #[derive(Debug, Clone, PartialEq, Args)]