summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAva Hahn <a.hahn@f5.com>2024-05-01 17:08:56 -0700
committeravahahn <110854134+avahahn@users.noreply.github.com>2024-05-08 13:30:08 -0700
commitf6989dd67965c7489f8c68ecd0e25f0358b5993f (patch)
treee0ac500e68661e2e31f3f1eace7b3f5ffe133443
parent818d4ad76592c87a5c0c7bbd728636023c07daa0 (diff)
downloadunit-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.rs3
-rw-r--r--tools/unitctl/unit-client-rs/src/unitd_docker.rs102
-rw-r--r--tools/unitctl/unitctl/src/cmd/instances.rs13
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(())
+ })
}
}
}