product-os-command-control 0.0.15

Product OS : Command and Control provides a set of tools for running command and control across a distributed set of Product OS : Servers.
Documentation
use std::prelude::v1::*;

use std::collections::BTreeMap;
use regex::Regex;
use product_os_request::{BodyType, ProductOSClient, ProductOSResponse};
use serde_json::Value;

use crate::registry::Node;




pub struct Command {
    pub requester: product_os_request::ProductOSRequestClient,
    pub node_url: url::Url,
    pub verify_key: Vec<u8>,
    pub module: String,
    pub instruction: String,
    pub data: Option<serde_json::Value>
}

impl Command {
    pub async fn command(&self) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
        command(&self.requester, self.node_url.to_owned(), self.verify_key.to_owned(), self.module.to_owned(), self.instruction.to_owned(), self.data.to_owned()).await
    }
}



pub struct Ask {
    pub requester: product_os_request::ProductOSRequestClient,
    pub node_url: url::Url,
    pub verify_key: Vec<u8>,
    pub path: String,
    pub data: Option<serde_json::Value>,
    pub headers: BTreeMap<String, String>,
    pub params: BTreeMap<String, String>,
    pub method: product_os_request::Method
}

impl Ask {
    pub async fn ask(&self) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
        ask(&self.requester, self.node_url.to_owned(), self.verify_key.to_owned(), self.path.to_owned(), self.data.to_owned(), self.headers.to_owned(), self.params.to_owned(), self.method.to_owned()).await
    }
}



/*
pub async fn command_node(requester: &product_os_request::ProductOSRequester, node: &Node, verify_key: Vec<u8>,
           module: String, instruction: String, data: Option<serde_json::Value>) -> Result<ProductOSResponse, product_os_request::ProductOSRequestError> {
    let url = node.get_address();

    command(requester, url, verify_key, module, instruction, data).await
}
*/

pub async fn command(requester: &product_os_request::ProductOSRequestClient, url: url::Url, verify_key: Vec<u8>,
                     module: String, instruction: String, data: Option<serde_json::Value>) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
    let endpoint = String::from(url.to_string().trim_end_matches("/")) +
        "/command/" + module.as_str() + "/" + instruction.as_str();

    let mut request = requester.new_request(product_os_request::Method::POST, endpoint);

    match &data {
        None => {}
        Some(data) => {
            requester.set_body_json(&mut request, data.to_owned());
        }
    }

    request.add_header("x-product-os-verify".to_string(),
                       product_os_security::create_auth_request(None, false, data,
                                                                   None, &[], Some(verify_key.as_slice())),
                       true);

    match requester.request(request).await {
        Ok(response) => {
            tracing::trace!("Successfully sent {:?}/{:?} command to server {:?}", module, instruction, url);
            Ok(response)
        },
        Err(e) => {
            tracing::error!("Error encountered {:?} from {}", e, url);
            Err(product_os_request::ProductOSRequestError::Error(format!("No matching node found: {:?}", e)))
        }
    }
}

/*
pub fn command_node_sync(requester: &mut product_os_request::ProductOSRequester, node: &Node, verify_key: Vec<u8>,
                          module: String, instruction: String, data: Option<serde_json::Value>) -> Result<ProductOSResponse, product_os_request::ProductOSRequestSyncError> {
    let url = node.get_address();

    command_sync(requester, url, verify_key, module, instruction, data)
}
*/

/*
pub fn command_sync(requester: &mut product_os_request::ProductOSRequester, url: url::Url, verify_key: Vec<u8>,
                     module: String, instruction: String, data: Option<serde_json::Value>) -> Result<ProductOSResponse, product_os_request::ProductOSRequestSyncError> {
    let endpoint = String::from(url.to_string().trim_end_matches("/")) +
        "/command/" + module.as_str() + "/" + instruction.as_str();

    let mut request = requester.new_request(product_os_request::Method::POST, endpoint);

    request.add_header("x-product-os-verify".to_string(),
                       product_os_security::create_auth_request(None, false, data.clone(),
                                                              None, &[], Some(verify_key.as_slice())),
                       true);

    match requester.request_sync(request, BodyType::Json, data) {
        Ok(response) => {
            tracing::trace!("Successfully sent {:?}/{:?} command to server {:?}", module, instruction, url);
            Ok(response)
        },
        Err(e) => {
            tracing::error!("Error encountered {:?} from {}", e, url);
            Err(product_os_request::ProductOSRequestSyncError {
                error: product_os_request::ProductOSRequestError::Error("No matching node found".to_string()),
                generated_error: Some(e)
            })
        }
    }
}
*/

pub async fn ask_node(requester: &product_os_request::ProductOSRequestClient, node: &Node, verify_key: Vec<u8>,
       path: String, data: Option<serde_json::Value>, headers: BTreeMap<String, String>,
       params: BTreeMap<String, String>, method: product_os_request::Method) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
    let url = node.get_address();

    ask(requester, url, verify_key, path, data, headers, params, method).await
}

/*
pub fn ask_node_sync(requester: &mut product_os_request::ProductOSRequester, node: &Node, verify_key: Vec<u8>,
                      path: String, data: Option<serde_json::Value>, headers: BTreeMap<String, String>,
                      params: BTreeMap<String, String>, method: product_os_request::Method) -> Result<ProductOSResponse, product_os_request::ProductOSRequestSyncError> {
    let url = node.get_address();

    ask_sync(requester, url, verify_key, path, data, headers, params, method)
}
*/

pub async fn ask(requester: &product_os_request::ProductOSRequestClient, url: url::Url, verify_key: Vec<u8>,
                 path: String, data: Option<serde_json::Value>, headers: BTreeMap<String, String>,
                 params: BTreeMap<String, String>, method: product_os_request::Method) -> Result<ProductOSResponse<product_os_request::BodyBytes>, product_os_request::ProductOSRequestError> {
    let path_clean = clean_path(path, false);
    let endpoint = String::from(url.to_string().trim_end_matches("/")) +
        path_clean.clone().as_str();

    let mut request = requester.new_request(method, endpoint);

    request.add_headers(headers, false);
    request.add_params(params);

    match &data {
        None => {}
        Some(data) => {
            requester.set_body_json(&mut request, data.to_owned());
        }
    }

    request.add_header("x-product-os-verify".to_string(),
                       product_os_security::create_auth_request(None, false, data,
                                                                   None, &[], Some(verify_key.as_slice())),
                       true);

    match requester.request(request).await {
        Ok(response) => {
            tracing::trace!("Successfully sent ask {:?} command to server {:?}", path_clean, url);
            Ok(response)
        },
        Err(e) => {
            tracing::error!("Error encountered {:?} from {}", e, url);
            Err(product_os_request::ProductOSRequestError::Error(format!("No matching node found: {:?}", e)))
        }
    }
}

/*
pub fn ask_sync(requester: &mut product_os_request::ProductOSRequester, url: url::Url, verify_key: Vec<u8>,
                 path: String, data: Option<serde_json::Value>, headers: BTreeMap<String, String>,
                 params: BTreeMap<String, String>, method: product_os_request::Method) -> Result<ProductOSResponse, product_os_request::ProductOSRequestSyncError> {
    let path_clean = clean_path(path, false);
    let endpoint = String::from(url.to_string().trim_end_matches("/")) +
        path_clean.clone().as_str();

    let mut request = requester.new_request(method, endpoint);

    request.add_headers(headers, false);
    request.add_params(params);

    request.add_header("x-product-os-verify".to_string(),
                       product_os_security::create_auth_request(None, false, data.clone(),
                                                              None, &[], Some(verify_key.as_slice())),
                       true);

    match requester.request_sync(request, BodyType::Json, data) {
        Ok(response) => {
            tracing::trace!("Successfully sent ask {:?} command to server {:?}", path_clean, url);
            Ok(response)
        },
        Err(e) => {
            tracing::error!("Error encountered {:?} from {}", e, url);
            Err(product_os_request::ProductOSRequestSyncError {
                error: product_os_request::ProductOSRequestError::Error("No matching node found".to_string()),
                generated_error: Some(e)
            })
        }
    }
}
*/

fn clean_path(input: String, add_back_slash: bool) -> String {
    let mut path = input;

    if path != "/" {
        path = Regex::new(r"//").unwrap().replace_all(path.as_str(), "").to_string();
        path = Regex::new(r"/$").unwrap().replace_all(path.as_str(), "").to_string();
    }

    if !path.starts_with("/") {
        path = "/".to_owned() + path.as_str();
    }

    if add_back_slash && !path.ends_with("/") {
        path = path + "/";
    }

    Regex::new(r"[.][.]").unwrap().replace_all(path.as_str(), "").to_string()
}