Add a new client / server command to rename CIDR (#310)
* Add a new client / server command to rename CIDR. * Add a docker test case * Apply suggestions from code review Co-authored-by: Matěj Laitl <matej@laitl.cz> Co-authored-by: Jake McGinty <me@jakebot.org> --------- Co-authored-by: Matěj Laitl <matej@laitl.cz> Co-authored-by: Jake McGinty <me@jakebot.org>pull/312/head
parent
8ab0989f8f
commit
3c69de4e4e
|
@ -12,7 +12,7 @@ use shared::{
|
||||||
AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, AssociationContents, Cidr,
|
AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, AssociationContents, Cidr,
|
||||||
CidrTree, DeleteCidrOpts, EnableDisablePeerOpts, Endpoint, EndpointContents, InstallOpts,
|
CidrTree, DeleteCidrOpts, EnableDisablePeerOpts, Endpoint, EndpointContents, InstallOpts,
|
||||||
Interface, IoErrorContext, ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer,
|
Interface, IoErrorContext, ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer,
|
||||||
RedeemContents, RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT,
|
RedeemContents, RenameCidrOpts, RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
fmt, io,
|
fmt, io,
|
||||||
|
@ -193,6 +193,19 @@ enum Command {
|
||||||
sub_opts: AddCidrOpts,
|
sub_opts: AddCidrOpts,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Rename a CIDR
|
||||||
|
///
|
||||||
|
/// By default, you'll be prompted interactively to select a CIDR, but you can
|
||||||
|
/// also specify all the options in the command, eg:
|
||||||
|
///
|
||||||
|
/// --name 'group' --new-name 'family'
|
||||||
|
RenameCidr {
|
||||||
|
interface: Interface,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
sub_opts: RenameCidrOpts,
|
||||||
|
},
|
||||||
|
|
||||||
/// Delete a CIDR
|
/// Delete a CIDR
|
||||||
DeleteCidr {
|
DeleteCidr {
|
||||||
interface: Interface,
|
interface: Interface,
|
||||||
|
@ -724,6 +737,36 @@ fn add_cidr(interface: &InterfaceName, opts: &Opts, sub_opts: AddCidrOpts) -> Re
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rename_cidr(
|
||||||
|
interface: &InterfaceName,
|
||||||
|
opts: &Opts,
|
||||||
|
sub_opts: RenameCidrOpts,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let InterfaceConfig { server, .. } =
|
||||||
|
InterfaceConfig::from_interface(&opts.config_dir, interface)?;
|
||||||
|
let api = Api::new(&server);
|
||||||
|
|
||||||
|
log::info!("Fetching CIDRs");
|
||||||
|
let cidrs: Vec<Cidr> = api.http("GET", "/admin/cidrs")?;
|
||||||
|
|
||||||
|
if let Some((cidr_request, old_name)) = prompts::rename_cidr(&cidrs, &sub_opts)? {
|
||||||
|
log::info!("Renaming CIDR...");
|
||||||
|
|
||||||
|
let id = cidrs
|
||||||
|
.iter()
|
||||||
|
.find(|c| c.name == old_name)
|
||||||
|
.ok_or_else(|| anyhow!("CIDR not found."))?
|
||||||
|
.id;
|
||||||
|
|
||||||
|
api.http_form("PUT", &format!("/admin/cidrs/{id}"), cidr_request)?;
|
||||||
|
log::info!("CIDR renamed.");
|
||||||
|
} else {
|
||||||
|
log::info!("Exited without renaming CIDR.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn delete_cidr(
|
fn delete_cidr(
|
||||||
interface: &InterfaceName,
|
interface: &InterfaceName,
|
||||||
opts: &Opts,
|
opts: &Opts,
|
||||||
|
@ -1251,6 +1294,10 @@ fn run(opts: &Opts) -> Result<(), Error> {
|
||||||
interface,
|
interface,
|
||||||
sub_opts,
|
sub_opts,
|
||||||
} => add_cidr(&interface, opts, sub_opts)?,
|
} => add_cidr(&interface, opts, sub_opts)?,
|
||||||
|
Command::RenameCidr {
|
||||||
|
interface,
|
||||||
|
sub_opts,
|
||||||
|
} => rename_cidr(&interface, opts, sub_opts)?,
|
||||||
Command::DeleteCidr {
|
Command::DeleteCidr {
|
||||||
interface,
|
interface,
|
||||||
sub_opts,
|
sub_opts,
|
||||||
|
|
|
@ -201,6 +201,30 @@ test_simultaneous_redemption() {
|
||||||
cmd docker exec "$PEER2_CONTAINER" ping -c3 10.66.1.1
|
cmd docker exec "$PEER2_CONTAINER" ping -c3 10.66.1.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test_rename_cidr() {
|
||||||
|
info "Renaming CIDR from peer1"
|
||||||
|
cmd docker exec "$PEER1_CONTAINER" innernet \
|
||||||
|
rename-cidr evilcorp \
|
||||||
|
--name "robots" \
|
||||||
|
--new-name "coolbeans" \
|
||||||
|
--yes
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
info "Confirming the CIDR rename from peer1"
|
||||||
|
cmd docker exec "$PEER1_CONTAINER" innernet list-cidrs evilcorp | grep coolbeans
|
||||||
|
|
||||||
|
info "Renaming CIDR from server"
|
||||||
|
cmd docker exec "$SERVER_CONTAINER" innernet-server \
|
||||||
|
rename-cidr evilcorp \
|
||||||
|
--name "coolbeans" \
|
||||||
|
--new-name "robots" \
|
||||||
|
--yes
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
info "Confirming the CIDR rename from peer1"
|
||||||
|
cmd docker exec "$PEER1_CONTAINER" innernet list-cidrs evilcorp | grep robots
|
||||||
|
}
|
||||||
|
|
||||||
# Run tests (functions prefixed with test_) in alphabetical order.
|
# Run tests (functions prefixed with test_) in alphabetical order.
|
||||||
# Optional filter provided by positional arguments is applied.
|
# Optional filter provided by positional arguments is applied.
|
||||||
for func in $(declare -F | awk '{print $3}'); do
|
for func in $(declare -F | awk '{print $3}'); do
|
||||||
|
|
|
@ -19,6 +19,11 @@ pub async fn routes(
|
||||||
let form = form_body(req).await?;
|
let form = form_body(req).await?;
|
||||||
handlers::create(form, session).await
|
handlers::create(form, session).await
|
||||||
},
|
},
|
||||||
|
(&Method::PUT, Some(id)) => {
|
||||||
|
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
|
||||||
|
let form = form_body(req).await?;
|
||||||
|
handlers::update(id, form, session).await
|
||||||
|
},
|
||||||
(&Method::DELETE, Some(id)) => {
|
(&Method::DELETE, Some(id)) => {
|
||||||
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
|
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
|
||||||
handlers::delete(id, session).await
|
handlers::delete(id, session).await
|
||||||
|
@ -43,6 +48,18 @@ mod handlers {
|
||||||
json_status_response(cidr, StatusCode::CREATED)
|
json_status_response(cidr, StatusCode::CREATED)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
id: i64,
|
||||||
|
form: CidrContents,
|
||||||
|
session: Session,
|
||||||
|
) -> Result<Response<Body>, ServerError> {
|
||||||
|
let conn = session.context.db.lock();
|
||||||
|
let cidr = DatabaseCidr::get(&conn, id)?;
|
||||||
|
DatabaseCidr::from(cidr).update(&conn, form)?;
|
||||||
|
|
||||||
|
status_response(StatusCode::NO_CONTENT)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list(session: Session) -> Result<Response<Body>, ServerError> {
|
pub async fn list(session: Session) -> Result<Response<Body>, ServerError> {
|
||||||
let conn = session.context.db.lock();
|
let conn = session.context.db.lock();
|
||||||
let cidrs = DatabaseCidr::list(&conn)?;
|
let cidrs = DatabaseCidr::list(&conn)?;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::ServerError;
|
||||||
use ipnet::IpNet;
|
use ipnet::IpNet;
|
||||||
use rusqlite::{params, Connection};
|
use rusqlite::{params, Connection};
|
||||||
use shared::{Cidr, CidrContents};
|
use shared::{Cidr, CidrContents};
|
||||||
use std::ops::Deref;
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
pub static CREATE_TABLE_SQL: &str = "CREATE TABLE cidrs (
|
pub static CREATE_TABLE_SQL: &str = "CREATE TABLE cidrs (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
|
@ -35,6 +35,12 @@ impl Deref for DatabaseCidr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DerefMut for DatabaseCidr {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DatabaseCidr {
|
impl DatabaseCidr {
|
||||||
pub fn create(conn: &Connection, contents: CidrContents) -> Result<Cidr, ServerError> {
|
pub fn create(conn: &Connection, contents: CidrContents) -> Result<Cidr, ServerError> {
|
||||||
let CidrContents { name, cidr, parent } = &contents;
|
let CidrContents { name, cidr, parent } = &contents;
|
||||||
|
@ -106,6 +112,23 @@ impl DatabaseCidr {
|
||||||
Ok(Cidr { id, contents })
|
Ok(Cidr { id, contents })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update self with new contents, validating them and updating the backend in the process.
|
||||||
|
/// Currently this only supports updating the name and ignores changes to any other field.
|
||||||
|
pub fn update(&mut self, conn: &Connection, contents: CidrContents) -> Result<(), ServerError> {
|
||||||
|
let new_contents = CidrContents {
|
||||||
|
name: contents.name,
|
||||||
|
..self.contents.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE cidrs SET name = ?2 WHERE id = ?1",
|
||||||
|
params![self.id, &*new_contents.name,],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.contents = new_contents;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn delete(conn: &Connection, id: i64) -> Result<(), ServerError> {
|
pub fn delete(conn: &Connection, id: i64) -> Result<(), ServerError> {
|
||||||
conn.execute("DELETE FROM cidrs WHERE id = ?1", params![id])?;
|
conn.execute("DELETE FROM cidrs WHERE id = ?1", params![id])?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -10,7 +10,8 @@ use rusqlite::Connection;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use shared::{
|
use shared::{
|
||||||
get_local_addrs, AddCidrOpts, AddPeerOpts, DeleteCidrOpts, EnableDisablePeerOpts, Endpoint,
|
get_local_addrs, AddCidrOpts, AddPeerOpts, DeleteCidrOpts, EnableDisablePeerOpts, Endpoint,
|
||||||
IoErrorContext, NetworkOpts, PeerContents, RenamePeerOpts, INNERNET_PUBKEY_HEADER,
|
IoErrorContext, NetworkOpts, PeerContents, RenameCidrOpts, RenamePeerOpts,
|
||||||
|
INNERNET_PUBKEY_HEADER,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
|
@ -126,6 +127,14 @@ enum Command {
|
||||||
args: AddCidrOpts,
|
args: AddCidrOpts,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Rename an existing CIDR.
|
||||||
|
RenameCidr {
|
||||||
|
interface: Interface,
|
||||||
|
|
||||||
|
#[clap(flatten)]
|
||||||
|
args: RenameCidrOpts,
|
||||||
|
},
|
||||||
|
|
||||||
/// Delete a CIDR.
|
/// Delete a CIDR.
|
||||||
DeleteCidr {
|
DeleteCidr {
|
||||||
interface: Interface,
|
interface: Interface,
|
||||||
|
@ -288,6 +297,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
enable_or_disable_peer(&interface, &conf, true, opts.network, args)?
|
enable_or_disable_peer(&interface, &conf, true, opts.network, args)?
|
||||||
},
|
},
|
||||||
Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?,
|
Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?,
|
||||||
|
Command::RenameCidr { interface, args } => rename_cidr(&interface, &conf, args)?,
|
||||||
Command::DeleteCidr { interface, args } => delete_cidr(&interface, &conf, args)?,
|
Command::DeleteCidr { interface, args } => delete_cidr(&interface, &conf, args)?,
|
||||||
Command::Completions { shell } => {
|
Command::Completions { shell } => {
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
|
@ -460,6 +470,27 @@ fn add_cidr(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rename_cidr(
|
||||||
|
interface: &InterfaceName,
|
||||||
|
conf: &ServerConfig,
|
||||||
|
opts: RenameCidrOpts,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let conn = open_database_connection(interface, conf)?;
|
||||||
|
let cidrs = DatabaseCidr::list(&conn)?;
|
||||||
|
|
||||||
|
if let Some((cidr_request, old_name)) = shared::prompts::rename_cidr(&cidrs, &opts)? {
|
||||||
|
let db_cidr = DatabaseCidr::list(&conn)?
|
||||||
|
.into_iter()
|
||||||
|
.find(|c| c.name == old_name)
|
||||||
|
.ok_or_else(|| anyhow!("CIDR not found."))?;
|
||||||
|
db::DatabaseCidr::from(db_cidr).update(&conn, cidr_request)?;
|
||||||
|
} else {
|
||||||
|
println!("exited without renaming CIDR.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn delete_cidr(
|
fn delete_cidr(
|
||||||
interface: &InterfaceName,
|
interface: &InterfaceName,
|
||||||
conf: &ServerConfig,
|
conf: &ServerConfig,
|
||||||
|
|
|
@ -2,7 +2,8 @@ use crate::{
|
||||||
interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo},
|
interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo},
|
||||||
AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree,
|
AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree,
|
||||||
DeleteCidrOpts, EnableDisablePeerOpts, Endpoint, Error, Hostname, IpNetExt, ListenPortOpts,
|
DeleteCidrOpts, EnableDisablePeerOpts, Endpoint, Error, Hostname, IpNetExt, ListenPortOpts,
|
||||||
OverrideEndpointOpts, Peer, PeerContents, RenamePeerOpts, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
|
OverrideEndpointOpts, Peer, PeerContents, RenameCidrOpts, RenamePeerOpts,
|
||||||
|
PERSISTENT_KEEPALIVE_INTERVAL_SECS,
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use colored::*;
|
use colored::*;
|
||||||
|
@ -111,6 +112,49 @@ pub fn add_cidr(cidrs: &[Cidr], request: &AddCidrOpts) -> Result<Option<CidrCont
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bring up a prompt to rename an existing CIDR. Returns the CIDR request.
|
||||||
|
pub fn rename_cidr(
|
||||||
|
cidrs: &[Cidr],
|
||||||
|
args: &RenameCidrOpts,
|
||||||
|
) -> Result<Option<(CidrContents, String)>, Error> {
|
||||||
|
let old_cidr = if let Some(ref name) = args.name {
|
||||||
|
cidrs
|
||||||
|
.iter()
|
||||||
|
.find(|c| &c.name == name)
|
||||||
|
.ok_or_else(|| anyhow!("CIDR '{}' does not exist", name))?
|
||||||
|
.clone()
|
||||||
|
} else {
|
||||||
|
let (cidr_index, _) = select(
|
||||||
|
"CIDR to rename",
|
||||||
|
&cidrs.iter().map(|ep| ep.name.clone()).collect::<Vec<_>>(),
|
||||||
|
)?;
|
||||||
|
cidrs[cidr_index].clone()
|
||||||
|
};
|
||||||
|
let old_name = old_cidr.name.clone();
|
||||||
|
let new_name = if let Some(ref name) = args.new_name {
|
||||||
|
name.clone()
|
||||||
|
} else {
|
||||||
|
input("New Name", Prefill::None)?
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut new_cidr = old_cidr;
|
||||||
|
new_cidr.contents.name = new_name.clone();
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
if args.yes
|
||||||
|
|| confirm(&format!(
|
||||||
|
"Rename CIDR {} to {}?",
|
||||||
|
old_name.yellow(),
|
||||||
|
new_name.yellow()
|
||||||
|
))?
|
||||||
|
{
|
||||||
|
Some((new_cidr.contents, old_name))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Bring up a prompt to delete a CIDR. Returns the peer request.
|
/// 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> {
|
pub fn delete_cidr(cidrs: &[Cidr], peers: &[Peer], request: &DeleteCidrOpts) -> Result<i64, Error> {
|
||||||
let eligible_cidrs: Vec<_> = cidrs
|
let eligible_cidrs: Vec<_> = cidrs
|
||||||
|
@ -342,7 +386,7 @@ pub fn add_peer(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bring up a prompt to create a new peer. Returns the peer request.
|
/// Bring up a prompt to rename an existing peer. Returns the peer request.
|
||||||
pub fn rename_peer(
|
pub fn rename_peer(
|
||||||
peers: &[Peer],
|
peers: &[Peer],
|
||||||
args: &RenamePeerOpts,
|
args: &RenamePeerOpts,
|
||||||
|
|
|
@ -381,6 +381,21 @@ pub struct AddCidrOpts {
|
||||||
pub yes: bool,
|
pub yes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Args)]
|
||||||
|
pub struct RenameCidrOpts {
|
||||||
|
/// Name of CIDR to rename
|
||||||
|
#[clap(long)]
|
||||||
|
pub name: Option<String>,
|
||||||
|
|
||||||
|
/// The new name of the CIDR
|
||||||
|
#[clap(long)]
|
||||||
|
pub new_name: Option<String>,
|
||||||
|
|
||||||
|
/// Bypass confirmation
|
||||||
|
#[clap(long)]
|
||||||
|
pub yes: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Args)]
|
#[derive(Debug, Clone, PartialEq, Eq, Args)]
|
||||||
pub struct DeleteCidrOpts {
|
pub struct DeleteCidrOpts {
|
||||||
/// The CIDR name (eg. 'engineers')
|
/// The CIDR name (eg. 'engineers')
|
||||||
|
|
Loading…
Reference in New Issue