From f6989dd67965c7489f8c68ecd0e25f0358b5993f Mon Sep 17 00:00:00 2001 From: Ava Hahn Date: Wed, 1 May 2024 17:08:56 -0700 Subject: tools/unitctl: Add Docker deployment functionality * add UnitdDockerError type * write complete procedure to deploy unit via docker * additional tweaks verifying it fails peacefully * print important information in client Signed-off-by: Ava Hahn --- tools/unitctl/unit-client-rs/src/unit_client.rs | 3 + tools/unitctl/unit-client-rs/src/unitd_docker.rs | 102 +++++++++++++++++++++-- tools/unitctl/unitctl/src/cmd/instances.rs | 13 ++- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/tools/unitctl/unit-client-rs/src/unit_client.rs b/tools/unitctl/unit-client-rs/src/unit_client.rs index 7456b106..f76004cd 100644 --- a/tools/unitctl/unit-client-rs/src/unit_client.rs +++ b/tools/unitctl/unit-client-rs/src/unit_client.rs @@ -66,6 +66,9 @@ custom_error! {pub UnitClientError executable_path: String, pid: u64 } = "{message} for [pid={pid}, executable_path={executable_path}]: {source}", + UnitdDockerError { + message: String + } = "Failed to communicate with docker daemon: {message}", } impl UnitClientError { diff --git a/tools/unitctl/unit-client-rs/src/unitd_docker.rs b/tools/unitctl/unit-client-rs/src/unitd_docker.rs index 0f30ae8a..47f017a9 100644 --- a/tools/unitctl/unit-client-rs/src/unitd_docker.rs +++ b/tools/unitctl/unit-client-rs/src/unitd_docker.rs @@ -4,13 +4,16 @@ use std::path::PathBuf; use crate::unitd_process::UnitdProcess; use crate::unit_client::UnitClientError; - +use bollard::{models::ContainerSummary, Docker}; +use bollard::container::{Config, StartContainerOptions, ListContainersOptions}; +use bollard::image::CreateImageOptions; use bollard::secret::ContainerInspectResponse; +use bollard::models::{HostConfig, MountTypeEnum, Mount, ContainerCreateResponse}; use regex::Regex; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; +use crate::futures::StreamExt; -use bollard::{models::ContainerSummary, Docker}; #[derive(Clone, Debug)] pub struct UnitdContainer { @@ -215,13 +218,94 @@ impl UnitdContainer { /* deploys a new docker image of tag $image_tag. * mounts $socket to /var/run in the new container. - * mounts $application to /www in the new container. */ -pub fn deploy_new_container( - _socket: &String, - _application: &String, - _image: &String -) -> Result<(), UnitClientError> { - todo!() + * mounts $application read only to /www. + * new container is on host network. + * + * ON SUCCESS returns vector of warnings from Docker API + * ON FAILURE returns wrapped error from Docker API + */ +pub async fn deploy_new_container( + socket: &String, + application: &String, + image: &String +) -> Result, UnitClientError> { + match Docker::connect_with_local_defaults() { + Ok(docker) => { + let mut mounts = vec![]; + mounts.push(Mount{ + typ: Some(MountTypeEnum::BIND), + source: Some(socket.clone()), + target: Some("/var/run".to_string()), + ..Default::default() + }); + mounts.push(Mount{ + typ: Some(MountTypeEnum::BIND), + source: Some(application.clone()), + target: Some("/www".to_string()), + read_only: Some(true), + ..Default::default() + }); + + let _ = docker.create_image( + Some(CreateImageOptions { + from_image: image.as_str(), + ..Default::default() + }), None, None) + .next() + .await + .unwrap() + .or_else(|err| Err(UnitClientError::UnitdDockerError{message: err.to_string()})); + + let resp: ContainerCreateResponse; + match docker.create_container::( + None, Config { + image: Some(image.clone()), + host_config: Some(HostConfig { + network_mode: Some("host".to_string()), + mounts: Some(mounts), + ..Default::default() + }), ..Default::default()}) + .await { + Err(err) => return Err(UnitClientError::UnitdDockerError{message: err.to_string()}), + Ok(response) => resp = response, + } + + let mut list_container_filters = HashMap::new(); + list_container_filters.insert("id".to_string(), vec![resp.id]); + match docker.list_containers::( + Some(ListContainersOptions{ + all: true, + limit: None, + size: false, + filters: list_container_filters, + })) + .await { + Err(e) => Err(UnitClientError::UnitdDockerError{message: e.to_string()}), + Ok(info) => { + if info.len() < 1 { + return Err(UnitClientError::UnitdDockerError{message: "couldnt find new container".to_string()}); + } + if info[0].names.is_none() || info[0].names.clone().unwrap().len() < 1 { + return Err(UnitClientError::UnitdDockerError{message: "new container has no name".to_string()}); + } + + match docker.start_container( + info[0] + .names + .clone() + .unwrap()[0] + .strip_prefix("/") + .unwrap(), + None::> + ).await { + Err(err) => Err(UnitClientError::UnitdDockerError{message: err.to_string()}), + Ok(_) => Ok(resp.warnings) + } + } + } + }, + Err(e) => Err(UnitClientError::UnitdDockerError{message: e.to_string()}) + } } /* Returns either 64 char docker container ID or None */ diff --git a/tools/unitctl/unitctl/src/cmd/instances.rs b/tools/unitctl/unitctl/src/cmd/instances.rs index 84725957..cd59b436 100644 --- a/tools/unitctl/unitctl/src/cmd/instances.rs +++ b/tools/unitctl/unitctl/src/cmd/instances.rs @@ -12,12 +12,23 @@ pub(crate) async fn cmd(args: InstanceArgs) -> Result<(), UnitctlError> { ref application, ref image } => { + println!("Pulling and starting a container from {}", image); + println!("Will mount {} to /var/run for socket access", socket); + println!("Will READ ONLY mount {} to /www for application access", application); + println!("Note: Container will be on host network"); if !PathBuf::from(socket).is_dir() || !PathBuf::from(application).is_dir() { eprintln!("application and socket paths must be directories"); Err(UnitctlError::NoFilesImported) } else { deploy_new_container(socket, application, image) - .or_else(|e| Err(UnitctlError::UnitClientError{source: e})) + .await + .map_or_else(|e| Err(UnitctlError::UnitClientError{source: e}), + |warn| { + for i in warn { + println!("warning from docker: {}", i); + } + Ok(()) + }) } } } -- cgit