diff --git a/CHANGELOG.md b/CHANGELOG.md index b7171dd..4ed930f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ ## Added +[[#21](https://github.com/MitchellBerend/docker-manager/issues/21)] Remove implicit sudo call + + +## Added + v0.0.4 --- diff --git a/README.md b/README.md index 0d19926..23431fb 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,9 @@ to support all docker commands on remote nodes. | STOP | | | LOGS | | | EXEC | --tty is not implemented | + + +# Flags + +`-s`/`--sudo` Enables sudo on the remote node. This might be needed depending on +how the remote node and it's user is set up. diff --git a/src/cli/app.rs b/src/cli/app.rs index 33d68f1..1842195 100644 --- a/src/cli/app.rs +++ b/src/cli/app.rs @@ -3,6 +3,10 @@ use clap::{Parser, Subcommand}; #[derive(Parser)] #[command(author, version, about)] pub struct App { + /// Runs the command as sudo on the remote nodes + #[arg(short, long)] + pub sudo: bool, + /// This command will be ran on the remote nodes #[command(subcommand)] pub command: Command, diff --git a/src/client/connector.rs b/src/client/connector.rs index 6c41ffb..56b9cfc 100644 --- a/src/client/connector.rs +++ b/src/client/connector.rs @@ -42,7 +42,7 @@ impl Node { Self { address } } - pub async fn run_command(&self, command: Command) -> Result { + pub async fn run_command(&self, command: Command, sudo: bool) -> Result { let session = match openssh::SessionBuilder::default() .connect_timeout(std::time::Duration::new(1, 0)) .connect_mux(&self.address) @@ -79,8 +79,15 @@ impl Node { user, workdir, ); - match command::run_exec(self.address.clone(), session, container_id, command, flags) - .await + match command::run_exec( + self.address.clone(), + session, + container_id, + sudo, + command, + flags, + ) + .await { Ok(result) => Ok(result), Err(e) => Err(NodeError::SessionError(self.address.clone(), e)), @@ -96,7 +103,7 @@ impl Node { } => { let flags = ImagesFlags::new(all, digest, filter, format, no_trunc, quiet); - match command::run_images(self.address.clone(), session, flags).await { + match command::run_images(self.address.clone(), session, sudo, flags).await { Ok(result) => Ok(result), Err(e) => Err(NodeError::SessionError(self.address.clone(), e)), } @@ -112,7 +119,9 @@ impl Node { } => { let flags = LogsFlags::new(details, follow, since, tail, timestamps, until); - match command::run_logs(self.address.clone(), session, container_id, flags).await { + match command::run_logs(self.address.clone(), session, container_id, sudo, flags) + .await + { //, follow).await { Ok(result) => Ok(result), Err(e) => Err(NodeError::SessionError(self.address.clone(), e)), @@ -129,13 +138,13 @@ impl Node { size, } => { let flags = PsFlags::new(all, filter, format, last, latests, no_trunc, quiet, size); - match command::run_ps(self.address.clone(), session, flags).await { + match command::run_ps(self.address.clone(), session, sudo, flags).await { Ok(result) => Ok(result), Err(e) => Err(NodeError::SessionError(self.address.clone(), e)), } } Command::Stop { container_id } => { - match command::run_stop(self.address.clone(), session, container_id).await { + match command::run_stop(self.address.clone(), session, sudo, container_id).await { Ok(result) => Ok(result), Err(e) => Err(NodeError::SessionError(self.address.clone(), e)), } diff --git a/src/lib.rs b/src/lib.rs index 4a2d9a1..e06703f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ pub async fn run() { } _ => { - for word in utility::run_command(_cli.command).await { + for word in utility::run_command(_cli.command, _cli.sudo).await { match word { Ok(s) => println!("{}", s), Err(e) => println!("{}", e), diff --git a/src/utility/command.rs b/src/utility/command.rs index 5f84a5b..04946b0 100644 --- a/src/utility/command.rs +++ b/src/utility/command.rs @@ -4,11 +4,12 @@ pub async fn run_exec( hostname: String, session: openssh::Session, container_id: String, + sudo: bool, command: Vec, //args: Option>, flags: ExecFlags, ) -> Result { - let mut _command: Vec = vec!["docker".into(), "exec".into()]; + let mut _command: Vec = vec!["exec".into()]; for flag in &flags.flags() { _command.push(flag.clone()); @@ -22,13 +23,35 @@ pub async fn run_exec( if flags.interactive { // This needs to be mutable so the stdout can be written to #[allow(unused_mut)] - let mut _output = session.command("sudo").args(_command).spawn().await?; + let mut _output = match sudo { + true => { + session + .command("sudo") + .arg("docker") + .args(_command) + .spawn() + .await? + } + false => session.command("docker").args(_command).spawn().await?, + }; loop { std::thread::sleep(std::time::Duration::new(1, 0)); } } else { - let output = match session.command("sudo").args(_command).output().await { + let _output = match sudo { + true => { + session + .command("sudo") + .arg("docker") + .args(_command) + .output() + .await + } + false => session.command("docker").args(_command).output().await, + }; + + let output = match _output { Ok(output) => output, Err(e) => return Err(e), }; @@ -45,18 +68,32 @@ pub async fn run_exec( pub async fn run_images( hostname: String, session: openssh::Session, + sudo: bool, flags: ImagesFlags, ) -> Result { - let mut command: Vec = vec!["docker".into(), "images".into()]; + let mut command: Vec = vec!["images".into()]; for flag in flags.flags() { command.push(flag) } - let output = match session.command("sudo").args(command).output().await { + let _output = match sudo { + true => { + session + .command("sudo") + .arg("docker") + .args(command) + .output() + .await + } + false => session.command("docker").args(command).output().await, + }; + + let output = match _output { Ok(output) => output, Err(e) => return Err(e), }; + let mut rv: String = format!("{}\n", hostname); match output.status.code().unwrap() { 0 => rv.push_str(std::str::from_utf8(&output.stdout).unwrap_or("")), @@ -70,9 +107,10 @@ pub async fn run_logs( hostname: String, session: openssh::Session, container_id: String, + sudo: bool, flags: LogsFlags, ) -> Result { - let mut command: Vec = vec!["docker".into(), "logs".into()]; + let mut command: Vec = vec!["logs".into()]; for item in flags.flags() { command.push(item) @@ -84,15 +122,36 @@ pub async fn run_logs( // This needs to be mutable so the stdout can be written to #[allow(unused_mut)] - let mut _output = session.command("sudo").args(command).spawn().await?; + let mut _output = match sudo { + true => { + session + .command("sudo") + .arg("docker") + .args(command) + .spawn() + .await? + } + false => session.command("docker").args(command).spawn().await?, + }; loop { std::thread::sleep(std::time::Duration::new(1, 0)); } } else { command.push(container_id); + let _output = match sudo { + true => { + session + .command("sudo") + .arg("docker") + .args(command) + .output() + .await + } + false => session.command("docker").args(command).output().await, + }; - let output = match session.command("sudo").args(command).output().await { + let output = match _output { Ok(output) => output, Err(e) => return Err(e), }; @@ -111,18 +170,32 @@ pub async fn run_logs( pub async fn run_ps( hostname: String, session: openssh::Session, + sudo: bool, flags: PsFlags, ) -> Result { - let mut command: Vec = vec!["docker".into(), "ps".into()]; + let mut command: Vec = vec!["ps".into()]; for flag in flags.flags() { command.push(flag) } - let output = match session.command("sudo").args(command).output().await { + let _output = match sudo { + true => { + session + .command("sudo") + .arg("docker") + .args(command) + .output() + .await + } + false => session.command("docker").args(command).output().await, + }; + + let output = match _output { Ok(output) => output, Err(e) => return Err(e), }; + let mut rv: String = format!("{}\n", hostname); match output.status.code().unwrap() { 0 => rv.push_str(std::str::from_utf8(&output.stdout).unwrap_or("")), @@ -135,11 +208,24 @@ pub async fn run_ps( pub async fn run_stop( hostname: String, session: openssh::Session, + sudo: bool, container_id: String, ) -> Result { - let command = vec!["docker", "stop", &container_id]; + let command = vec!["stop", &container_id]; + + let _output = match sudo { + true => { + session + .command("sudo") + .arg("docker") + .args(command) + .output() + .await + } + false => session.command("docker").args(command).output().await, + }; - let output = match session.command("sudo").args(command).output().await { + let output = match _output { Ok(output) => output, Err(e) => return Err(e), }; diff --git a/src/utility/other.rs b/src/utility/other.rs index 7d56241..0d5307d 100644 --- a/src/utility/other.rs +++ b/src/utility/other.rs @@ -7,20 +7,27 @@ use futures::{stream, StreamExt}; /// This function takes a `Client` and returns a list of matched node names in the form of a tuple /// with (hostname, container_id). -pub async fn find_container(client: Client, container_id: &str) -> Vec<(String, String)> { +pub async fn find_container( + client: Client, + container_id: &str, + sudo: bool, +) -> Vec<(String, String)> { let bodies = stream::iter(client.nodes_info()) .map(|(hostname, node)| async move { match node - .run_command(Command::Ps { - all: false, - filter: None, - format: None, - last: false, - latests: false, - no_trunc: false, - quiet: false, - size: false, - }) + .run_command( + Command::Ps { + all: false, + filter: None, + format: None, + last: false, + latests: false, + no_trunc: false, + quiet: false, + size: false, + }, + sudo, + ) .await { Ok(result) => (hostname.clone(), Ok(result)), diff --git a/src/utility/run.rs b/src/utility/run.rs index 2ba0d4f..0aa0cb0 100644 --- a/src/utility/run.rs +++ b/src/utility/run.rs @@ -6,7 +6,7 @@ use crate::cli::Command; use crate::client::{Client, Node, NodeError}; use crate::utility::find_container; -pub async fn run_command(command: Command) -> Vec> { +pub async fn run_command(command: Command, sudo: bool) -> Vec> { let config_path = format!( "{}/.ssh/config", std::env::var("HOME").unwrap_or_else(|_| "/home/root".into()) @@ -31,7 +31,7 @@ pub async fn run_command(command: Command) -> Vec> workdir, } => { let node_containers: Vec<(String, String)> = - find_container(client, &container_id).await; + find_container(client, &container_id, sudo).await; match node_containers.len() { 0 => { @@ -42,18 +42,21 @@ pub async fn run_command(command: Command) -> Vec> let node_tuple = node_containers.get(0).unwrap().to_owned(); let node = Node::new(node_tuple.1); match node - .run_command(Command::Exec { - container_id, - command, - detach, - detach_keys, - env, - env_file, - interactive, - privileged, - user, - workdir, - }) + .run_command( + Command::Exec { + container_id, + command, + detach, + detach_keys, + env, + env_file, + interactive, + privileged, + user, + workdir, + }, + sudo, + ) .await { Ok(s) => vec![Ok(s)], @@ -84,14 +87,17 @@ pub async fn run_command(command: Command) -> Vec> let _format: Option = format.as_ref().map(|format| String::from(&format.clone())); match node - .run_command(Command::Images { - all, - digest, - filter: _filter, - format: _format, - no_trunc, - quiet, - }) + .run_command( + Command::Images { + all, + digest, + filter: _filter, + format: _format, + no_trunc, + quiet, + }, + sudo, + ) .await { Ok(result) => Ok(result), @@ -111,7 +117,7 @@ pub async fn run_command(command: Command) -> Vec> until, } => { let node_containers: Vec<(String, String)> = - find_container(client, &container_id).await; + find_container(client, &container_id, sudo).await; match node_containers.len() { 0 => { vec![Err(CommandError::NoNodesFound(container_id))] @@ -121,15 +127,18 @@ pub async fn run_command(command: Command) -> Vec> let node_tuple = node_containers.get(0).unwrap().to_owned(); let node = Node::new(node_tuple.1); match node - .run_command(Command::Logs { - container_id, - details, - follow, - since, - tail, - timestamps, - until, - }) + .run_command( + Command::Logs { + container_id, + details, + follow, + since, + tail, + timestamps, + until, + }, + sudo, + ) .await { Ok(s) => vec![Ok(s)], @@ -162,16 +171,19 @@ pub async fn run_command(command: Command) -> Vec> let _format: Option = format.as_ref().map(|format| String::from(&format.clone())); match node - .run_command(Command::Ps { - all, - filter: _filter, - format: _format, - last, - latests, - no_trunc, - quiet, - size, - }) + .run_command( + Command::Ps { + all, + filter: _filter, + format: _format, + last, + latests, + no_trunc, + quiet, + size, + }, + sudo, + ) .await { Ok(result) => Ok(result), @@ -183,7 +195,7 @@ pub async fn run_command(command: Command) -> Vec> } Command::Stop { container_id } => { let node_containers: Vec<(String, String)> = - find_container(client, &container_id).await; + find_container(client, &container_id, sudo).await; match node_containers.len() { 0 => { @@ -193,7 +205,7 @@ pub async fn run_command(command: Command) -> Vec> // unwrap is safe here since we .unwrap()check if there is exactly 1 element let node_tuple = node_containers.get(0).unwrap().to_owned(); let node = Node::new(node_tuple.1); - match node.run_command(Command::Stop { container_id }).await { + match node.run_command(Command::Stop { container_id }, sudo).await { Ok(s) => vec![Ok(s)], Err(e) => vec![Err(CommandError::NodeError(e))], }