diff options
author | Wojciech Kępka <46892771+wojciechkepka@users.noreply.github.com> | 2021-02-06 07:57:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-06 01:57:49 -0500 |
commit | ef3dfad8f691f64e41fb1d369399471cde6ad8c0 (patch) | |
tree | e8b5bba9d148b3522171eaf72b33624334774416 /src | |
parent | 9b85dc8a9d370139e8eb3cafadf5a0f8f6dc6597 (diff) |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/builder.rs | 69 | ||||
-rw-r--r-- | src/lib.rs | 178 | ||||
-rw-r--r-- | src/rep.rs | 27 |
3 files changed, 229 insertions, 45 deletions
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<String> { + serde_json::to_string(&self.params).map_err(Error::from) + } + + pub fn parse_from<'a, K, V>( + &self, + params: &'a HashMap<K, V>, + body: &mut BTreeMap<String, Value>, + ) where + &'a HashMap<K, V>: 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 { @@ -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<String> { - #[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<Item = Result<tty::TtyChunk>> + 'a { - let bytes: &[u8] = b"{}"; - - let stream = Box::pin(self.docker.stream_post( - format!("/exec/{}/start", id), - Some((bytes.into(), mime::APPLICATION_JSON)), - None::<iter::Empty<_>>, - )); - - tty::decode(stream) - } - + /// Execute a command in this container pub fn exec( &'a self, opts: &'a ExecContainerOptions, ) -> impl Stream<Item = Result<tty::TtyChunk>> + 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<S>( + docker: &'a Docker, + id: S, + ) -> Exec<'a> + where + S: Into<String>, + { + 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<String> { + #[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<Item = Result<tty::TtyChunk>> + 'a { + let bytes: &[u8] = b"{}"; + + let stream = Box::pin(docker.stream_post( + format!("/exec/{}/start", &exec_id), + Some((bytes.into(), mime::APPLICATION_JSON)), + None::<iter::Empty<_>>, + )); + + 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<Exec<'a>> { + 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<S>( + docker: &'a Docker, + id: S, + ) -> Exec<'a> + where + S: Into<String>, + { + Exec::new(docker, id) + } + + /// Starts this exec instance returning a multiplexed tty stream + pub fn start(&'a self) -> impl Stream<Item = Result<tty::TtyChunk>> + '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::<iter::Empty<_>>, + )); + + Ok(tty::decode(stream)) + } + .try_flatten_stream(), + ) + } + + /// Inspect this exec instance to aquire detailed information + pub async fn inspect(&self) -> Result<ExecDetails> { + 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> { @@ -495,6 +495,33 @@ pub struct Event { } #[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<u64>, + #[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<String>, + pub entrypoint: String, + pub privileged: bool, + pub tty: bool, + pub user: Option<String>, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Actor { #[serde(rename = "ID")] pub id: String, |