diff options
author | Ava Hahn <a.hahn@f5.com> | 2024-05-01 17:08:56 -0700 |
---|---|---|
committer | avahahn <110854134+avahahn@users.noreply.github.com> | 2024-05-08 13:30:08 -0700 |
commit | f6989dd67965c7489f8c68ecd0e25f0358b5993f (patch) | |
tree | e0ac500e68661e2e31f3f1eace7b3f5ffe133443 | |
parent | 818d4ad76592c87a5c0c7bbd728636023c07daa0 (diff) | |
download | unit-f6989dd67965c7489f8c68ecd0e25f0358b5993f.tar.gz unit-f6989dd67965c7489f8c68ecd0e25f0358b5993f.tar.bz2 |
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 <a.hahn@f5.com>
-rw-r--r-- | tools/unitctl/unit-client-rs/src/unit_client.rs | 3 | ||||
-rw-r--r-- | tools/unitctl/unit-client-rs/src/unitd_docker.rs | 102 | ||||
-rw-r--r-- | 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<Vec<String>, 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::<String, String>( + 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::<String>( + 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::<StartContainerOptions<String>> + ).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(()) + }) } } } |