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,
prompts,
wg::{DeviceExt, PeerInfoExt},
AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree,
DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext,
AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, AssociationContents, Cidr,
CidrTree, DeleteCidrOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext,
ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer, RedeemContents,
RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT,
};
@ -149,7 +149,13 @@ enum Command {
},
/// 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>')
Down { interface: Interface },
@ -216,11 +222,16 @@ enum Command {
interface: Interface,
#[clap(flatten)]
sub_opts: AddAssociationOpts,
sub_opts: AddDeleteAssociationOpts,
},
/// Delete an association between CIDRs
DeleteAssociation { interface: Interface },
DeleteAssociation {
interface: Interface,
#[clap(flatten)]
sub_opts: AddDeleteAssociationOpts,
},
/// List existing assocations between CIDRs
ListAssociations { interface: Interface },
@ -628,7 +639,7 @@ fn fetch(
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 data = DataStore::get_path(&opts.data_dir, interface);
@ -639,14 +650,15 @@ fn uninstall(interface: &InterfaceName, opts: &Opts) -> Result<(), Error> {
);
}
if Confirm::with_theme(&*prompts::THEME)
.with_prompt(&format!(
"Permanently delete network \"{}\"?",
interface.as_str_lossy().yellow()
))
.default(false)
.wait_for_newline(true)
.interact()?
if yes
|| Confirm::with_theme(&*prompts::THEME)
.with_prompt(&format!(
"Permanently delete network \"{}\"?",
interface.as_str_lossy().yellow()
))
.default(false)
.wait_for_newline(true)
.interact()?
{
log::info!("bringing down interface (if up).");
wg::down(interface, opts.network.backend).ok();
@ -821,7 +833,7 @@ fn enable_or_disable_peer(
fn add_association(
interface: &InterfaceName,
opts: &Opts,
sub_opts: AddAssociationOpts,
sub_opts: AddDeleteAssociationOpts,
) -> Result<(), Error> {
let InterfaceConfig { server, .. } =
InterfaceConfig::from_interface(&opts.config_dir, interface)?;
@ -830,7 +842,8 @@ fn add_association(
log::info!("Fetching 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
.iter()
.find(|c| &c.name == cidr1)
@ -840,7 +853,7 @@ fn add_association(
.find(|c| &c.name == cidr2)
.ok_or_else(|| anyhow!("can't find cidr '{}'", 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)
} else {
log::info!("exiting without adding association.");
@ -859,7 +872,11 @@ fn add_association(
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, .. } =
InterfaceConfig::from_interface(&opts.config_dir, interface)?;
let api = Api::new(&server);
@ -869,7 +886,9 @@ fn delete_association(interface: &InterfaceName, opts: &Opts) -> Result<(), Erro
log::info!("Fetching 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))?;
} else {
log::info!("exiting without adding association.");
@ -1211,7 +1230,7 @@ fn run(opts: &Opts) -> Result<(), Error> {
&nat,
)?,
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 {
interface,
sub_opts,
@ -1235,7 +1254,10 @@ fn run(opts: &Opts) -> Result<(), Error> {
interface,
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::SetListenPort {
interface,

View File

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

View File

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