From ef3dfad8f691f64e41fb1d369399471cde6ad8c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20K=C4=99pka?= <46892771+wojciechkepka@users.noreply.github.com> Date: Sat, 6 Feb 2021 07:57:49 +0100 Subject: Add Exec struct for easier manipulation of exec instances (#251) * Add ExecDetails and ProcessConfig * Add exec_with_id, exec_inspect * Add example how to inspect an exec instance * Make clippy happy * exit_code is an Option on ExecDetails * Add Exec struct * Update example * Fix typo * Add Exec::resize and ExecResizeOptions * Add resize example --- src/builder.rs | 69 ++++++++++++++++++++++ src/lib.rs | 178 ++++++++++++++++++++++++++++++++++++++++++--------------- src/rep.rs | 27 +++++++++ 3 files changed, 229 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/builder.rs b/src/builder.rs index 110abcc..6971be9 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1701,6 +1701,75 @@ impl VolumeCreateOptionsBuilder { } } } +/// +/// Interface for creating volumes +#[derive(Serialize, Debug)] +pub struct ExecResizeOptions { + params: HashMap<&'static str, Value>, +} + +impl ExecResizeOptions { + /// serialize options as a string. returns None if no options are defined + pub fn serialize(&self) -> Result { + serde_json::to_string(&self.params).map_err(Error::from) + } + + pub fn parse_from<'a, K, V>( + &self, + params: &'a HashMap, + body: &mut BTreeMap, + ) where + &'a HashMap: IntoIterator, + K: ToString + Eq + Hash, + V: Serialize, + { + for (k, v) in params.iter() { + let key = k.to_string(); + let value = serde_json::to_value(v).unwrap(); + + body.insert(key, value); + } + } + + /// return a new instance of a builder for options + pub fn builder() -> ExecResizeOptionsBuilder { + ExecResizeOptionsBuilder::new() + } +} + +#[derive(Default)] +pub struct ExecResizeOptionsBuilder { + params: HashMap<&'static str, Value>, +} + +impl ExecResizeOptionsBuilder { + pub(crate) fn new() -> Self { + let params = HashMap::new(); + ExecResizeOptionsBuilder { params } + } + + pub fn height( + &mut self, + height: u64, + ) -> &mut Self { + self.params.insert("Name", json!(height)); + self + } + + pub fn width( + &mut self, + width: u64, + ) -> &mut Self { + self.params.insert("Name", json!(width)); + self + } + + pub fn build(&self) -> ExecResizeOptions { + ExecResizeOptions { + params: self.params.clone(), + } + } +} #[cfg(test)] mod tests { diff --git a/src/lib.rs b/src/lib.rs index da60d27..79813e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,16 +28,16 @@ mod tarball; pub use crate::{ builder::{ BuildOptions, ContainerConnectionOptions, ContainerFilter, ContainerListOptions, - ContainerOptions, EventsOptions, ExecContainerOptions, ImageFilter, ImageListOptions, - LogsOptions, NetworkCreateOptions, NetworkListOptions, PullOptions, RegistryAuth, - RmContainerOptions, TagOptions, VolumeCreateOptions, + ContainerOptions, EventsOptions, ExecContainerOptions, ExecResizeOptions, ImageFilter, + ImageListOptions, LogsOptions, NetworkCreateOptions, NetworkListOptions, PullOptions, + RegistryAuth, RmContainerOptions, TagOptions, VolumeCreateOptions, }, errors::Error, }; use crate::{ rep::{ - Change, Container as ContainerRep, ContainerCreateInfo, ContainerDetails, Event, Exit, - History, Image as ImageRep, ImageDetails, Info, NetworkCreateInfo, + Change, Container as ContainerRep, ContainerCreateInfo, ContainerDetails, Event, + ExecDetails, Exit, History, Image as ImageRep, ImageDetails, Info, NetworkCreateInfo, NetworkDetails as NetworkInfo, SearchResult, Stats, Status, Top, Version, Volume as VolumeRep, VolumeCreateInfo, Volumes as VolumesRep, }, @@ -514,52 +514,15 @@ impl<'a> Container<'a> { Ok(()) } - async fn exec_create( - &self, - opts: &ExecContainerOptions, - ) -> Result { - #[derive(serde::Deserialize)] - #[serde(rename_all = "PascalCase")] - struct Response { - id: String, - } - - let body: Body = opts.serialize()?.into(); - - let Response { id } = self - .docker - .post_json( - &format!("/containers/{}/exec", self.id)[..], - Some((body, mime::APPLICATION_JSON)), - ) - .await?; - - Ok(id) - } - - fn exec_start( - &self, - id: String, - ) -> impl Stream> + 'a { - let bytes: &[u8] = b"{}"; - - let stream = Box::pin(self.docker.stream_post( - format!("/exec/{}/start", id), - Some((bytes.into(), mime::APPLICATION_JSON)), - None::>, - )); - - tty::decode(stream) - } - + /// Execute a command in this container pub fn exec( &'a self, opts: &'a ExecContainerOptions, ) -> impl Stream> + Unpin + 'a { Box::pin( async move { - let id = self.exec_create(opts).await?; - Ok(self.exec_start(id)) + let id = Exec::create_id(&self.docker, &self.id, opts).await?; + Ok(Exec::_start(&self.docker, &id)) } .try_flatten_stream(), ) @@ -693,6 +656,131 @@ impl<'a> Containers<'a> { .await } } +/// Interface for docker exec instance +pub struct Exec<'a> { + docker: &'a Docker, + id: String, +} + +impl<'a> Exec<'a> { + fn new( + docker: &'a Docker, + id: S, + ) -> Exec<'a> + where + S: Into, + { + Exec { + docker, + id: id.into(), + } + } + + /// Creates an exec instance in docker and returns its id + pub(crate) async fn create_id( + docker: &'a Docker, + container_id: &str, + opts: &ExecContainerOptions, + ) -> Result { + #[derive(serde::Deserialize)] + #[serde(rename_all = "PascalCase")] + struct Response { + id: String, + } + + let body: Body = opts.serialize()?.into(); + + docker + .post_json( + &format!("/containers/{}/exec", container_id)[..], + Some((body, mime::APPLICATION_JSON)), + ) + .await + .map(|resp: Response| resp.id) + } + + /// Starts an exec instance with id exec_id + pub(crate) fn _start( + docker: &'a Docker, + exec_id: &str, + ) -> impl Stream> + 'a { + let bytes: &[u8] = b"{}"; + + let stream = Box::pin(docker.stream_post( + format!("/exec/{}/start", &exec_id), + Some((bytes.into(), mime::APPLICATION_JSON)), + None::>, + )); + + tty::decode(stream) + } + + /// Creates a new exec instance that will be executed in a container with id == container_id + pub async fn create( + docker: &'a Docker, + container_id: &str, + opts: &ExecContainerOptions, + ) -> Result> { + Ok(Exec::new( + docker, + Exec::create_id(docker, container_id, opts).await?, + )) + } + + /// Get a reference to a set of operations available to an already created exec instance. + /// + /// It's in callers responsibility to ensure that exec instance with specified id actually + /// exists. Use [Exec::create](Exec::create) to ensure that the exec instance is created + /// beforehand. + pub async fn get( + docker: &'a Docker, + id: S, + ) -> Exec<'a> + where + S: Into, + { + Exec::new(docker, id) + } + + /// Starts this exec instance returning a multiplexed tty stream + pub fn start(&'a self) -> impl Stream> + 'a { + Box::pin( + async move { + let bytes: &[u8] = b"{}"; + + let stream = Box::pin(self.docker.stream_post( + format!("/exec/{}/start", &self.id), + Some((bytes.into(), mime::APPLICATION_JSON)), + None::>, + )); + + Ok(tty::decode(stream)) + } + .try_flatten_stream(), + ) + } + + /// Inspect this exec instance to aquire detailed information + pub async fn inspect(&self) -> Result { + self.docker + .get_json(&format!("/exec/{}/json", &self.id)[..]) + .await + } + + pub async fn resize( + &self, + opts: &ExecResizeOptions, + ) -> Result<()> { + let body: Body = opts.serialize()?.into(); + + self.docker + .post_json( + &format!("/exec/{}/resize", &self.id)[..], + Some((body, mime::APPLICATION_JSON)), + ) + .await + } +} /// Interface for docker network pub struct Networks<'a> { diff --git a/src/rep.rs b/src/rep.rs index ededa6e..b5619f6 100644 --- a/src/rep.rs +++ b/src/rep.rs @@ -494,6 +494,33 @@ pub struct Event { pub time_nano: u64, } +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct ExecDetails { + pub can_remove: bool, + #[serde(rename = "ContainerID")] + pub container_id: String, + pub detach_keys: String, + pub exit_code: Option, + #[serde(rename = "ID")] + pub id: String, + pub open_stderr: bool, + pub open_stdin: bool, + pub open_stdout: bool, + pub process_config: ProcessConfig, + pub running: bool, + pub pid: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ProcessConfig { + pub arguments: Vec, + pub entrypoint: String, + pub privileged: bool, + pub tty: bool, + pub user: Option, +} + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Actor { #[serde(rename = "ID")] -- cgit v1.2.3