diff --git a/src/server/config.rs b/src/server/config.rs index 984068b..dae7c45 100644 --- a/src/server/config.rs +++ b/src/server/config.rs @@ -39,7 +39,7 @@ pub struct Project { /* * Used for optionally defining an inline Yml configuration */ - inline: Option, + pub inline: Option, pub filename: Option, #[serde(default = "default_scm", with = "serde_yaml::with::singleton_map")] pub scm: Scm, @@ -58,13 +58,23 @@ fn default_scm() -> Scm { * Loaded meaning the server has pinged the agent and gotten necessary bootstrap * information */ -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct Agent { pub name: String, pub url: Url, pub capabilities: Vec, } +impl Default for Agent { + fn default() -> Self { + Self { + name: "default-agent".into(), + url: Url::parse("http://example.com").unwrap(), + capabilities: vec![], + } + } +} + impl Agent { pub fn new(name: String, url: Url, capabilities: Vec) -> Self { Self { @@ -82,18 +92,22 @@ impl Agent { // data: data).await.unwrap_or("".into()) } + /* + * Determine if this agent can meet the specified needs + */ pub fn can_meet(&self, needs: &Vec) -> bool { - // TODO: Improve the performance of this by reducing the clones - let mut needs = needs.clone(); - needs.sort(); - - let mut capabilities: Vec = self + let capabilities: Vec = self .capabilities .iter() .map(|c| c.name.to_lowercase()) .collect(); - capabilities.sort(); - capabilities == needs + + for need in needs { + if !capabilities.contains(&need) { + return false; + } + } + true } } @@ -202,6 +216,7 @@ fn merge_yaml(a: &mut serde_yaml::Value, b: serde_yaml::Value) { mod tests { use super::*; use std::path::PathBuf; + use synchronik::Capability; #[test] fn test_serverconfig_from_filepath() { @@ -307,4 +322,71 @@ projects: } } } + + #[test] + fn agent_can_meet_false() { + let needs: Vec = vec!["rspec".into(), "git".into(), "dotnet".into()]; + let capabilities = vec![Capability::with_name("rustc")]; + let agent = Agent { + name: "test".into(), + url: Url::parse("http://localhost").unwrap(), + capabilities, + }; + assert_eq!(false, agent.can_meet(&needs)); + } + + #[test] + fn agent_can_meet_true() { + let needs: Vec = vec!["dotnet".into()]; + let capabilities = vec![Capability::with_name("dotnet")]; + let agent = Agent { + name: "test".into(), + url: Url::parse("http://localhost").unwrap(), + capabilities, + }; + assert!(agent.can_meet(&needs)); + } + + #[test] + fn agent_can_meet_false_multiple() { + let needs: Vec = vec!["rspec".into(), "git".into(), "dotnet".into()]; + let capabilities = vec![Capability::with_name("dotnet")]; + let agent = Agent { + name: "test".into(), + url: Url::parse("http://localhost").unwrap(), + capabilities, + }; + assert_eq!(false, agent.can_meet(&needs)); + } + + #[test] + fn agent_can_meet_true_multiple() { + let needs: Vec = vec!["rspec".into(), "dotnet".into()]; + let capabilities = vec![ + Capability::with_name("dotnet"), + Capability::with_name("rspec"), + ]; + let agent = Agent { + name: "test".into(), + url: Url::parse("http://localhost").unwrap(), + capabilities, + }; + assert!(agent.can_meet(&needs)); + } + + #[test] + fn agent_can_meet_intersection() { + let needs: Vec = vec!["git".into()]; + let capabilities = vec![ + Capability::with_name("dotnet"), + Capability::with_name("git"), + Capability::with_name("rspec"), + ]; + let agent = Agent { + name: "test".into(), + url: Url::parse("http://localhost").unwrap(), + capabilities, + }; + assert!(agent.can_meet(&needs)); + } } diff --git a/src/server/main.rs b/src/server/main.rs index 282f096..57b040a 100644 --- a/src/server/main.rs +++ b/src/server/main.rs @@ -171,60 +171,3 @@ async fn main() -> Result<(), tide::Error> { app.listen(opts.listen).await?; Ok(()) } - -#[cfg(test)] -mod tests { - use super::*; - use synchronik::*; - - #[test] - fn agent_can_meet_false() { - let needs: Vec = vec!["rspec".into(), "git".into(), "dotnet".into()]; - let capabilities = vec![Capability::with_name("rustc")]; - let agent = Agent { - name: "test".into(), - url: Url::parse("http://localhost").unwrap(), - capabilities, - }; - assert_eq!(false, agent.can_meet(&needs)); - } - - #[test] - fn agent_can_meet_true() { - let needs: Vec = vec!["dotnet".into()]; - let capabilities = vec![Capability::with_name("dotnet")]; - let agent = Agent { - name: "test".into(), - url: Url::parse("http://localhost").unwrap(), - capabilities, - }; - assert!(agent.can_meet(&needs)); - } - - #[test] - fn agent_can_meet_false_multiple() { - let needs: Vec = vec!["rspec".into(), "git".into(), "dotnet".into()]; - let capabilities = vec![Capability::with_name("dotnet")]; - let agent = Agent { - name: "test".into(), - url: Url::parse("http://localhost").unwrap(), - capabilities, - }; - assert_eq!(false, agent.can_meet(&needs)); - } - - #[test] - fn agent_can_meet_true_multiple() { - let needs: Vec = vec!["rspec".into(), "dotnet".into()]; - let capabilities = vec![ - Capability::with_name("dotnet"), - Capability::with_name("rspec"), - ]; - let agent = Agent { - name: "test".into(), - url: Url::parse("http://localhost").unwrap(), - capabilities, - }; - assert!(agent.can_meet(&needs)); - } -} diff --git a/src/server/routes.rs b/src/server/routes.rs index 6f41b5e..d90523a 100644 --- a/src/server/routes.rs +++ b/src/server/routes.rs @@ -49,6 +49,7 @@ pub async fn project(req: Request>) -> Result { pub mod api { use crate::config::{Scm, Yml}; + use crate::Agent; use crate::AppState; use log::*; use serde::Deserialize; @@ -74,7 +75,16 @@ pub mod api { if let Some(project) = state.config.projects.get(&name) { match &project.scm { - Scm::Nonexistent => {} + Scm::Nonexistent => { + info!("Nonexistent SCM, using inline configuration for {}", name); + info!("configuration: {:?}", project.inline); + if let Some(config) = &project.inline { + execute_commands(config, &state.agents).await?; + if let Some(red) = &next.next { + return Ok(tide::Redirect::new(red).into()); + } + } + } Scm::GitHub { owner, repo, @@ -91,36 +101,9 @@ pub mod api { .await?; let config_file: Yml = serde_yaml::from_str(&res.text().await?)?; debug!("text: {:?}", config_file); - - for agent in &state.agents { - if agent.can_meet(&config_file.needs) { - debug!("agent: {:?} can meet our needs", agent); - let commands: Vec = config_file - .commands - .iter() - .map(|c| synchronik::Command::with_script(c)) - .collect(); - let commands = synchronik::CommandRequest { commands }; - let client = reqwest::Client::new(); - let _res = client - .put( - agent - .url - .join("/api/v1/execute") - .expect("Failed to join execute URL"), - ) - .json(&commands) - .send() - .await?; - - if let Some(red) = &next.next { - return Ok(tide::Redirect::new(red).into()); - } - - return Ok( - json!({ "msg": format!("Executing on {}", &agent.url) }).into() - ); - } + execute_commands(&config_file, &state.agents).await?; + if let Some(red) = &next.next { + return Ok(tide::Redirect::new(red).into()); } } } @@ -128,4 +111,32 @@ pub mod api { } Ok(Response::new(StatusCode::InternalServerError)) } + + async fn execute_commands(config: &Yml, agents: &Vec) -> Result<(), tide::Error> { + debug!("working {:?}", config); + for agent in agents { + debug!("agent: {:?}", agent); + if agent.can_meet(&config.needs) { + debug!("agent: {:?} can meet our needs", agent); + let commands: Vec = config + .commands + .iter() + .map(|c| synchronik::Command::with_script(c)) + .collect(); + let commands = synchronik::CommandRequest { commands }; + let client = reqwest::Client::new(); + let _res = client + .put( + agent + .url + .join("/api/v1/execute") + .expect("Failed to join execute URL"), + ) + .json(&commands) + .send() + .await?; + } + } + Ok(()) + } } diff --git a/views/index.hbs b/views/index.hbs index cbebb8c..0e08c94 100644 --- a/views/index.hbs +++ b/views/index.hbs @@ -50,7 +50,7 @@
- +
diff --git a/views/project.hbs b/views/project.hbs index ade1387..6d1b0eb 100644 --- a/views/project.hbs +++ b/views/project.hbs @@ -1,7 +1,7 @@ - Janky - {{name}} + Synchronik - {{name}}