master
Zoran Zaric 5 years ago
commit 321dcb6877

3
.gitignore vendored

@ -0,0 +1,3 @@
/target
**/*.rs.bk
Cargo.lock

@ -0,0 +1,16 @@
[package]
name = "deploy-config"
version = "0.1.0"
authors = ["Zoran Zaric <zz@zoranzaric.de>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0.99", features = [ "derive" ] }
ssh2 = "0.3.3"
url = "2.1.0"
serde_yaml = "0.8.9"
serde_json = "1.0.40"
serde_with = "1.3.1"
reqwest = "0.9.20"

@ -0,0 +1,298 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{self, Display};
use std::path::PathBuf;
use std::process::Command;
use std::str::FromStr;
use url::Url;
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct DeployConfig {
tasks: Vec<Task>,
}
pub trait Deploy {
fn deploy(&self) -> Result<(), ()>;
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub enum Task {
#[serde(rename = "copy")]
Copy {
source: String,
#[serde(with = "serde_with::rust::display_fromstr")]
target: Target,
},
#[serde(rename = "trigger")]
Trigger {
#[serde(with = "serde_with::rust::display_fromstr")]
method: TriggerMethod,
url: String,
parameters: Vec<TriggerParameter>,
},
}
impl Deploy for Task {
fn deploy(&self) -> Result<(), ()> {
match self {
Self::Copy { source, target } => match target.deploy(PathBuf::from(source)) {
Ok(_) => Ok(()),
Err(_) => Err(()),
},
Self::Trigger {
method,
url,
parameters,
} => match method {
&TriggerMethod::HttpGet => {
let params: Vec<_> = parameters
.iter()
.map(|ref p| (p.name.clone(), p.value.clone()))
.collect();
let resp = reqwest::Client::new().get(url).query(&params).send();
match resp {
Ok(resp) => {
if resp.status().is_success() {
Ok(())
} else {
Err(())
}
}
Err(_) => Err(()),
}
}
&TriggerMethod::HttpPost => {
let params: HashMap<_, _> = parameters
.iter()
.map(|ref p| (p.name.clone(), p.value.clone()))
.collect();
let resp = reqwest::Client::new().post(url).form(&params).send();
match resp {
Ok(resp) => {
if resp.status().is_success() {
Ok(())
} else {
Err(())
}
}
Err(_) => Err(()),
}
}
},
}
}
}
#[derive(Debug, PartialEq)]
pub enum Target {
Scp {
username: Option<String>,
host: String,
port: Option<u16>,
path: Option<String>,
},
}
impl FromStr for Target {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match Url::parse(s) {
Ok(url) => match url.scheme() {
"scp" => {
let username = if url.username().is_empty() {
None
} else {
Some(url.username().into())
};
let host = match url.host_str() {
Some(host_str) => host_str.into(),
None => return Err("Host is missing".into()),
};
let port = url.port();
let path = if url.path().is_empty() {
None
} else {
Some(url.path().into())
};
Ok(Self::Scp {
username,
host,
port,
path,
})
}
scheme => Err(format!("Unsupported scheme \"{}\"", scheme)),
},
Err(e) => Err(format!("{}", e)),
}
}
}
impl Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// FIXME
write!(f, "foo")
}
}
impl Target {
pub fn deploy(&self, source: PathBuf) -> Result<(), std::io::Error> {
match self {
Self::Scp {
username,
host,
port,
path,
} => {
let mut cmd = Command::new("scp");
let source: String = source.to_string_lossy().into();
let cmd = cmd.arg(source);
let cmd = if let Some(port) = port {
cmd.arg("-P").arg(format!("{}", port))
} else {
cmd
};
let mut s = String::new();
if let Some(username) = username {
s.push_str(&format!("{}@", username));
}
s.push_str(&format!("{}:", host));
if let Some(path) = path {
s.push_str(path);
}
match cmd.output() {
Ok(output) => {
if output.status.success() {
Ok(())
} else {
Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!(
"scp command failed with exit code {:?}",
output.status.code()
),
))
}
}
Err(e) => Err(e),
}
}
}
}
}
#[derive(Debug, PartialEq)]
pub enum TriggerMethod {
HttpGet,
HttpPost,
}
impl FromStr for TriggerMethod {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"http-get" => Ok(Self::HttpGet),
"http-post" => Ok(Self::HttpPost),
trigger_method => Err(format!("Unsupported trigger-method \"{}\"", trigger_method)),
}
}
}
impl Display for TriggerMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::HttpGet => write!(f, "http-get"),
Self::HttpPost => write!(f, "http-post"),
}
}
}
#[derive(Debug, PartialEq, Deserialize, Serialize)]
pub struct TriggerParameter {
name: String,
value: String,
}
#[cfg(test)]
mod test {
use super::*;
#[test]
pub fn test_parse_target() {
let target = dbg!(Target::from_str("scp://foo@bar:/vol1/asdf")).unwrap();
assert_eq!(
Target::Scp {
username: Some("foo".into()),
host: "bar".into(),
port: None,
path: Some("/vol1/asdf".into()),
},
target
);
}
#[test]
pub fn test_parse() {
let s = r#"---
tasks:
- copy:
source: hermes.war
target: scp://compaxdev@delivery.comi.run:/vol1/delivery/aax2/microservices/tuadev/hermes/hermes.war
- trigger:
method: http-get
url: http://172.29.110.20:8080/job/00_congo_neue_tuad_microservices_deployment_trigger_compax/buildWithParameters
parameters:
- name: token
value: up9ul9oph5augh2oz8oak3aiSeemaxa
- name: ms_visibility
value: cob2b
- name: warfile
value: hermes
"#;
assert_eq!(
DeployConfig {
tasks: vec![Task::Copy {
source: "hermes.war".into(),
target: Target::Scp {
username: Some("compaxdev".into()),
host: "delivery.comi.run".into(),
port: None,
path: Some("/vol1/delivery/aax2/microservices/tuadev/hermes/hermes.war".into()),
}
}, Task::Trigger {
method: TriggerMethod::HttpGet,
url: "http://172.29.110.20:8080/job/00_congo_neue_tuad_microservices_deployment_trigger_compax/buildWithParameters".into(),
parameters: vec![
TriggerParameter {
name: "token".into(),
value: "up9ul9oph5augh2oz8oak3aiSeemaxa".into(),
},
TriggerParameter {
name: "ms_visibility".into(),
value: "cob2b".into(),
},
TriggerParameter {
name: "warfile".into(),
value: "hermes".into(),
},
],
}],
},
serde_yaml::from_str(&s).unwrap()
);
}
}
Loading…
Cancel
Save