src/bin/proxmox-backup-manager.rs: add dns sub command
Also improved the DNS api, added a --delete option.
This commit is contained in:
		| @ -5,6 +5,7 @@ use lazy_static::lazy_static; | ||||
| use openssl::sha; | ||||
| use regex::Regex; | ||||
| use serde_json::{json, Value}; | ||||
| use ::serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; | ||||
| use proxmox::tools::fs::{file_get_contents, replace_file, CreateOptions}; | ||||
| @ -15,6 +16,19 @@ use crate::config::acl::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY}; | ||||
|  | ||||
| static RESOLV_CONF_FN: &str = "/etc/resolv.conf"; | ||||
|  | ||||
| #[api()] | ||||
| #[derive(Serialize, Deserialize)] | ||||
| #[allow(non_camel_case_types)] | ||||
| /// Deletable property name | ||||
| pub enum DeletableProperty { | ||||
|     /// Delete first nameserver entry | ||||
|     dns1, | ||||
|     /// Delete second nameserver entry | ||||
|     dns2, | ||||
|     /// Delete third nameserver entry | ||||
|     dns3, | ||||
| } | ||||
|  | ||||
| pub fn read_etc_resolv_conf() -> Result<Value, Error> { | ||||
|  | ||||
|     let mut result = json!({}); | ||||
| @ -33,6 +47,8 @@ pub fn read_etc_resolv_conf() -> Result<Value, Error> { | ||||
|             concat!(r"^\s*nameserver\s+(", IPRE!(),  r")\s*")).unwrap(); | ||||
|     } | ||||
|  | ||||
|     let mut options = String::new(); | ||||
|  | ||||
|     for line in data.lines() { | ||||
|  | ||||
|         if let Some(caps) = DOMAIN_REGEX.captures(&line) { | ||||
| @ -43,9 +59,16 @@ pub fn read_etc_resolv_conf() -> Result<Value, Error> { | ||||
|             let nameserver = &caps[1]; | ||||
|             let id = format!("dns{}", nscount); | ||||
|             result[id] = Value::from(nameserver); | ||||
|         } else { | ||||
|             if !options.is_empty() { options.push('\n'); } | ||||
|             options.push_str(line); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if !options.is_empty() { | ||||
|         result["options"] = options.into(); | ||||
|     } | ||||
|  | ||||
|     Ok(result) | ||||
| } | ||||
|  | ||||
| @ -59,6 +82,7 @@ pub fn read_etc_resolv_conf() -> Result<Value, Error> { | ||||
|             }, | ||||
|             search: { | ||||
|                 schema: SEARCH_DOMAIN_SCHEMA, | ||||
|                 optional: true, | ||||
|             }, | ||||
|             dns1: { | ||||
|                 optional: true, | ||||
| @ -72,6 +96,14 @@ pub fn read_etc_resolv_conf() -> Result<Value, Error> { | ||||
|                 optional: true, | ||||
|                 schema: THIRD_DNS_SERVER_SCHEMA, | ||||
|             }, | ||||
|             delete: { | ||||
|                 description: "List of properties to delete.", | ||||
|                 type: Array, | ||||
|                 optional: true, | ||||
|                 items: { | ||||
|                     type: DeletableProperty, | ||||
|                 } | ||||
|             }, | ||||
|             digest: { | ||||
|                 optional: true, | ||||
|                 schema: PROXMOX_CONFIG_DIGEST_SCHEMA, | ||||
| @ -83,10 +115,13 @@ pub fn read_etc_resolv_conf() -> Result<Value, Error> { | ||||
|     } | ||||
| )] | ||||
| /// Update DNS settings | ||||
| fn update_dns( | ||||
|     param: Value, | ||||
|     _info: &ApiMethod, | ||||
|     _rpcenv: &mut dyn RpcEnvironment, | ||||
| pub fn update_dns( | ||||
|     search: Option<String>, | ||||
|     dns1: Option<String>, | ||||
|     dns2: Option<String>, | ||||
|     dns3: Option<String>, | ||||
|     delete: Option<Vec<DeletableProperty>>, | ||||
|     digest: Option<String>, | ||||
| ) -> Result<Value, Error> { | ||||
|  | ||||
|     lazy_static! { | ||||
| @ -95,33 +130,41 @@ fn update_dns( | ||||
|  | ||||
|     let _guard = MUTEX.lock(); | ||||
|  | ||||
|     let search = crate::tools::required_string_param(¶m, "search")?; | ||||
|     let mut config = read_etc_resolv_conf()?; | ||||
|     let old_digest = config["digest"].as_str().unwrap(); | ||||
|  | ||||
|     let raw = file_get_contents(RESOLV_CONF_FN)?; | ||||
|     let old_digest = proxmox::tools::digest_to_hex(&sha::sha256(&raw)); | ||||
|  | ||||
|     if let Some(digest) = param["digest"].as_str() { | ||||
|         crate::tools::assert_if_modified(&old_digest, &digest)?; | ||||
|     if let Some(digest) = digest { | ||||
|         crate::tools::assert_if_modified(old_digest, &digest)?; | ||||
|     } | ||||
|  | ||||
|     let old_data = String::from_utf8(raw)?; | ||||
|  | ||||
|     let mut data = format!("search {}\n", search); | ||||
|  | ||||
|     for opt in &["dns1", "dns2", "dns3"] { | ||||
|         if let Some(server) = param[opt].as_str() { | ||||
|             data.push_str(&format!("nameserver {}\n", server)); | ||||
|     if let Some(delete) = delete { | ||||
|         for delete_prop in delete { | ||||
|             let config = config.as_object_mut().unwrap(); | ||||
|             match delete_prop { | ||||
|                 DeletableProperty::dns1 => { config.remove("dns1"); }, | ||||
|                 DeletableProperty::dns2 => { config.remove("dns2"); }, | ||||
|                 DeletableProperty::dns3 => { config.remove("dns3"); }, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // append other data | ||||
|     lazy_static! { | ||||
|         static ref SKIP_REGEX: Regex = Regex::new(r"^(search|domain|nameserver)\s+").unwrap(); | ||||
|     if let Some(search) = search { config["search"] = search.into(); } | ||||
|     if let Some(dns1) = dns1 { config["dns1"] = dns1.into(); } | ||||
|     if let Some(dns2) = dns2 { config["dns2"] = dns2.into(); } | ||||
|     if let Some(dns3) = dns3 { config["dns3"] = dns3.into(); } | ||||
|  | ||||
|     let mut data = String::new(); | ||||
|  | ||||
|     if let Some(search) = config["search"].as_str() { | ||||
|         data.push_str(&format!("search {}\n", search)); | ||||
|     } | ||||
|     for line in old_data.lines() { | ||||
|         if SKIP_REGEX.is_match(line) { continue; } | ||||
|         data.push_str(line); | ||||
|         data.push('\n'); | ||||
|     for opt in &["dns1", "dns2", "dns3"] { | ||||
|         if let Some(server) = config[opt].as_str() { | ||||
|             data.push_str(&format!("nameserver {}\n", server)); | ||||
|         } | ||||
|     } | ||||
|     if let Some(options) = config["options"].as_str() { | ||||
|         data.push_str(options); | ||||
|     } | ||||
|  | ||||
|     replace_file(RESOLV_CONF_FN, data.as_bytes(), CreateOptions::new())?; | ||||
| @ -167,7 +210,7 @@ fn update_dns( | ||||
|     } | ||||
| )] | ||||
| /// Read DNS settings. | ||||
| fn get_dns( | ||||
| pub fn get_dns( | ||||
|     _param: Value, | ||||
|     _info: &ApiMethod, | ||||
|     _rpcenv: &mut dyn RpcEnvironment, | ||||
|  | ||||
| @ -365,6 +365,57 @@ fn network_commands() -> CommandLineInterface { | ||||
|     cmd_def.into() | ||||
| } | ||||
|  | ||||
| #[api( | ||||
|     input: { | ||||
|         properties: { | ||||
|             "output-format": { | ||||
|                 schema: OUTPUT_FORMAT, | ||||
|                 optional: true, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| )] | ||||
| /// Read DNS settings | ||||
| fn get_dns(mut param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result<Value, Error> { | ||||
|  | ||||
|     let output_format = get_output_format(¶m); | ||||
|  | ||||
|     param["node"] = "localhost".into(); | ||||
|  | ||||
|     let info = &api2::node::dns::API_METHOD_GET_DNS; | ||||
|     let mut data = match info.handler { | ||||
|         ApiHandler::Sync(handler) => (handler)(param, info, rpcenv)?, | ||||
|         _ => unreachable!(), | ||||
|     }; | ||||
|  | ||||
|  | ||||
|     let options = default_table_format_options() | ||||
|         .column(ColumnConfig::new("search")) | ||||
|         .column(ColumnConfig::new("dns1")) | ||||
|         .column(ColumnConfig::new("dns2")) | ||||
|         .column(ColumnConfig::new("dns3")); | ||||
|  | ||||
|     format_and_print_result_full(&mut data, info.returns, &output_format, &options); | ||||
|  | ||||
|     Ok(Value::Null) | ||||
| } | ||||
|  | ||||
| fn dns_commands() -> CommandLineInterface { | ||||
|  | ||||
|     let cmd_def = CliCommandMap::new() | ||||
|         .insert( | ||||
|             "get", | ||||
|             CliCommand::new(&API_METHOD_GET_DNS) | ||||
|         ) | ||||
|         .insert( | ||||
|             "set", | ||||
|             CliCommand::new(&api2::node::dns::API_METHOD_UPDATE_DNS) | ||||
|                 .fixed_param("node", String::from("localhost")) | ||||
|         ); | ||||
|  | ||||
|     cmd_def.into() | ||||
| } | ||||
|  | ||||
| #[api( | ||||
|     input: { | ||||
|         properties: { | ||||
| @ -766,6 +817,7 @@ fn main() { | ||||
|     let cmd_def = CliCommandMap::new() | ||||
|         .insert("acl", acl_commands()) | ||||
|         .insert("datastore", datastore_commands()) | ||||
|         .insert("dns", dns_commands()) | ||||
|         .insert("network", network_commands()) | ||||
|         .insert("user", user_commands()) | ||||
|         .insert("remote", remote_commands()) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user