Refactor some code to make the Ssh a "transport"
I'm still not thrilled with the need to hard-code some things here, and tried to use the enum_dispatch crate to make the transport mapping go directly from inventory::Transport to transport::Ssh. Didn't work because it needs to be struct/tuple syntax
This commit is contained in:
parent
e82ca1eff0
commit
822eceb0d6
|
@ -0,0 +1,47 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Inventory {
|
||||||
|
pub groups: Vec<Group>,
|
||||||
|
pub targets: Vec<Target>,
|
||||||
|
pub config: Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Group {
|
||||||
|
pub name: String,
|
||||||
|
pub targets: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Target {
|
||||||
|
pub name: String,
|
||||||
|
pub uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub transport: Transport,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum Transport {
|
||||||
|
Ssh,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_with_transport() {
|
||||||
|
let buf = r#"
|
||||||
|
---
|
||||||
|
targets: []
|
||||||
|
groups: []
|
||||||
|
config:
|
||||||
|
transport: ssh"#;
|
||||||
|
let _i: Inventory = serde_yaml::from_str(&buf).expect("Failed to deser");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +1,12 @@
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use serde::Deserialize;
|
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::net::{TcpStream};
|
|
||||||
use ssh2::Session;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
mod inventory;
|
||||||
struct Inventory {
|
mod transport;
|
||||||
groups: Vec<Group>,
|
|
||||||
targets: Vec<Target>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
use crate::inventory::*;
|
||||||
struct Target {
|
use crate::transport::ssh::Ssh;
|
||||||
name: String,
|
use crate::transport::Transport;
|
||||||
uri: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
struct Group {
|
|
||||||
name: String,
|
|
||||||
targets: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let opts = MyOptions::parse_args_default_or_exit();
|
let opts = MyOptions::parse_args_default_or_exit();
|
||||||
|
@ -31,59 +16,33 @@ fn main() {
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = std::fs::File::open("inventory.yml").expect("Failed to load the inventory.ymll file");
|
let file =
|
||||||
|
std::fs::File::open("inventory.yml").expect("Failed to load the inventory.ymll file");
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
let inventory: Inventory = serde_yaml::from_reader(reader).expect("Failed to read intenvory");
|
let inventory: Inventory = serde_yaml::from_reader(reader).expect("Failed to read intenvory");
|
||||||
|
|
||||||
|
let runner = match &inventory.config.transport {
|
||||||
|
crate::inventory::Transport::Ssh => Ssh::default(),
|
||||||
|
};
|
||||||
|
|
||||||
match opts.command.unwrap() {
|
match opts.command.unwrap() {
|
||||||
Command::Cmd(runopts) => {
|
Command::Cmd(runopts) => {
|
||||||
|
println!("run a command: {:?}", runopts);
|
||||||
if let Some(group) = inventory.groups.iter().find(|g| g.name == runopts.targets) {
|
if let Some(group) = inventory.groups.iter().find(|g| g.name == runopts.targets) {
|
||||||
std::process::exit(run_group(&runopts.command, &group, &inventory));
|
std::process::exit(runner.run_group(&runopts.command, &group, &inventory));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(target) = inventory.targets.iter().find(|t| t.name == runopts.targets) {
|
if let Some(target) = inventory.targets.iter().find(|t| t.name == runopts.targets) {
|
||||||
println!("run a command: {:?}", runopts);
|
println!("run a command: {:?}", runopts);
|
||||||
std::process::exit(run(&runopts.command, &target));
|
std::process::exit(runner.run(&runopts.command, &target));
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Couldn't find a target named `{}`", runopts.targets);
|
println!("Couldn't find a target named `{}`", runopts.targets);
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn run_group(command: &str, group: &Group, inventory: &Inventory) -> i32 {
|
|
||||||
let mut status = 1;
|
|
||||||
for target_name in group.targets.iter() {
|
|
||||||
// XXX: This is inefficient
|
|
||||||
for target in inventory.targets.iter() {
|
|
||||||
if &target.name == target_name {
|
|
||||||
println!("Running on `{}`", target.name);
|
|
||||||
status = run(command, &target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(command: &str, target: &Target) -> i32 {
|
|
||||||
// Connect to the local SSH server
|
|
||||||
let tcp = TcpStream::connect(format!("{}:22", target.uri)).unwrap();
|
|
||||||
let mut sess = Session::new().unwrap();
|
|
||||||
sess.set_tcp_stream(tcp);
|
|
||||||
sess.handshake().unwrap();
|
|
||||||
sess.userauth_agent(&std::env::var("USER").unwrap()).unwrap();
|
|
||||||
|
|
||||||
let mut channel = sess.channel_session().unwrap();
|
|
||||||
channel.exec(command).unwrap();
|
|
||||||
let mut s = String::new();
|
|
||||||
channel.read_to_string(&mut s).unwrap();
|
|
||||||
print!("{}", s);
|
|
||||||
channel.wait_close().expect("Failed to close the channel");
|
|
||||||
return channel.exit_status().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
struct MyOptions {
|
struct MyOptions {
|
||||||
// Options here can be accepted with any command (or none at all),
|
// Options here can be accepted with any command (or none at all),
|
||||||
|
@ -113,7 +72,7 @@ enum Command {
|
||||||
// Names can be explicitly specified using `#[options(name = "...")]`
|
// Names can be explicitly specified using `#[options(name = "...")]`
|
||||||
#[options(help = "show help for a command")]
|
#[options(help = "show help for a command")]
|
||||||
Help(HelpOpts),
|
Help(HelpOpts),
|
||||||
#[options(help="Run a single command on a target(s)")]
|
#[options(help = "Run a single command on a target(s)")]
|
||||||
Cmd(RunOpts),
|
Cmd(RunOpts),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +85,7 @@ struct HelpOpts {
|
||||||
// Options accepted for the `make` command
|
// Options accepted for the `make` command
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
struct RunOpts {
|
struct RunOpts {
|
||||||
#[options(free, help="Command to execute on the target(s)")]
|
#[options(free, help = "Command to execute on the target(s)")]
|
||||||
command: String,
|
command: String,
|
||||||
#[options(help = "Name of a target or group")]
|
#[options(help = "Name of a target or group")]
|
||||||
targets: String,
|
targets: String,
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
use crate::inventory::{Group, Inventory, Target};
|
||||||
|
|
||||||
|
pub mod ssh;
|
||||||
|
|
||||||
|
pub trait Transport {
|
||||||
|
fn run_group(&self, cmd: &str, group: &Group, inv: &Inventory) -> i32;
|
||||||
|
fn run(&self, command: &str, target: &Target) -> i32;
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
use crate::inventory::{Group, Inventory, Target};
|
||||||
|
use crate::transport::Transport;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ssh2::Session;
|
||||||
|
use std::io::prelude::*;
|
||||||
|
use std::net::TcpStream;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Ssh {}
|
||||||
|
|
||||||
|
impl Default for Ssh {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transport for Ssh {
|
||||||
|
fn run_group(&self, command: &str, group: &Group, inventory: &Inventory) -> i32 {
|
||||||
|
let mut status = 1;
|
||||||
|
for target_name in group.targets.iter() {
|
||||||
|
// XXX: This is inefficient
|
||||||
|
for target in inventory.targets.iter() {
|
||||||
|
if &target.name == target_name {
|
||||||
|
println!("Running on `{}`", target.name);
|
||||||
|
status = self.run(command, &target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status
|
||||||
|
}
|
||||||
|
fn run(&self, command: &str, target: &Target) -> i32 {
|
||||||
|
// Connect to the local SSH server
|
||||||
|
let tcp = TcpStream::connect(format!("{}:22", target.uri)).unwrap();
|
||||||
|
let mut sess = Session::new().unwrap();
|
||||||
|
sess.set_tcp_stream(tcp);
|
||||||
|
sess.handshake().unwrap();
|
||||||
|
sess.userauth_agent(&std::env::var("USER").unwrap())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut channel = sess.channel_session().unwrap();
|
||||||
|
channel.exec(command).unwrap();
|
||||||
|
let mut s = String::new();
|
||||||
|
channel.read_to_string(&mut s).unwrap();
|
||||||
|
print!("{}", s);
|
||||||
|
channel.wait_close().expect("Failed to close the channel");
|
||||||
|
return channel.exit_status().unwrap();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue