Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new client / server command to rename CIDR #310

Merged
merged 3 commits into from Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 48 additions & 1 deletion client/src/main.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -724,6 +737,36 @@ 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<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(
interface: &InterfaceName,
opts: &Opts,
Expand Down Expand Up @@ -1251,6 +1294,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,
Expand Down
24 changes: 24 additions & 0 deletions docker-tests/run-docker-tests.sh
Expand Up @@ -201,6 +201,30 @@ test_simultaneous_redemption() {
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.
# Optional filter provided by positional arguments is applied.
for func in $(declare -F | awk '{print $3}'); do
Expand Down
17 changes: 17 additions & 0 deletions server/src/api/admin/cidr.rs
Expand Up @@ -19,6 +19,11 @@ pub async fn routes(
let form = form_body(req).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)) => {
let id: i64 = id.parse().map_err(|_| ServerError::NotFound)?;
handlers::delete(id, session).await
Expand All @@ -43,6 +48,18 @@ mod handlers {
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> {
let conn = session.context.db.lock();
let cidrs = DatabaseCidr::list(&conn)?;
Expand Down
25 changes: 24 additions & 1 deletion server/src/db/cidr.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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<Cidr, ServerError> {
let CidrContents { name, cidr, parent } = &contents;
Expand Down Expand Up @@ -106,6 +112,23 @@ impl DatabaseCidr {
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> {
conn.execute("DELETE FROM cidrs WHERE id = ?1", params![id])?;
Ok(())
Expand Down
33 changes: 32 additions & 1 deletion server/src/main.rs
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -288,6 +297,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
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;
Expand Down Expand Up @@ -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 renaming CIDR.");
}

Ok(())
}

fn delete_cidr(
interface: &InterfaceName,
conf: &ServerConfig,
Expand Down
48 changes: 46 additions & 2 deletions shared/src/prompts.rs
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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.
pub fn delete_cidr(cidrs: &[Cidr], peers: &[Peer], request: &DeleteCidrOpts) -> Result<i64, Error> {
let eligible_cidrs: Vec<_> = cidrs
Expand Down Expand Up @@ -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.
skywhale marked this conversation as resolved.
Show resolved Hide resolved
pub fn rename_peer(
peers: &[Peer],
args: &RenamePeerOpts,
Expand Down
15 changes: 15 additions & 0 deletions shared/src/types.rs
Expand Up @@ -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<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)]
pub struct DeleteCidrOpts {
/// The CIDR name (eg. 'engineers')
Expand Down