From 3c57bad133c58efea1b6673611688d15bd257d1e Mon Sep 17 00:00:00 2001 From: Ryo Kawaguchi Date: Thu, 18 Apr 2024 20:50:50 +0900 Subject: [PATCH] Add a new client / server command to rename CIDR. --- client/src/main.rs | 50 ++++++++++++++++++++++++++++++++++++++++++- server/src/db/cidr.rs | 24 ++++++++++++++++++++- server/src/main.rs | 33 +++++++++++++++++++++++++++- shared/src/prompts.rs | 48 +++++++++++++++++++++++++++++++++++++++-- shared/src/types.rs | 15 +++++++++++++ 5 files changed, 165 insertions(+), 5 deletions(-) diff --git a/client/src/main.rs b/client/src/main.rs index 70b7c093..0d529262 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -12,7 +12,7 @@ use shared::{ AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, AssociationContents, Cidr, CidrTree, DeleteCidrOpts, EnableDisablePeerOpts, Endpoint, EndpointContents, InstallOpts, Interface, IoErrorContext, ListenPortOpts, NatOpts, NetworkOpts, OverrideEndpointOpts, Peer, - RedeemContents, RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT, + RedeemContents, RenameCidrOpts, RenamePeerOpts, State, WrappedIoError, REDEEM_TRANSITION_WAIT, }; use std::{ fmt, io, @@ -193,6 +193,19 @@ enum Command { 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 DeleteCidr { interface: Interface, @@ -724,6 +737,37 @@ fn add_cidr(interface: &InterfaceName, opts: &Opts, sub_opts: AddCidrOpts) -> Re 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 = 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() + .filter(|c| c.name == old_name) + .map(|c| c.id) + .next() + .ok_or_else(|| anyhow!("CIDR not found."))?; + + 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( interface: &InterfaceName, opts: &Opts, @@ -1251,6 +1295,10 @@ fn run(opts: &Opts) -> Result<(), Error> { interface, sub_opts, } => add_cidr(&interface, opts, sub_opts)?, + Command::RenameCidr { + interface, + sub_opts, + } => rename_cidr(&interface, opts, sub_opts)?, Command::DeleteCidr { interface, sub_opts, diff --git a/server/src/db/cidr.rs b/server/src/db/cidr.rs index 924f0549..ec1a9354 100644 --- a/server/src/db/cidr.rs +++ b/server/src/db/cidr.rs @@ -2,7 +2,7 @@ use crate::ServerError; use ipnet::IpNet; use rusqlite::{params, Connection}; use shared::{Cidr, CidrContents}; -use std::ops::Deref; +use std::ops::{Deref, DerefMut}; pub static CREATE_TABLE_SQL: &str = "CREATE TABLE cidrs ( 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 { pub fn create(conn: &Connection, contents: CidrContents) -> Result { let CidrContents { name, cidr, parent } = &contents; @@ -106,6 +112,22 @@ impl DatabaseCidr { Ok(Cidr { id, contents }) } + /// Update self with new contents, validating them and updating the backend in the process. + 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> { conn.execute("DELETE FROM cidrs WHERE id = ?1", params![id])?; Ok(()) diff --git a/server/src/main.rs b/server/src/main.rs index d34949ce..cb82ff62 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -10,7 +10,8 @@ use rusqlite::Connection; use serde::{Deserialize, Serialize}; use shared::{ 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::{ collections::{HashMap, VecDeque}, @@ -126,6 +127,14 @@ enum Command { args: AddCidrOpts, }, + /// Rename an existing CIDR. + RenameCidr { + interface: Interface, + + #[clap(flatten)] + args: RenameCidrOpts, + }, + /// Delete a CIDR. DeleteCidr { interface: Interface, @@ -288,6 +297,7 @@ async fn main() -> Result<(), Box> { enable_or_disable_peer(&interface, &conf, true, opts.network, 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::Completions { shell } => { use clap::CommandFactory; @@ -460,6 +470,27 @@ fn add_cidr( 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 creating CIDR."); + } + + Ok(()) +} + fn delete_cidr( interface: &InterfaceName, conf: &ServerConfig, diff --git a/shared/src/prompts.rs b/shared/src/prompts.rs index b2b42fea..238249a1 100644 --- a/shared/src/prompts.rs +++ b/shared/src/prompts.rs @@ -2,7 +2,8 @@ use crate::{ interface_config::{InterfaceConfig, InterfaceInfo, ServerInfo}, AddCidrOpts, AddDeleteAssociationOpts, AddPeerOpts, Association, Cidr, CidrContents, CidrTree, 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 colored::*; @@ -111,6 +112,49 @@ pub fn add_cidr(cidrs: &[Cidr], request: &AddCidrOpts) -> Result Result, 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::>(), + )?; + 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. pub fn delete_cidr(cidrs: &[Cidr], peers: &[Peer], request: &DeleteCidrOpts) -> Result { 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( peers: &[Peer], args: &RenamePeerOpts, diff --git a/shared/src/types.rs b/shared/src/types.rs index f482d92a..cd8f156b 100644 --- a/shared/src/types.rs +++ b/shared/src/types.rs @@ -381,6 +381,21 @@ pub struct AddCidrOpts { pub yes: bool, } +#[derive(Debug, Clone, PartialEq, Eq, Args)] +pub struct RenameCidrOpts { + /// Name of CIDR to rename + #[clap(long)] + pub name: Option, + + /// The new name of the CIDR + #[clap(long)] + pub new_name: Option, + + /// Bypass confirmation + #[clap(long)] + pub yes: bool, +} + #[derive(Debug, Clone, PartialEq, Eq, Args)] pub struct DeleteCidrOpts { /// The CIDR name (eg. 'engineers')