Skip to content

Commit 7cd191e

Browse files
author
Jason Ozias
committed
initial commit
0 parents  commit 7cd191e

File tree

6 files changed

+421
-0
lines changed

6 files changed

+421
-0
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target
2+
**/*.rs.bk
3+
Cargo.lock

Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "libmussh"
3+
version = "0.1.0"
4+
authors = ["Jason Ozias <[email protected]>"]
5+
edition = "2018"
6+
7+
[dependencies]
8+
clap = "2"
9+
crossbeam = "0"
10+
failure = "0"
11+
getset = "0"
12+
indexmap = "1"
13+
serde = "1"
14+
serde_derive = "1"
15+
toml = "0"

src/config.rs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Copyright (c) 2018 libmussh developers
2+
//
3+
// Licensed under the Apache License, Version 2.0
4+
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5+
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. All files in the project carrying such notice may not be copied,
7+
// modified, or distributed except according to those terms.
8+
9+
use failure::{Error, Fallible};
10+
use getset::Getters;
11+
use serde_derive::{Deserialize, Serialize};
12+
use std::collections::BTreeMap;
13+
use std::convert::TryFrom;
14+
use std::fs::File;
15+
use std::io::{BufReader, Read};
16+
use std::path::PathBuf;
17+
18+
#[derive(Clone, Debug, Default, Deserialize, Eq, Getters, PartialEq, Serialize)]
19+
/// The base configuration.
20+
crate struct Mussh {
21+
/// A list of hosts.
22+
#[serde(serialize_with = "toml::ser::tables_last")]
23+
#[get = "pub"]
24+
hostlist: BTreeMap<String, Hosts>,
25+
/// The hosts.
26+
#[serde(serialize_with = "toml::ser::tables_last")]
27+
#[get = "pub"]
28+
hosts: BTreeMap<String, Host>,
29+
/// A command.
30+
#[serde(serialize_with = "toml::ser::tables_last")]
31+
#[get = "pub"]
32+
cmd: BTreeMap<String, Command>,
33+
}
34+
35+
impl TryFrom<PathBuf> for Mussh {
36+
type Error = Error;
37+
38+
fn try_from(path: PathBuf) -> Fallible<Self> {
39+
let mut buf_reader = BufReader::new(File::open(path)?);
40+
let mut buffer = String::new();
41+
let _bytes_read = buf_reader.read_to_string(&mut buffer)?;
42+
Ok(toml::from_str(&buffer)?)
43+
}
44+
}
45+
46+
#[derive(Clone, Debug, Default, Deserialize, Eq, Getters, PartialEq, Serialize)]
47+
/// hosts configuration
48+
crate struct Hosts {
49+
/// The hostnames.
50+
#[get = "pub"]
51+
hostnames: Vec<String>,
52+
}
53+
54+
#[derive(Clone, Debug, Default, Deserialize, Eq, Getters, PartialEq, Serialize)]
55+
/// Host configuration.
56+
crate struct Host {
57+
/// A hostname.
58+
#[get = "pub"]
59+
hostname: String,
60+
/// A pem key.
61+
#[get = "pub"]
62+
pem: Option<String>,
63+
/// A port
64+
#[get = "pub"]
65+
port: Option<u16>,
66+
/// A username.
67+
#[get = "pub"]
68+
username: String,
69+
/// A command alias.
70+
#[get = "pub"]
71+
alias: Option<Vec<Alias>>,
72+
}
73+
74+
#[derive(Clone, Debug, Default, Deserialize, Eq, Getters, PartialEq, Serialize)]
75+
/// command configuration
76+
crate struct Command {
77+
/// A Command.
78+
#[get = "pub"]
79+
command: String,
80+
}
81+
82+
#[derive(Clone, Debug, Default, Deserialize, Eq, Getters, PartialEq, Serialize)]
83+
/// command alias configuration.
84+
crate struct Alias {
85+
/// A command alias.
86+
#[get = "pub"]
87+
command: String,
88+
/// The command this is an alias for.
89+
#[get = "pub"]
90+
aliasfor: String,
91+
}
92+
93+
#[cfg(test)]
94+
mod test {
95+
use super::{Alias, Command};
96+
use failure::Fallible;
97+
98+
const ALIAS: &str = r#"command = "blah"
99+
aliasfor = "dedah"
100+
"#;
101+
const COMMAND: &str = r#"command = "blah"
102+
"#;
103+
#[allow(dead_code)]
104+
const HOST: &str = r#"hostname = "10.0.0.3"
105+
port = 22
106+
pem = "abcdef"
107+
username = "jozias"
108+
[[alias]]
109+
command = "blah"
110+
aliasfor = "dedah"
111+
"#;
112+
113+
#[test]
114+
fn de_alias() -> Fallible<()> {
115+
let expected = Alias {
116+
command: "blah".to_string(),
117+
aliasfor: "dedah".to_string(),
118+
};
119+
let actual: Alias = toml::from_str(ALIAS)?;
120+
assert_eq!(expected, actual);
121+
Ok(())
122+
}
123+
124+
#[test]
125+
fn ser_alias() -> Fallible<()> {
126+
let expected = ALIAS;
127+
let actual = toml::to_string(&Alias {
128+
command: "blah".to_string(),
129+
aliasfor: "dedah".to_string(),
130+
})?;
131+
assert_eq!(expected, actual);
132+
Ok(())
133+
}
134+
135+
#[test]
136+
fn de_command() -> Fallible<()> {
137+
let expected = Command {
138+
command: "blah".to_string(),
139+
};
140+
let actual: Command = toml::from_str(COMMAND)?;
141+
assert_eq!(expected, actual);
142+
Ok(())
143+
}
144+
145+
#[test]
146+
fn ser_command() -> Fallible<()> {
147+
let expected = COMMAND;
148+
let actual = toml::to_string(&Command {
149+
command: "blah".to_string(),
150+
})?;
151+
assert_eq!(expected, actual);
152+
Ok(())
153+
}
154+
}

src/lib.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright (c) 2018 libmussh developers
2+
//
3+
// Licensed under the Apache License, Version 2.0
4+
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5+
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. All files in the project carrying such notice may not be copied,
7+
// modified, or distributed except according to those terms.
8+
9+
//! libmussh
10+
#![feature(crate_visibility_modifier, try_from)]
11+
#![deny(
12+
clippy::all,
13+
clippy::pedantic,
14+
macro_use_extern_crate,
15+
missing_copy_implementations,
16+
missing_debug_implementations,
17+
missing_docs,
18+
trivial_casts,
19+
trivial_numeric_casts,
20+
unused
21+
)]
22+
#![warn(
23+
absolute_paths_not_starting_with_crate,
24+
anonymous_parameters,
25+
bare_trait_objects,
26+
box_pointers,
27+
elided_lifetimes_in_paths,
28+
ellipsis_inclusive_range_patterns,
29+
keyword_idents,
30+
question_mark_macro_sep,
31+
single_use_lifetimes,
32+
unreachable_pub,
33+
unsafe_code,
34+
unused_extern_crates,
35+
unused_import_braces,
36+
unused_labels,
37+
unused_lifetimes,
38+
unused_qualifications,
39+
unused_results,
40+
variant_size_differences
41+
)]
42+
#![allow(clippy::stutter)]
43+
#![doc(html_root_url = "https://docs.rs/libmussh/0.1.0")]
44+
45+
mod config;
46+
mod multiplex;
47+
mod utils;
48+
49+
pub use self::multiplex::Multiplex;
50+
pub use self::utils::as_set;

src/multiplex.rs

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (c) 2018 libmussh developers
2+
//
3+
// Licensed under the Apache License, Version 2.0
4+
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5+
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6+
// option. All files in the project carrying such notice may not be copied,
7+
// modified, or distributed except according to those terms.
8+
9+
//! Multiplex commands over hosts.
10+
use clap::ArgMatches;
11+
use crate::utils;
12+
use getset::{Getters, Setters};
13+
use indexmap::IndexSet;
14+
15+
/// Multiplex struct
16+
#[derive(Clone, Debug, Default, Getters, Eq, PartialEq, Setters)]
17+
pub struct Multiplex {
18+
/// Hosts
19+
hosts: IndexSet<String>,
20+
/// Host that need to complete `sync_commands` before run on
21+
/// other hosts
22+
sync_hosts: IndexSet<String>,
23+
/// Commands
24+
commands: IndexSet<String>,
25+
///s Commands that need to be run on `sync_hosts` before run
26+
/// on other hosts
27+
sync_commands: IndexSet<String>,
28+
}
29+
30+
impl<'a> From<&'a ArgMatches<'a>> for Multiplex {
31+
fn from(matches: &'a ArgMatches<'a>) -> Self {
32+
let mut run = Self::default();
33+
run.hosts = utils::as_set(
34+
matches
35+
.values_of("hosts")
36+
.map_or_else(|| vec![], utils::map_vals),
37+
);
38+
run.sync_hosts = utils::as_set(
39+
matches
40+
.values_of("sync_hosts")
41+
.map_or_else(|| vec![], utils::map_vals),
42+
);
43+
run.commands = utils::as_set(
44+
matches
45+
.values_of("commands")
46+
.map_or_else(|| vec![], utils::map_vals),
47+
);
48+
run.sync_commands = utils::as_set(
49+
matches
50+
.values_of("sync_commands")
51+
.map_or_else(|| vec![], utils::map_vals),
52+
);
53+
run
54+
}
55+
}
56+
57+
#[cfg(test)]
58+
mod tests {
59+
use super::Multiplex;
60+
use clap::{App, Arg};
61+
use crate::utils::as_set;
62+
use failure::Fallible;
63+
64+
fn test_cli<'a, 'b>() -> App<'a, 'b> {
65+
App::new(env!("CARGO_PKG_NAME"))
66+
.arg(
67+
Arg::with_name("hosts")
68+
.short("h")
69+
.long("hosts")
70+
.use_delimiter(true),
71+
)
72+
.arg(
73+
Arg::with_name("commands")
74+
.short("c")
75+
.long("commands")
76+
.use_delimiter(true),
77+
)
78+
.arg(
79+
Arg::with_name("sync_hosts")
80+
.short("s")
81+
.long("sync_hosts")
82+
.use_delimiter(true),
83+
)
84+
.arg(
85+
Arg::with_name("sync_commands")
86+
.short("y")
87+
.long("sync_commands")
88+
.use_delimiter(true),
89+
)
90+
}
91+
92+
#[test]
93+
fn hosts_from_cli() -> Fallible<()> {
94+
let mut expected = Multiplex::default();
95+
expected.hosts = as_set(
96+
vec!["m1", "m2", "m3"]
97+
.iter()
98+
.map(|v| v.to_string())
99+
.collect::<Vec<String>>(),
100+
);
101+
let cli = vec!["test", "-h", "m1,m2,m3,m1,m3"];
102+
let matches = test_cli().get_matches_from_safe(cli)?;
103+
assert_eq!(Multiplex::from(&matches), expected);
104+
Ok(())
105+
}
106+
107+
#[test]
108+
fn sync_hosts_from_cli() -> Fallible<()> {
109+
let mut expected = Multiplex::default();
110+
expected.sync_hosts = as_set(
111+
vec!["m1", "m2", "m3"]
112+
.iter()
113+
.map(|v| v.to_string())
114+
.collect::<Vec<String>>(),
115+
);
116+
let cli = vec!["test", "-s", "m1,m2,m3,m1,m3"];
117+
let matches = test_cli().get_matches_from_safe(cli)?;
118+
assert_eq!(Multiplex::from(&matches), expected);
119+
Ok(())
120+
}
121+
122+
#[test]
123+
fn commands_from_cli() -> Fallible<()> {
124+
let mut expected = Multiplex::default();
125+
expected.commands = as_set(
126+
vec!["foo", "bar", "baz"]
127+
.iter()
128+
.map(|v| v.to_string())
129+
.collect::<Vec<String>>(),
130+
);
131+
let cli = vec!["test", "-c", "foo,bar,foo,foo,baz,bar"];
132+
let matches = test_cli().get_matches_from_safe(cli)?;
133+
assert_eq!(Multiplex::from(&matches), expected);
134+
Ok(())
135+
}
136+
137+
#[test]
138+
fn sync_commands_from_cli() -> Fallible<()> {
139+
let mut expected = Multiplex::default();
140+
expected.sync_commands = as_set(
141+
vec!["foo", "bar", "baz"]
142+
.iter()
143+
.map(|v| v.to_string())
144+
.collect::<Vec<String>>(),
145+
);
146+
let cli = vec!["test", "-y", "foo,bar,foo,foo,baz,bar"];
147+
let matches = test_cli().get_matches_from_safe(cli)?;
148+
assert_eq!(Multiplex::from(&matches), expected);
149+
Ok(())
150+
}
151+
152+
}

0 commit comments

Comments
 (0)