contain-rs 0.1.0

Run containers with podman (or docker)
Documentation
use std::process::Command;

use crate::{
    container::*,
    error::{Context, ErrorType, Result},
    rt::{ContainerInfo, ProcessState},
};

use super::{
    shared::{
        build_inspect_command, build_log_command, build_ps_command, build_rm_command,
        build_run_command, build_stop_command, do_log, run_and_wait_for_command,
        run_and_wait_for_command_infallible, wait_for,
    },
    Client, ContainerHandle, Log,
};

///
/// The Podman struct is used for acessing the podman binary. 
///
/// ```
/// use contain_rs::{
///     client::{podman::Podman, Client, Handle},
///     container::{postgres::Postgres, Container, Image, HealthCheck, WaitStrategy},
/// };
///
/// let client = Podman::new();
/// 
/// let container = Container::from_image(Image::from_name("docker.io/library/nginx"))
///     .health_check(HealthCheck::new("curl http://localhost || exit 1"))
///     .wait_for(WaitStrategy::HealthCheck);
///
/// client.run(&container).unwrap();
/// 
/// assert!(client.wait(&container).is_ok());
/// ```
///
#[allow(dead_code)]
#[derive(Clone)]
pub struct Podman {
    host: Option<String>,
}

impl Podman {
    const BINARY: &'static str = "podman";

    pub fn new() -> Self {
        Self { host: None }
    }

    fn build_command(&self) -> Command {
        Command::new(Self::BINARY)
    }
}

impl Client for Podman {
    type ClientType = Self;

    fn create(&self, container: Container) -> ContainerHandle<Podman> {
        ContainerHandle {
            client: self.to_owned(),
            container: container,
        }
    }

    fn run(&self, container: &Container) -> Result<()> {
        let mut command = self.build_command();

        build_run_command(&mut command, container);
        run_and_wait_for_command_infallible(&mut command)?;

        Ok(())
    }

    fn stop(&self, container: &Container) -> Result<()> {
        let mut command = self.build_command();

        build_stop_command(&mut command, container);
        run_and_wait_for_command_infallible(&mut command)?;

        Ok(())
    }

    fn rm(&self, container: &Container) -> Result<()> {
        let mut command = self.build_command();

        build_rm_command(&mut command, container);
        run_and_wait_for_command_infallible(&mut command)?;

        Ok(())
    }

    fn log(&self, container: &Container) -> Result<Option<Log>> {
        if self.runs(container)? {
            let mut command = self.build_command();

            build_log_command(&mut command, container);

            Ok(Some(do_log(&mut command)?))
        } else {
            Ok(None)
        }
    }

    fn inspect(&self, container: &Container) -> Result<Option<ContainerInfo>> {
        let mut command = self.build_command();

        build_inspect_command(&mut command, container);

        let json = run_and_wait_for_command_infallible(&mut command)?;

        let container_infos: Vec<ContainerInfo> = serde_json::from_str(&json).map_err(|e| {
            Context::new()
                .source(e)
                .info("message", "could not parse inspect output")
                .info("json", &json)
                .into_error(ErrorType::JsonError)
        })?;

        match container_infos.get(0) {
            Some(info) => Ok(Some(info.to_owned())),
            None => Ok(None),
        }
    }

    fn exists(&self, container: &Container) -> Result<bool> {
        let mut command = self.build_command();

        command.arg("container").arg("exists").arg(&container.name);

        let output = run_and_wait_for_command(&mut command)?;

        match output.status.code() {
            Some(0) => Ok(true),
            Some(1) => Ok(false),
            Some(code) => Err(Context::new()
                .info("message", "exists command failed")
                .info("code", &code)
                .info("stderr", &String::from_utf8(output.stderr).unwrap())
                .into_error(ErrorType::CommandError)),
            None => panic!(
                "{}",
                Context::new()
                    .info("message", "command exitted with no status code")
                    .into_error(ErrorType::Unrecoverable)
            ),
        }
    }

    fn runs(&self, container: &Container) -> Result<bool> {
        Ok(self.inspect(container)?.is_some())
    }

    fn ps(&self) -> Result<Vec<ProcessState>> {
        let mut command = self.build_command();

        build_ps_command(&mut command);

        let output = run_and_wait_for_command_infallible(&mut command)?;

        match serde_json::from_str(&output) {
            Ok(vec) => Ok(vec),
            Err(e) => Err(Context::new()
                .source(e)
                .info("reason", "could not parse json")
                .info("json", &output)
                .into_error(ErrorType::JsonError)),
        }
    }

    fn wait(&self, container: &Container) -> Result<()> {
        let command = self.build_command();

        wait_for(command, container)
    }
}