diff --git a/src/api2/config/network.rs b/src/api2/config/network.rs index 6f6de282..69df0670 100644 --- a/src/api2/config/network.rs +++ b/src/api2/config/network.rs @@ -5,8 +5,8 @@ use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; //use crate::api2::types::*; use crate::config::network; -use crate::config::acl::{PRIV_SYS_AUDIT}; -use crate::api2::types::Interface; +use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; +use crate::api2::types::*; #[api( input: { @@ -44,5 +44,145 @@ pub fn list_network_devices( Ok(list.into()) } +#[api( + input: { + properties: { + name: { + schema: NETWORK_INTERFACE_NAME_SCHEMA, + }, + }, + }, + returns: { + description: "The network interface configuration (with config digest).", + type: Interface, + }, + access: { + permission: &Permission::Privilege(&[], PRIV_SYS_AUDIT, false), + }, +)] +/// Read a network interface configuration. +pub fn read_interface(name: String) -> Result { + + let (config, digest) = network::config()?; + + let interface = config.lookup(&name)?; + + let mut data: Value = to_value(interface)?; + data["digest"] = proxmox::tools::digest_to_hex(&digest).into(); + + Ok(data) +} + +#[api( + protected: true, + input: { + properties: { + name: { + schema: NETWORK_INTERFACE_NAME_SCHEMA, + }, + address: { + schema: CIDR_SCHEMA, + optional: true, + }, + gateway: { + schema: IP_SCHEMA, + optional: true, + }, + digest: { + optional: true, + schema: PROXMOX_CONFIG_DIGEST_SCHEMA, + }, + }, + }, + access: { + permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), + }, +)] +/// Update network interface config. +pub fn update_interface( + name: String, + address: Option, + gateway: Option, + digest: Option, +) -> Result<(), Error> { + + let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?; + + let (mut config, expected_digest) = network::config()?; + + if let Some(ref digest) = digest { + let digest = proxmox::tools::hex_to_digest(digest)?; + crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; + } + + let interface = config.lookup_mut(&name)?; + + if let Some(address) = address { + let (_, _, is_v6) = network::parse_cidr(&address)?; + if is_v6 { + interface.cidr_v6 = Some(address); + } else { + interface.cidr_v4 = Some(address); + } + } + + if let Some(gateway) = gateway { + let is_v6 = gateway.contains(':'); + if is_v6 { + interface.gateway_v6 = Some(gateway); + } else { + interface.gateway_v4 = Some(gateway); + } + } + + network::save_config(&config)?; + + Ok(()) +} + +#[api( + protected: true, + input: { + properties: { + name: { + schema: NETWORK_INTERFACE_NAME_SCHEMA, + }, + digest: { + optional: true, + schema: PROXMOX_CONFIG_DIGEST_SCHEMA, + }, + }, + }, + access: { + permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false), + }, +)] +/// Remove network interface configuration. +pub fn delete_interface(name: String, digest: Option) -> Result<(), Error> { + + let _lock = crate::tools::open_file_locked(network::NETWORK_LOCKFILE, std::time::Duration::new(10, 0))?; + + let (mut config, expected_digest) = network::config()?; + + if let Some(ref digest) = digest { + let digest = proxmox::tools::hex_to_digest(digest)?; + crate::tools::detect_modified_configuration_file(&digest, &expected_digest)?; + } + + let _interface = config.lookup(&name)?; // check if interface exists + + config.interfaces.remove(&name); + + network::save_config(&config)?; + + Ok(()) +} + +const ITEM_ROUTER: Router = Router::new() + .get(&API_METHOD_READ_INTERFACE) + .put(&API_METHOD_UPDATE_INTERFACE) + .delete(&API_METHOD_DELETE_INTERFACE); + pub const ROUTER: Router = Router::new() - .get(&API_METHOD_LIST_NETWORK_DEVICES); + .get(&API_METHOD_LIST_NETWORK_DEVICES) + .match_all("name", &ITEM_ROUTER); diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index 0f8164d3..4a2904b3 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -236,7 +236,17 @@ fn acl_commands() -> CommandLineInterface { fn network_commands() -> CommandLineInterface { let cmd_def = CliCommandMap::new() - .insert("list", CliCommand::new(&api2::config::network::API_METHOD_LIST_NETWORK_DEVICES)); + .insert("list", CliCommand::new(&api2::config::network::API_METHOD_LIST_NETWORK_DEVICES)) + .insert("update", + CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE) + .arg_param(&["name"]) + .completion_cb("name", config::network::complete_interface_name) + ) + .insert("remove", + CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE) + .arg_param(&["name"]) + .completion_cb("name", config::network::complete_interface_name) + ); cmd_def.into() } diff --git a/src/config/network.rs b/src/config/network.rs index c493a26f..bb5ef30e 100644 --- a/src/config/network.rs +++ b/src/config/network.rs @@ -1,7 +1,7 @@ use std::io::{Write}; use std::collections::{HashSet, HashMap}; -use anyhow::{Error, bail}; +use anyhow::{Error, format_err, bail}; use proxmox::tools::{fs::replace_file, fs::CreateOptions}; @@ -184,6 +184,20 @@ impl NetworkConfig { } } + pub fn lookup(&self, name: &str) -> Result<&Interface, Error> { + let interface = self.interfaces.get(name).ok_or_else(|| { + format_err!("interface '{}' does not exist.", name) + })?; + Ok(interface) + } + + pub fn lookup_mut(&mut self, name: &str) -> Result<&mut Interface, Error> { + let interface = self.interfaces.get_mut(name).ok_or_else(|| { + format_err!("interface '{}' does not exist.", name) + })?; + Ok(interface) + } + pub fn write_config(&self, w: &mut dyn Write) -> Result<(), Error> { let mut done = HashSet::new(); @@ -267,3 +281,11 @@ pub fn save_config(config: &NetworkConfig) -> Result<(), Error> { Ok(()) } + +// shell completion helper +pub fn complete_interface_name(_arg: &str, _param: &HashMap) -> Vec { + match config() { + Ok((data, _digest)) => data.interfaces.keys().map(|id| id.to_string()).collect(), + Err(_) => return vec![], + } +}