src/config/network.rs: implement network reload, set "changes" attribute
This commit is contained in:
		@ -27,7 +27,7 @@ use crate::api2::types::*;
 | 
				
			|||||||
pub fn list_network_devices(
 | 
					pub fn list_network_devices(
 | 
				
			||||||
    _param: Value,
 | 
					    _param: Value,
 | 
				
			||||||
    _info: &ApiMethod,
 | 
					    _info: &ApiMethod,
 | 
				
			||||||
    _rpcenv: &mut dyn RpcEnvironment,
 | 
					    rpcenv: &mut dyn RpcEnvironment,
 | 
				
			||||||
) -> Result<Value, Error> {
 | 
					) -> Result<Value, Error> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (config, digest) = network::config()?;
 | 
					    let (config, digest) = network::config()?;
 | 
				
			||||||
@ -41,6 +41,11 @@ pub fn list_network_devices(
 | 
				
			|||||||
        list.push(item);
 | 
					        list.push(item);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let diff = network::changes()?;
 | 
				
			||||||
 | 
					    if !diff.is_empty() {
 | 
				
			||||||
 | 
					        rpcenv.set_result_attrib("changes",  diff.into());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(list.into())
 | 
					    Ok(list.into())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -312,6 +317,23 @@ pub fn delete_interface(name: String, digest: Option<String>) -> Result<(), Erro
 | 
				
			|||||||
    Ok(())
 | 
					    Ok(())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[api(
 | 
				
			||||||
 | 
					    access: {
 | 
				
			||||||
 | 
					        permission: &Permission::Privilege(&[], PRIV_SYS_MODIFY, false),
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					)]
 | 
				
			||||||
 | 
					/// Reload network configuration (requires ifupdown2).
 | 
				
			||||||
 | 
					pub fn reload_network_config() -> Result<(), Error> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    network::assert_ifupdown2_installed()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let _ = std::fs::rename(network::NETWORK_INTERFACES_NEW_FILENAME, network::NETWORK_INTERFACES_FILENAME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    network::network_reload()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ITEM_ROUTER: Router = Router::new()
 | 
					const ITEM_ROUTER: Router = Router::new()
 | 
				
			||||||
    .get(&API_METHOD_READ_INTERFACE)
 | 
					    .get(&API_METHOD_READ_INTERFACE)
 | 
				
			||||||
    .put(&API_METHOD_UPDATE_INTERFACE)
 | 
					    .put(&API_METHOD_UPDATE_INTERFACE)
 | 
				
			||||||
@ -319,4 +341,5 @@ const ITEM_ROUTER: Router = Router::new()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub const ROUTER: Router = Router::new()
 | 
					pub const ROUTER: Router = Router::new()
 | 
				
			||||||
    .get(&API_METHOD_LIST_NETWORK_DEVICES)
 | 
					    .get(&API_METHOD_LIST_NETWORK_DEVICES)
 | 
				
			||||||
 | 
					    .put(&API_METHOD_RELOAD_NETWORK_CONFIG)
 | 
				
			||||||
    .match_all("name", &ITEM_ROUTER);
 | 
					    .match_all("name", &ITEM_ROUTER);
 | 
				
			||||||
 | 
				
			|||||||
@ -254,6 +254,12 @@ fn list_network_devices(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result
 | 
				
			|||||||
        _ => unreachable!(),
 | 
					        _ => unreachable!(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if let Some(changes) = rpcenv.get_result_attrib("changes") {
 | 
				
			||||||
 | 
					        if let Some(diff) = changes.as_str() {
 | 
				
			||||||
 | 
					            eprintln!("pending changes:\n{}\n", diff);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn render_address(_value: &Value, record: &Value) -> Result<String, Error> {
 | 
					    fn render_address(_value: &Value, record: &Value) -> Result<String, Error> {
 | 
				
			||||||
        let mut text = String::new();
 | 
					        let mut text = String::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -300,15 +306,21 @@ fn network_commands() -> CommandLineInterface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let cmd_def = CliCommandMap::new()
 | 
					    let cmd_def = CliCommandMap::new()
 | 
				
			||||||
        .insert("list", CliCommand::new(&API_METHOD_LIST_NETWORK_DEVICES))
 | 
					        .insert("list", CliCommand::new(&API_METHOD_LIST_NETWORK_DEVICES))
 | 
				
			||||||
        .insert("update",
 | 
					        .insert(
 | 
				
			||||||
                CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE)
 | 
					            "update",
 | 
				
			||||||
 | 
					            CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE)
 | 
				
			||||||
                .arg_param(&["name"])
 | 
					                .arg_param(&["name"])
 | 
				
			||||||
                .completion_cb("name", config::network::complete_interface_name)
 | 
					                .completion_cb("name", config::network::complete_interface_name)
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .insert("remove",
 | 
					        .insert(
 | 
				
			||||||
                CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE)
 | 
					            "remove",
 | 
				
			||||||
 | 
					            CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE)
 | 
				
			||||||
                .arg_param(&["name"])
 | 
					                .arg_param(&["name"])
 | 
				
			||||||
                .completion_cb("name", config::network::complete_interface_name)
 | 
					                .completion_cb("name", config::network::complete_interface_name)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .insert(
 | 
				
			||||||
 | 
					            "reload",
 | 
				
			||||||
 | 
					            CliCommand::new(&api2::config::network::API_METHOD_RELOAD_NETWORK_CONFIG)
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cmd_def.into()
 | 
					    cmd_def.into()
 | 
				
			||||||
 | 
				
			|||||||
@ -387,6 +387,15 @@ pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> {
 | 
				
			|||||||
    Ok((data, digest))
 | 
					    Ok((data, digest))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn changes() -> Result<String, Error> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !std::path::Path::new(NETWORK_INTERFACES_NEW_FILENAME).exists() {
 | 
				
			||||||
 | 
					        return Ok(String::new());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    compute_file_diff(NETWORK_INTERFACES_FILENAME, NETWORK_INTERFACES_NEW_FILENAME)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn save_config(config: &NetworkConfig) -> Result<(), Error> {
 | 
					pub fn save_config(config: &NetworkConfig) -> Result<(), Error> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut raw = Vec::new();
 | 
					    let mut raw = Vec::new();
 | 
				
			||||||
 | 
				
			|||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					use std::path::Path;
 | 
				
			||||||
 | 
					use std::process::Command;
 | 
				
			||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::{Error, bail, format_err};
 | 
					use anyhow::{Error, bail, format_err};
 | 
				
			||||||
@ -136,3 +138,59 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Ok(interface_list)
 | 
					    Ok(interface_list)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn compute_file_diff(filename: &str, shadow: &str) -> Result<String, Error> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let output = Command::new("/usr/bin/diff")
 | 
				
			||||||
 | 
					        .arg("-b")
 | 
				
			||||||
 | 
					        .arg("-u")
 | 
				
			||||||
 | 
					        .arg(filename)
 | 
				
			||||||
 | 
					        .arg(shadow)
 | 
				
			||||||
 | 
					        .output()
 | 
				
			||||||
 | 
					        .map_err(|err| format_err!("failed to execute diff - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !output.status.success() {
 | 
				
			||||||
 | 
					        match output.status.code() {
 | 
				
			||||||
 | 
					            Some(code) => {
 | 
				
			||||||
 | 
					                if code == 0 { return Ok(String::new()); }
 | 
				
			||||||
 | 
					                if code != 1 {
 | 
				
			||||||
 | 
					                    let msg = String::from_utf8(output.stderr)
 | 
				
			||||||
 | 
					                        .map(|m| if m.is_empty() { String::from("no error message") } else { m })
 | 
				
			||||||
 | 
					                        .unwrap_or_else(|_| String::from("non utf8 error message (suppressed)"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                   bail!("diff failed with status code: {} - {}", code, msg);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            None => bail!("diff terminated by signal"),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let diff = String::from_utf8(output.stdout)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(diff)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn assert_ifupdown2_installed() -> Result<(), Error> {
 | 
				
			||||||
 | 
					    if !Path::new("/usr/share/ifupdown2").exists() {
 | 
				
			||||||
 | 
					        bail!("ifupdown2 is not installed.");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn network_reload() -> Result<(), Error> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let status = Command::new("/sbin/ifreload")
 | 
				
			||||||
 | 
					        .arg("-a")
 | 
				
			||||||
 | 
					        .status()
 | 
				
			||||||
 | 
					        .map_err(|err| format_err!("failed to execute ifreload: - {}", err))?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !status.success() {
 | 
				
			||||||
 | 
					        match status.code() {
 | 
				
			||||||
 | 
					            Some(code) => bail!("ifreload failed with status code: {}", code),
 | 
				
			||||||
 | 
					            None => bail!("ifreload terminated by signal")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user