client, server: adds ability to delete cidrs (#88)

This commit adds a `delete-cidr` to both the client and server. It walks
through the prompts just like adding a CIDR.

Only eligible CIDRs are presented to the user. Eligibilty requires:

- CIDR has no child CIDRs
- CIDR has no assigned peers

Closes #23
pull/92/head
Kevin K 2021-05-20 23:39:33 -04:00 committed by GitHub
parent 15595d6f23
commit ff0527d836
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 6 deletions

View File

@ -4,9 +4,9 @@ use hostsfile::HostsBuilder;
use indoc::eprintdoc;
use shared::{
interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts,
Association, AssociationContents, Cidr, CidrTree, EndpointContents, InstallOpts, Interface,
IoErrorContext, NetworkOpt, Peer, RedeemContents, State, CLIENT_CONFIG_DIR,
REDEEM_TRANSITION_WAIT,
Association, AssociationContents, Cidr, CidrTree, DeleteCidrOpts, EndpointContents,
InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, RedeemContents, State,
CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT,
};
use std::{
fmt,
@ -148,6 +148,14 @@ enum Command {
opts: AddCidrOpts,
},
/// Delete a CIDR.
DeleteCidr {
interface: Interface,
#[structopt(flatten)]
opts: DeleteCidrOpts,
},
/// Disable an enabled peer.
DisablePeer { interface: Interface },
@ -556,6 +564,23 @@ fn add_cidr(interface: &InterfaceName, opts: AddCidrOpts) -> Result<(), Error> {
Ok(())
}
fn delete_cidr(interface: &InterfaceName, opts: DeleteCidrOpts) -> Result<(), Error> {
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
println!("Fetching eligible CIDRs");
let api = Api::new(&server);
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
let peers: Vec<Peer> = api.http("GET", "/admin/peers")?;
let cidr_id = prompts::delete_cidr(&cidrs, &peers, &opts)?;
println!("Deleting CIDR...");
let _ = api.http("DELETE", &*format!("/admin/cidrs/{}", cidr_id))?;
println!("CIDR deleted.");
Ok(())
}
fn add_peer(interface: &InterfaceName, opts: AddPeerOpts) -> Result<(), Error> {
let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?;
let api = Api::new(&server);
@ -957,6 +982,7 @@ fn run(opt: Opts) -> Result<(), Error> {
Command::Uninstall { interface } => uninstall(&interface, opt.network)?,
Command::AddPeer { interface, opts } => add_peer(&interface, opts)?,
Command::AddCidr { interface, opts } => add_cidr(&interface, opts)?,
Command::DeleteCidr { interface, opts } => delete_cidr(&interface, opts)?,
Command::DisablePeer { interface } => enable_or_disable_peer(&interface, false)?,
Command::EnablePeer { interface } => enable_or_disable_peer(&interface, true)?,
Command::AddAssociation { interface, opts } => add_association(&interface, opts)?,

View File

@ -6,7 +6,7 @@ use ipnetwork::IpNetwork;
use parking_lot::{Mutex, RwLock};
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use shared::{AddCidrOpts, AddPeerOpts, IoErrorContext, NetworkOpt, INNERNET_PUBKEY_HEADER};
use shared::{AddCidrOpts, DeleteCidrOpts, AddPeerOpts, IoErrorContext, NetworkOpt, INNERNET_PUBKEY_HEADER};
use std::{
collections::{HashMap, VecDeque},
convert::TryInto,
@ -85,6 +85,15 @@ enum Command {
#[structopt(flatten)]
args: AddCidrOpts,
},
/// Delete a CIDR.
DeleteCidr {
interface: Interface,
#[structopt(flatten)]
args: DeleteCidrOpts,
},
}
pub type Db = Arc<Mutex<Connection>>;
@ -221,6 +230,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} => serve(*interface, &conf, routing).await?,
Command::AddPeer { interface, args } => add_peer(&interface, &conf, args, opt.network)?,
Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?,
Command::DeleteCidr { interface, args } => delete_cidr(&interface, &conf, args)?,
}
Ok(())
@ -317,6 +327,25 @@ fn add_cidr(
Ok(())
}
fn delete_cidr(interface: &InterfaceName, conf: &ServerConfig, args: DeleteCidrOpts) -> Result<(), Error> {
println!("Fetching eligible CIDRs");
let conn = open_database_connection(interface, conf)?;
let cidrs = DatabaseCidr::list(&conn)?;
let peers = DatabasePeer::list(&conn)?
.into_iter()
.map(|dp| dp.inner)
.collect::<Vec<_>>();
let cidr_id = prompts::delete_cidr(&cidrs, &peers, &args)?;
println!("Deleting CIDR...");
let _ = DatabaseCidr::delete(&conn, cidr_id)?;
println!("CIDR deleted.");
Ok(())
}
fn uninstall(
interface: &InterfaceName,
conf: &ServerConfig,

View File

@ -1,7 +1,7 @@
use crate::{
interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo},
AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, Endpoint, Error, Peer,
PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint,
Error, Peer, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
};
use colored::*;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select};
@ -58,6 +58,42 @@ pub fn add_cidr(cidrs: &[Cidr], request: &AddCidrOpts) -> Result<Option<CidrCont
)
}
/// Bring up a prompt to delete a CIDR. Returns the peer request.
pub fn delete_cidr(cidrs: &[Cidr], peers: &[Peer], request: &DeleteCidrOpts) -> Result<i64, Error> {
let eligible_cidrs: Vec<_> = cidrs
.iter()
.filter(|cidr| {
!peers.iter().any(|peer| peer.contents.cidr_id == cidr.id) &&
!cidrs.iter().any(
|cidr2| matches!(cidr2.contents.parent, Some(parent_id) if parent_id == cidr.id)
)
})
.collect();
let cidr = if let Some(ref name) = request.name {
cidrs
.iter()
.find(|cidr| &cidr.name == name)
.ok_or_else(|| format!("CIDR {} doesn't exist or isn't eligible for deletion", name))?
} else {
let cidr_index = Select::with_theme(&*THEME)
.with_prompt("Delete CIDR")
.items(&eligible_cidrs)
.interact()?;
&eligible_cidrs[cidr_index]
};
if request.yes
|| Confirm::with_theme(&*THEME)
.with_prompt(&format!("Delete CIDR \"{}\"?", cidr.name))
.default(false)
.interact()?
{
Ok(cidr.id)
} else {
Err("Canceled".into())
}
}
pub fn choose_cidr<'a>(cidrs: &'a [Cidr], text: &'static str) -> Result<&'a Cidr, Error> {
let eligible_cidrs: Vec<_> = cidrs
.iter()

View File

@ -337,6 +337,17 @@ pub struct AddCidrOpts {
pub yes: bool,
}
#[derive(Debug, Clone, PartialEq, StructOpt)]
pub struct DeleteCidrOpts {
/// The CIDR name (eg. "engineers")
#[structopt(long)]
pub name: Option<String>,
/// Bypass confirmation
#[structopt(long)]
pub yes: bool,
}
#[derive(Debug, Clone, PartialEq, StructOpt)]
pub struct AddAssociationOpts {
/// The first cidr to associate