runlikers 0.1.0

Reverse-engineer docker run command line arguments based on running containers
Documentation
pub mod opts;
pub mod parsers;

use bollard::Docker;
use serde_json::Value;
use std::collections::BTreeMap;

pub struct Inspector {
    pub no_name: bool,
    pub use_volume_id: bool,
    pub pretty: bool,
    pub no_labels: bool,
    pub use_mount_flag: bool,
    pub tidy: bool,
    pub docker_host: Option<String>,
    pub indent: String,
    pub container_facts: Option<Value>,
    pub image_facts: Option<Value>,
}

impl Inspector {
    pub fn new(no_name: bool, use_volume_id: bool, pretty: bool, no_labels: bool) -> Self {
        Inspector {
            no_name,
            use_volume_id,
            pretty,
            no_labels,
            use_mount_flag: false,
            tidy: false,
            docker_host: None,
            indent: String::new(),
            container_facts: None,
            image_facts: None,
        }
    }

    pub fn set_container_facts(&mut self, raw_json: &str) -> Result<(), serde_json::Error> {
        let v: Value = serde_json::from_str(raw_json)?;
        let v = if let Value::Array(arr) = &v {
            arr.first().cloned().unwrap_or(v)
        } else {
            v
        };
        self.container_facts = Some(v);
        Ok(())
    }

    pub async fn inspect(&mut self, container: &str) -> Result<(), Box<dyn std::error::Error>> {
        let docker = if let Some(ref host) = self.docker_host {
            Docker::connect_with_host(host)?
        } else {
            Docker::connect_with_local_defaults()?
        };
        let container_json = docker.inspect_container(container, None).await?;
        self.container_facts = Some(serde_json::to_value(&container_json)?);
        let image_id = self.get_container_fact("Image").unwrap_or_default();
        let image_json = docker.inspect_image(&image_id).await?;
        self.image_facts = Some(serde_json::to_value(&image_json)?);
        Ok(())
    }

    pub fn get_container_fact(&self, path: &str) -> Option<String> {
        self.get_fact(path, self.container_facts.as_ref())
    }

    pub fn get_image_fact(&self, path: &str) -> Option<String> {
        self.get_fact(path, self.image_facts.as_ref())
    }

    pub fn get_fact(&self, path: &str, value: Option<&Value>) -> Option<String> {
        let value = value?;
        let parts: Vec<&str> = path.split('.').collect();
        let mut current = value;
        for p in &parts {
            current = current.get(*p)?;
        }
        match current {
            Value::String(s) => Some(s.clone()),
            Value::Number(n) => Some(n.to_string()),
            Value::Bool(b) => Some(b.to_string()),
            Value::Array(a) => Some(serde_json::to_string(a).unwrap_or_default()),
            Value::Object(o) => Some(serde_json::to_string(o).unwrap_or_default()),
            Value::Null => None,
        }
    }

    pub fn get_container_fact_list(&self, path: &str) -> Vec<Value> {
        self.get_fact_list(path, self.container_facts.as_ref())
    }

    pub fn get_image_fact_list(&self, path: &str) -> Vec<Value> {
        self.get_fact_list(path, self.image_facts.as_ref())
    }

    pub fn get_fact_list(&self, path: &str, value: Option<&Value>) -> Vec<Value> {
        let value = match value {
            Some(v) => v,
            None => return vec![],
        };
        let parts: Vec<&str> = path.split('.').collect();
        let mut current = value;
        for p in &parts {
            current = match current.get(*p) {
                Some(v) => v,
                None => return vec![],
            };
        }
        match current {
            Value::Array(a) => a.clone(),
            _ => vec![],
        }
    }

    pub fn get_container_fact_map(&self, path: &str) -> BTreeMap<String, Value> {
        self.get_fact_map(path, self.container_facts.as_ref())
    }

    pub fn get_image_fact_map(&self, path: &str) -> BTreeMap<String, Value> {
        self.get_fact_map(path, self.image_facts.as_ref())
    }

    pub fn get_fact_map(&self, path: &str, value: Option<&Value>) -> BTreeMap<String, Value> {
        let value = match value {
            Some(v) => v,
            None => return BTreeMap::new(),
        };
        let parts: Vec<&str> = path.split('.').collect();
        let mut current = value;
        for p in &parts {
            current = match current.get(*p) {
                Some(v) => v,
                None => return BTreeMap::new(),
            };
        }
        match current {
            Value::Object(o) => o.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
            _ => BTreeMap::new(),
        }
    }
}