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(
 | 
			
		||||
    _param: Value,
 | 
			
		||||
    _info: &ApiMethod,
 | 
			
		||||
    _rpcenv: &mut dyn RpcEnvironment,
 | 
			
		||||
    rpcenv: &mut dyn RpcEnvironment,
 | 
			
		||||
) -> Result<Value, Error> {
 | 
			
		||||
 | 
			
		||||
    let (config, digest) = network::config()?;
 | 
			
		||||
@ -41,6 +41,11 @@ pub fn list_network_devices(
 | 
			
		||||
        list.push(item);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let diff = network::changes()?;
 | 
			
		||||
    if !diff.is_empty() {
 | 
			
		||||
        rpcenv.set_result_attrib("changes",  diff.into());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(list.into())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -312,6 +317,23 @@ pub fn delete_interface(name: String, digest: Option<String>) -> Result<(), Erro
 | 
			
		||||
    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()
 | 
			
		||||
    .get(&API_METHOD_READ_INTERFACE)
 | 
			
		||||
    .put(&API_METHOD_UPDATE_INTERFACE)
 | 
			
		||||
@ -319,4 +341,5 @@ const ITEM_ROUTER: Router = Router::new()
 | 
			
		||||
 | 
			
		||||
pub const ROUTER: Router = Router::new()
 | 
			
		||||
    .get(&API_METHOD_LIST_NETWORK_DEVICES)
 | 
			
		||||
    .put(&API_METHOD_RELOAD_NETWORK_CONFIG)
 | 
			
		||||
    .match_all("name", &ITEM_ROUTER);
 | 
			
		||||
 | 
			
		||||
@ -254,6 +254,12 @@ fn list_network_devices(param: Value, rpcenv: &mut dyn RpcEnvironment) -> Result
 | 
			
		||||
        _ => 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> {
 | 
			
		||||
        let mut text = String::new();
 | 
			
		||||
 | 
			
		||||
@ -300,15 +306,21 @@ fn network_commands() -> CommandLineInterface {
 | 
			
		||||
 | 
			
		||||
    let cmd_def = CliCommandMap::new()
 | 
			
		||||
        .insert("list", CliCommand::new(&API_METHOD_LIST_NETWORK_DEVICES))
 | 
			
		||||
        .insert("update",
 | 
			
		||||
                CliCommand::new(&api2::config::network::API_METHOD_UPDATE_INTERFACE)
 | 
			
		||||
        .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)
 | 
			
		||||
        .insert(
 | 
			
		||||
            "remove",
 | 
			
		||||
            CliCommand::new(&api2::config::network::API_METHOD_DELETE_INTERFACE)
 | 
			
		||||
                .arg_param(&["name"])
 | 
			
		||||
                .completion_cb("name", config::network::complete_interface_name)
 | 
			
		||||
        )
 | 
			
		||||
        .insert(
 | 
			
		||||
            "reload",
 | 
			
		||||
            CliCommand::new(&api2::config::network::API_METHOD_RELOAD_NETWORK_CONFIG)
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
    cmd_def.into()
 | 
			
		||||
 | 
			
		||||
@ -387,6 +387,15 @@ pub fn config() -> Result<(NetworkConfig, [u8;32]), Error> {
 | 
			
		||||
    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> {
 | 
			
		||||
 | 
			
		||||
    let mut raw = Vec::new();
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,5 @@
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use std::process::Command;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use anyhow::{Error, bail, format_err};
 | 
			
		||||
@ -136,3 +138,59 @@ pub fn get_network_interfaces() -> Result<HashMap<String, bool>, Error> {
 | 
			
		||||
 | 
			
		||||
    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