diff --git a/client/src/main.rs b/client/src/main.rs index 9e396be..98e5627 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -5,8 +5,8 @@ use indoc::eprintdoc; use shared::{ interface_config::InterfaceConfig, prompts, AddAssociationOpts, AddCidrOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree, DeleteCidrOpts, EndpointContents, - InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, RedeemContents, State, - CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT, + InstallOpts, Interface, IoErrorContext, NetworkOpt, Peer, RedeemContents, RenamePeerOpts, + State, CLIENT_CONFIG_DIR, REDEEM_TRANSITION_WAIT, }; use std::{ fmt, @@ -140,6 +140,19 @@ enum Command { opts: AddPeerOpts, }, + /// Rename a peer. + /// + /// By default, you'll be prompted interactively to select a peer, but you can + /// also specify all the options in the command, eg: + /// + /// --name "person" --new-name "human" + RenamePeer { + interface: Interface, + + #[structopt(flatten)] + opts: RenamePeerOpts, + }, + /// Add a new CIDR. AddCidr { interface: Interface, @@ -618,6 +631,32 @@ fn add_peer(interface: &InterfaceName, opts: AddPeerOpts) -> Result<(), Error> { Ok(()) } +fn rename_peer(interface: &InterfaceName, opts: RenamePeerOpts) -> Result<(), Error> { + let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?; + let api = Api::new(&server); + + log::info!("Fetching peers"); + let peers: Vec = api.http("GET", "/admin/peers")?; + + if let Some((peer_request, old_name)) = prompts::rename_peer(&peers, &opts)? { + log::info!("Renaming peer..."); + + let id = peers + .iter() + .filter(|p| p.name == old_name) + .map(|p| p.id) + .next() + .ok_or_else(|| "Peer not found.")?; + + let _ = api.http_form("PUT", &format!("/admin/peers/{}", id), peer_request)?; + log::info!("Peer renamed."); + } else { + log::info!("exited without renaming peer."); + } + + Ok(()) +} + fn enable_or_disable_peer(interface: &InterfaceName, enable: bool) -> Result<(), Error> { let InterfaceConfig { server, .. } = InterfaceConfig::from_interface(interface)?; let api = Api::new(&server); @@ -988,6 +1027,7 @@ fn run(opt: Opts) -> Result<(), Error> { Command::Down { interface } => wg::down(&interface, opt.network.backend)?, Command::Uninstall { interface } => uninstall(&interface, opt.network)?, Command::AddPeer { interface, opts } => add_peer(&interface, opts)?, + Command::RenamePeer { interface, opts } => rename_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)?, diff --git a/server/src/main.rs b/server/src/main.rs index 5f218fa..c04a6f3 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -7,7 +7,8 @@ use parking_lot::{Mutex, RwLock}; use rusqlite::Connection; use serde::{Deserialize, Serialize}; use shared::{ - AddCidrOpts, AddPeerOpts, DeleteCidrOpts, IoErrorContext, NetworkOpt, INNERNET_PUBKEY_HEADER, + AddCidrOpts, AddPeerOpts, DeleteCidrOpts, IoErrorContext, NetworkOpt, RenamePeerOpts, + INNERNET_PUBKEY_HEADER, }; use std::{ collections::{HashMap, VecDeque}, @@ -80,6 +81,14 @@ enum Command { args: AddPeerOpts, }, + /// Rename an existing peer. + RenamePeer { + interface: Interface, + + #[structopt(flatten)] + args: RenamePeerOpts, + }, + /// Add a new CIDR to an existing network. AddCidr { interface: Interface, @@ -236,6 +245,7 @@ async fn main() -> Result<(), Box> { network: routing, } => serve(*interface, &conf, routing).await?, Command::AddPeer { interface, args } => add_peer(&interface, &conf, args, opt.network)?, + Command::RenamePeer { interface, args } => rename_peer(&interface, &conf, args)?, Command::AddCidr { interface, args } => add_cidr(&interface, &conf, args)?, Command::DeleteCidr { interface, args } => delete_cidr(&interface, &conf, args)?, Command::Completions { shell } => { @@ -311,6 +321,30 @@ fn add_peer( Ok(()) } +fn rename_peer( + interface: &InterfaceName, + conf: &ServerConfig, + opts: RenamePeerOpts, +) -> Result<(), Error> { + let conn = open_database_connection(interface, conf)?; + let peers = DatabasePeer::list(&conn)? + .into_iter() + .map(|dp| dp.inner) + .collect::>(); + + if let Some((peer_request, old_name)) = shared::prompts::rename_peer(&peers, &opts)? { + let mut db_peer = DatabasePeer::list(&conn)? + .into_iter() + .find(|p| p.name == old_name) + .ok_or_else(|| "Peer not found.")?; + let _peer = db_peer.update(&conn, peer_request)?; + } else { + println!("exited without creating peer."); + } + + Ok(()) +} + fn add_cidr( interface: &InterfaceName, conf: &ServerConfig, diff --git a/shared/src/prompts.rs b/shared/src/prompts.rs index 1e8a320..7120d45 100644 --- a/shared/src/prompts.rs +++ b/shared/src/prompts.rs @@ -1,7 +1,7 @@ use crate::{ interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo}, AddCidrOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, DeleteCidrOpts, Endpoint, - Error, Peer, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS, + Error, Hostname, Peer, PeerContents, RenamePeerOpts, PERSISTENT_KEEPALIVE_INTERVAL_SECS, }; use colored::*; use dialoguer::{theme::ColorfulTheme, Confirm, Input, Select}; @@ -274,6 +274,63 @@ pub fn add_peer( ) } +/// Bring up a prompt to create a new peer. Returns the peer request. +pub fn rename_peer( + peers: &[Peer], + args: &RenamePeerOpts, +) -> Result, Error> { + let eligible_peers = peers + .iter() + .filter(|p| &*p.name != "innernet-server") + .collect::>(); + let old_peer = if let Some(ref name) = args.name { + eligible_peers + .into_iter() + .find(|p| &p.name == name) + .ok_or_else(|| format!("Peer '{}' does not exist", name))? + .clone() + } else { + let peer_index = Select::with_theme(&*THEME) + .with_prompt("Peer to rename") + .items( + &eligible_peers + .iter() + .map(|ep| ep.name.clone()) + .collect::>(), + ) + .interact()?; + eligible_peers[peer_index].clone() + }; + let old_name = old_peer.name.clone(); + let new_name = if let Some(ref name) = args.new_name { + name.clone() + } else { + Input::with_theme(&*THEME) + .with_prompt("New Name") + .interact()? + }; + + let mut new_peer = old_peer; + new_peer.contents.name = new_name.clone(); + + Ok( + if args.yes + || Confirm::with_theme(&*THEME) + .with_prompt(&format!( + "Rename peer {} to {}?", + old_name.yellow(), + new_name.yellow() + )) + .default(false) + .interact()? + { + Some((new_peer.contents, old_name)) + } else { + None + }, + ) +} + /// Presents a selection and confirmation of eligible peers for either disabling or enabling, /// and returns back the ID of the selected peer. pub fn enable_or_disable_peer(peers: &[Peer], enable: bool) -> Result, Error> { diff --git a/shared/src/types.rs b/shared/src/types.rs index 19c111a..4c19909 100644 --- a/shared/src/types.rs +++ b/shared/src/types.rs @@ -318,6 +318,21 @@ pub struct AddPeerOpts { pub invite_expires: Option, } +#[derive(Debug, Clone, PartialEq, StructOpt)] +pub struct RenamePeerOpts { + /// Name of peer to rename + #[structopt(long)] + pub name: Option, + + /// The new name of the peer + #[structopt(long)] + pub new_name: Option, + + /// Bypass confirmation + #[structopt(long)] + pub yes: bool, +} + #[derive(Debug, Clone, PartialEq, StructOpt)] pub struct AddCidrOpts { /// The CIDR name (eg. "engineers")