diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2021-03-07 20:31:26 +0100 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2021-03-07 21:50:29 +0100 |
commit | 94a828e2fd4cd7dfdbb52e1a7df3cd04ee9e4c35 (patch) | |
tree | a9ca183000f4b05a75ee31cdd848be8e6832b7d6 | |
parent | f0a223a5c78fb5e7b534f0595899af42b296e0d5 (diff) |
Move container subcommand to own file
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r-- | src/commands/endpoint.rs | 176 | ||||
-rw-r--r-- | src/commands/endpoint_container.rs | 192 | ||||
-rw-r--r-- | src/commands/mod.rs | 1 |
3 files changed, 195 insertions, 174 deletions
diff --git a/src/commands/endpoint.rs b/src/commands/endpoint.rs index 7dd7c27..67e6929 100644 --- a/src/commands/endpoint.rs +++ b/src/commands/endpoint.rs @@ -11,7 +11,6 @@ use std::str::FromStr; use std::sync::Arc; -use anyhow::Error; use anyhow::Result; use anyhow::anyhow; use clap::ArgMatches; @@ -40,7 +39,7 @@ pub async fn endpoint(matches: &ArgMatches, config: &Configuration, progress_gen match matches.subcommand() { Some(("ping", matches)) => ping(endpoint_names, matches, config, progress_generator).await, Some(("stats", matches)) => stats(endpoint_names, matches, config, progress_generator).await, - Some(("container", matches)) => container(endpoint_names, matches, config).await, + Some(("container", matches)) => crate::commands::endpoint_container::container(endpoint_names, matches, config).await, Some(("containers", matches)) => containers(endpoint_names, matches, config).await, Some((other, _)) => Err(anyhow!("Unknown subcommand: {}", other)), None => Err(anyhow!("No subcommand")), @@ -154,177 +153,6 @@ async fn stats(endpoint_names: Vec<String>, crate::commands::util::display_data(hdr, data, csv) } -async fn container(endpoint_names: Vec<String>, - matches: &ArgMatches, - config: &Configuration, -) -> Result<()> { - let container_id = matches.value_of("container_id").unwrap(); - let endpoints = connect_to_endpoints(config, &endpoint_names).await?; - let relevant_endpoints = endpoints.into_iter() - .map(|ep| async { - ep.has_container_with_id(container_id) - .await - .map(|b| (ep, b)) - }) - .collect::<futures::stream::FuturesUnordered<_>>() - .collect::<Result<Vec<(_, bool)>>>() - .await? - .into_iter() - .filter_map(|tpl| { - if tpl.1 { - Some(tpl.0) - } else { - None - } - }) - .collect::<Vec<_>>(); - - if relevant_endpoints.len() > 1 { - return Err(anyhow!("Found more than one container for id {}", container_id)) - } - - let relevant_endpoint = relevant_endpoints.get(0).ok_or_else(|| { - anyhow!("Found no container for id {}", container_id) - })?; - - match matches.subcommand() { - Some(("top", matches)) => container_top(matches, relevant_endpoint, container_id).await, - Some(("kill", matches)) => container_kill(matches, relevant_endpoint, container_id).await, - Some(("delete", _)) => container_delete(relevant_endpoint, container_id).await, - Some(("start", _)) => container_start(relevant_endpoint, container_id).await, - Some(("stop", matches)) => container_stop(matches, relevant_endpoint, container_id).await, - Some(("exec", matches)) => container_exec(matches, relevant_endpoint, container_id).await, - Some((other, _)) => Err(anyhow!("Unknown subcommand: {}", other)), - None => Err(anyhow!("No subcommand")), - } -} - -async fn container_top( - matches: &ArgMatches, - endpoint: &Endpoint, - container_id: &str, -) -> Result<()> { - let csv = matches.is_present("csv"); - let top = endpoint - .get_container_by_id(container_id) - .await? - .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? - .top(None) - .await?; - - let hdr = crate::commands::util::mk_header(top.titles.iter().map(|s| s.as_ref()).collect()); - crate::commands::util::display_data(hdr, top.processes, csv) -} - -async fn container_kill( - matches: &ArgMatches, - endpoint: &Endpoint, - container_id: &str, -) -> Result<()> { - let signal = matches.value_of("signal"); - let prompt = if let Some(sig) = signal.as_ref() { - format!("Really kill {} with {}?", container_id, sig) - } else { - format!("Really kill {}?", container_id) - }; - - dialoguer::Confirm::new().with_prompt(prompt).interact()?; - - endpoint - .get_container_by_id(container_id) - .await? - .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? - .kill(signal) - .await - .map_err(Error::from) -} - -async fn container_delete( - endpoint: &Endpoint, - container_id: &str, -) -> Result<()> { - let prompt = format!("Really delete {}?", container_id); - dialoguer::Confirm::new().with_prompt(prompt).interact()?; - - endpoint - .get_container_by_id(container_id) - .await? - .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? - .delete() - .await - .map_err(Error::from) -} - -async fn container_start( - endpoint: &Endpoint, - container_id: &str, -) -> Result<()> { - let prompt = format!("Really start {}?", container_id); - dialoguer::Confirm::new().with_prompt(prompt).interact()?; - - endpoint - .get_container_by_id(container_id) - .await? - .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? - .start() - .await - .map_err(Error::from) -} - -async fn container_stop( - matches: &ArgMatches, - endpoint: &Endpoint, - container_id: &str, -) -> Result<()> { - let timeout = matches.value_of("timeout").map(u64::from_str).transpose()?.map(std::time::Duration::from_secs); - let prompt = format!("Really stop {}?", container_id); - dialoguer::Confirm::new().with_prompt(prompt).interact()?; - - endpoint - .get_container_by_id(container_id) - .await? - .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? - .stop(timeout) - .await - .map_err(Error::from) -} - -async fn container_exec( - matches: &ArgMatches, - endpoint: &Endpoint, - container_id: &str, -) -> Result<()> { - use std::io::Write; - use futures::TryStreamExt; - - let commands = matches.values_of("commands").unwrap().collect::<Vec<&str>>(); - let prompt = format!("Really run '{}' in {}?", commands.join(" "), container_id); - dialoguer::Confirm::new().with_prompt(prompt).interact()?; - - let execopts = shiplift::builder::ExecContainerOptions::builder() - .cmd(commands) - .attach_stdout(true) - .attach_stderr(true) - .build(); - - endpoint - .get_container_by_id(container_id) - .await? - .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? - .exec(&execopts) - .map_err(Error::from) - .try_for_each(|chunk| async { - let mut stdout = std::io::stdout(); - let mut stderr = std::io::stderr(); - match chunk { - shiplift::tty::TtyChunk::StdIn(_) => Err(anyhow!("Cannot handle STDIN TTY chunk")), - shiplift::tty::TtyChunk::StdOut(v) => stdout.write(&v).map_err(Error::from).map(|_| ()), - shiplift::tty::TtyChunk::StdErr(v) => stderr.write(&v).map_err(Error::from).map(|_| ()), - } - }) - .await -} - async fn containers(endpoint_names: Vec<String>, matches: &ArgMatches, @@ -397,7 +225,7 @@ async fn containers_list(endpoint_names: Vec<String>, /// Helper function to connect to all endpoints from the configuration, that appear (by name) in /// the `endpoint_names` list -async fn connect_to_endpoints(config: &Configuration, endpoint_names: &[String]) -> Result<Vec<Arc<Endpoint>>> { +pub(super) async fn connect_to_endpoints(config: &Configuration, endpoint_names: &[String]) -> Result<Vec<Arc<Endpoint>>> { let endpoint_configurations = config .docker() .endpoints() diff --git a/src/commands/endpoint_container.rs b/src/commands/endpoint_container.rs new file mode 100644 index 0000000..2003ae0 --- /dev/null +++ b/src/commands/endpoint_container.rs @@ -0,0 +1,192 @@ +// +// Copyright (c) 2020-2021 science+computing ag and other contributors +// +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// + +use std::str::FromStr; + +use anyhow::Error; +use anyhow::Result; +use anyhow::anyhow; +use clap::ArgMatches; +use tokio_stream::StreamExt; + +use crate::config::Configuration; +use crate::endpoint::Endpoint; + +pub async fn container(endpoint_names: Vec<String>, + matches: &ArgMatches, + config: &Configuration, +) -> Result<()> { + let container_id = matches.value_of("container_id").unwrap(); + let endpoints = crate::commands::endpoint::connect_to_endpoints(config, &endpoint_names).await?; + let relevant_endpoints = endpoints.into_iter() + .map(|ep| async { + ep.has_container_with_id(container_id) + .await + .map(|b| (ep, b)) + }) + .collect::<futures::stream::FuturesUnordered<_>>() + .collect::<Result<Vec<(_, bool)>>>() + .await? + .into_iter() + .filter_map(|tpl| { + if tpl.1 { + Some(tpl.0) + } else { + None + } + }) + .collect::<Vec<_>>(); + + if relevant_endpoints.len() > 1 { + return Err(anyhow!("Found more than one container for id {}", container_id)) + } + + let relevant_endpoint = relevant_endpoints.get(0).ok_or_else(|| { + anyhow!("Found no container for id {}", container_id) + })?; + + match matches.subcommand() { + Some(("top", matches)) => container_top(matches, relevant_endpoint, container_id).await, + Some(("kill", matches)) => container_kill(matches, relevant_endpoint, container_id).await, + Some(("delete", _)) => container_delete(relevant_endpoint, container_id).await, + Some(("start", _)) => container_start(relevant_endpoint, container_id).await, + Some(("stop", matches)) => container_stop(matches, relevant_endpoint, container_id).await, + Some(("exec", matches)) => container_exec(matches, relevant_endpoint, container_id).await, + Some((other, _)) => Err(anyhow!("Unknown subcommand: {}", other)), + None => Err(anyhow!("No subcommand")), + } +} + +async fn container_top( + matches: &ArgMatches, + endpoint: &Endpoint, + container_id: &str, +) -> Result<()> { + let csv = matches.is_present("csv"); + let top = endpoint + .get_container_by_id(container_id) + .await? + .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? + .top(None) + .await?; + + let hdr = crate::commands::util::mk_header(top.titles.iter().map(|s| s.as_ref()).collect()); + crate::commands::util::display_data(hdr, top.processes, csv) +} + +async fn container_kill( + matches: &ArgMatches, + endpoint: &Endpoint, + container_id: &str, +) -> Result<()> { + let signal = matches.value_of("signal"); + let prompt = if let Some(sig) = signal.as_ref() { + format!("Really kill {} with {}?", container_id, sig) + } else { + format!("Really kill {}?", container_id) + }; + + dialoguer::Confirm::new().with_prompt(prompt).interact()?; + + endpoint + .get_container_by_id(container_id) + .await? + .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? + .kill(signal) + .await + .map_err(Error::from) +} + +async fn container_delete( + endpoint: &Endpoint, + container_id: &str, +) -> Result<()> { + let prompt = format!("Really delete {}?", container_id); + dialoguer::Confirm::new().with_prompt(prompt).interact()?; + + endpoint + .get_container_by_id(container_id) + .await? + .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? + .delete() + .await + .map_err(Error::from) +} + +async fn container_start( + endpoint: &Endpoint, + container_id: &str, +) -> Result<()> { + let prompt = format!("Really start {}?", container_id); + dialoguer::Confirm::new().with_prompt(prompt).interact()?; + + endpoint + .get_container_by_id(container_id) + .await? + .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? + .start() + .await + .map_err(Error::from) +} + +async fn container_stop( + matches: &ArgMatches, + endpoint: &Endpoint, + container_id: &str, +) -> Result<()> { + let timeout = matches.value_of("timeout").map(u64::from_str).transpose()?.map(std::time::Duration::from_secs); + let prompt = format!("Really stop {}?", container_id); + dialoguer::Confirm::new().with_prompt(prompt).interact()?; + + endpoint + .get_container_by_id(container_id) + .await? + .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? + .stop(timeout) + .await + .map_err(Error::from) +} + +async fn container_exec( + matches: &ArgMatches, + endpoint: &Endpoint, + container_id: &str, +) -> Result<()> { + use std::io::Write; + use futures::TryStreamExt; + + let commands = matches.values_of("commands").unwrap().collect::<Vec<&str>>(); + let prompt = format!("Really run '{}' in {}?", commands.join(" "), container_id); + dialoguer::Confirm::new().with_prompt(prompt).interact()?; + + let execopts = shiplift::builder::ExecContainerOptions::builder() + .cmd(commands) + .attach_stdout(true) + .attach_stderr(true) + .build(); + + endpoint + .get_container_by_id(container_id) + .await? + .ok_or_else(|| anyhow!("Cannot find container {} on {}", container_id, endpoint.name()))? + .exec(&execopts) + .map_err(Error::from) + .try_for_each(|chunk| async { + let mut stdout = std::io::stdout(); + let mut stderr = std::io::stderr(); + match chunk { + shiplift::tty::TtyChunk::StdIn(_) => Err(anyhow!("Cannot handle STDIN TTY chunk")), + shiplift::tty::TtyChunk::StdOut(v) => stdout.write(&v).map_err(Error::from).map(|_| ()), + shiplift::tty::TtyChunk::StdErr(v) => stderr.write(&v).map_err(Error::from).map(|_| ()), + } + }) + .await +} + diff --git a/src/commands/mod.rs b/src/commands/mod.rs index bdabd7e..0256507 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -16,6 +16,7 @@ pub use db::db; mod endpoint; pub use endpoint::endpoint; +pub(super) mod endpoint_container; mod env_of; pub use env_of::env_of; |