Skip to content

Commit

Permalink
[#13] implemented exec command and most flags (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
MitchellBerend authored Oct 10, 2022
1 parent 1209fbf commit 8acc91a
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 15 deletions.
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ repos:
args: ['--all', '--', '--check']
- id: cargo-check
- id: clippy
args: ['--all-features']
11 changes: 5 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
## Added

[#5](https://github.com/MitchellBerend/docker-manager/issues/5)
Implementremaining logs flags
[[#5](https://github.com/MitchellBerend/docker-manager/issues/5)] Implementremaining logs flags

[#6](https://github.com/MitchellBerend/docker-manager/issues/6)
Implement remaining ps flags
[[#6](https://github.com/MitchellBerend/docker-manager/issues/6)] Implement remaining ps flags

[[#15](https://github.com/MitchellBerend/docker-manager/pull/15)] Added bash
compeltion command
[[#13](https://github.com/MitchellBerend/docker-manager/issues/13)] Implement exec command and most flags

[[#15](https://github.com/MitchellBerend/docker-manager/pull/15)] Added bash compeltion command
45 changes: 45 additions & 0 deletions src/cli/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,51 @@ pub enum Command {
/// ~/.bashrc.
Completion,

/// Execute a command on a given container unless 2 or more containers are found on remote nodes
Exec {
/// Container name or id
container_id: String,

/// Command
command: Vec<String>,

/// Detached mode: run command in the background
#[arg(short, long)]
detach: bool,

/// Override the key sequence for detaching a container
#[arg(long, value_name = "string")]
detach_keys: Option<String>,

/// Set environment variables
#[arg(short, long, value_name = "list")]
env: Option<Vec<String>>,

/// Read in a file of environment variables
#[arg(long, value_name = "list")]
env_file: Option<Vec<String>>,

/// Keep STDIN open even if not attached
#[arg(short, long)]
interactive: bool,

/// Give extended privileges to the command
#[arg(long)]
privileged: bool,

// This one is not useful for us
///// Allocate a pseudo-TTY
//#[arg(short, long)]
//tty: bool,
/// Username or UID (format: <name|uid>[:<group|gid>])
#[arg(short, long, value_name = "string")]
user: Option<String>,

/// Working directory inside the container
#[arg(short, long, value_name = "string")]
workdir: Option<String>,
},

/// Lists all containers on remote nodes
Ps {
/// Show all containers (default shows just running)
Expand Down
113 changes: 113 additions & 0 deletions src/cli/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,116 @@ impl PsFlags {
v
}
}

#[derive(Debug)]
pub struct ExecFlags {
pub detach: bool,
pub detach_keys: String,
pub env: Vec<String>,
pub env_file: Vec<String>,
pub interactive: bool,
pub privileged: bool,
//pub tty: bool,
pub user: String,
pub workdir: String,
}

impl ExecFlags {
#[allow(clippy::too_many_arguments)]
pub fn new(
detach: bool,
detach_keys: Option<String>,
env: Option<Vec<String>>,
env_file: Option<Vec<String>>,
interactive: bool,
privileged: bool,
//tty: bool,
user: Option<String>,
workdir: Option<String>,
) -> Self {
let detach_keys = match detach_keys {
Some(d) => d,
None => "".into(),
};

let env = match env {
Some(e) => e,
None => vec![],
};

let env_file = match env_file {
Some(e) => e,
None => vec![],
};

let user = match user {
Some(u) => u,
None => "".into(),
};

let workdir = match workdir {
Some(w) => w,
None => "".into(),
};

Self {
detach,
detach_keys,
env,
env_file,
interactive,
privileged,
//tty,
user,
workdir,
}
}

pub fn flags(&self) -> Vec<String> {
let mut v: Vec<String> = vec![];
if self.detach {
v.push("-d".into());
}

if self.interactive {
v.push("-i".into());
}

if self.privileged {
v.push("--privileged".into());
}

if !self.detach_keys.is_empty() {
v.push("--detach-keys".into());
v.push(self.detach_keys.clone());
}

if !self.env.is_empty() {
for var in &self.env {
v.push("-e".into());
v.push(var.clone());
}
}

if !self.env_file.is_empty() {
for file in &self.env_file {
v.push("--env-file".into());
v.push(file.clone());
}
}

if !self.user.is_empty() {
v.push("--user".into());
v.push(self.user.clone());
}

if !self.workdir.is_empty() {
v.push("--workdir".into());
v.push(self.workdir.clone());
}
// This one is not actually useful since this program is not a tty
// tty: bool,

v
}
}
31 changes: 30 additions & 1 deletion src/client/connector.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::path::Path;

use crate::cli::flags::{LogsFlags, PsFlags};
use crate::cli::flags::{ExecFlags, LogsFlags, PsFlags};
use crate::cli::Command;
use crate::utility::command;

Expand Down Expand Up @@ -57,6 +57,35 @@ impl Node {
// This command should not lead to any activity
unreachable!()
}
Command::Exec {
container_id,
command,
detach,
detach_keys,
env,
env_file,
interactive,
privileged,
user,
workdir,
} => {
let flags = ExecFlags::new(
detach,
detach_keys,
env,
env_file,
interactive,
privileged,
user,
workdir,
);
match command::run_exec(self.address.clone(), session, container_id, command, flags)
.await
{
Ok(result) => Ok(result),
Err(e) => Err(NodeError::SessionError(self.address.clone(), e)),
}
}
Command::Ps {
all,
filter,
Expand Down
59 changes: 51 additions & 8 deletions src/utility/command.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::cli::flags::{LogsFlags, PsFlags};
use crate::cli::flags::{ExecFlags, LogsFlags, PsFlags};

pub async fn run_ps(
hostname: String,
Expand Down Expand Up @@ -61,14 +61,12 @@ pub async fn run_logs(
command.push("-f".into());
command.push(container_id);

let mut output = match session.command("sudo").args(command).spawn().await {
Ok(output) => output,
Err(e) => return Err(e),
};
// 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?;

loop {
if let Some(stdout) = output.stdout().take() {
println!("{:?}", stdout)
};
std::thread::sleep(std::time::Duration::new(1, 0));
}
} else {
command.push(container_id);
Expand All @@ -78,6 +76,51 @@ pub async fn run_logs(
Err(e) => return Err(e),
};

let mut rv: String = format!("{}\n", hostname);

println!("o;{}", std::str::from_utf8(&output.stdout).unwrap_or(""));
println!("e;{}", std::str::from_utf8(&output.stderr).unwrap_or(""));
match output.status.code().unwrap() {
0 => rv.push_str(std::str::from_utf8(&output.stdout).unwrap_or("")),
_ => rv.push_str(std::str::from_utf8(&output.stderr).unwrap_or("")),
};
Ok(rv)
}
}

pub async fn run_exec(
hostname: String,
session: openssh::Session,
container_id: String,
command: Vec<String>,
//args: Option<Vec<String>>,
flags: ExecFlags,
) -> Result<String, openssh::Error> {
let mut _command: Vec<String> = vec!["docker".into(), "exec".into()];

for flag in &flags.flags() {
_command.push(flag.clone());
}
_command.push(container_id);

for arg in command {
_command.push(arg);
}

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?;

loop {
std::thread::sleep(std::time::Duration::new(1, 0));
}
} else {
let output = match session.command("sudo").args(_command).output().await {
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("")),
Expand Down
51 changes: 51 additions & 0 deletions src/utility/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,57 @@ pub async fn run_command(command: Command) -> Vec<Result<String, CommandError>>
// This command should not lead to any activity
unreachable!()
}
Command::Exec {
container_id,
command,
detach,
detach_keys,
env,
env_file,
interactive,
privileged,
user,
workdir,
} => {
let node_containers: Vec<(String, String)> =
find_container(client, &container_id).await;

match node_containers.len() {
0 => {
vec![Err(CommandError::NoNodesFound(container_id))]
}
1 => {
// 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::Exec {
container_id,
command,
detach,
detach_keys,
env,
env_file,
interactive,
privileged,
user,
workdir,
})
.await
{
Ok(s) => vec![Ok(s)],
Err(e) => vec![Err(CommandError::NodeError(e))],
}
}
_ => {
let nodes = node_containers
.iter()
.map(|(_, result)| result.clone())
.collect::<Vec<String>>();
vec![Err(CommandError::MutlipleNodesFound(nodes))]
}
}
}
Command::Ps {
all,
filter,
Expand Down

0 comments on commit 8acc91a

Please sign in to comment.