Correctly match capabilities when the agent has more than the project
This commit is contained in:
parent
0d00fb4cfd
commit
23c2d64eef
@ -39,7 +39,7 @@ pub struct Project {
|
||||
/*
|
||||
* Used for optionally defining an inline Yml configuration
|
||||
*/
|
||||
inline: Option<Yml>,
|
||||
pub inline: Option<Yml>,
|
||||
pub filename: Option<String>,
|
||||
#[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<synchronik::Capability>,
|
||||
}
|
||||
|
||||
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<synchronik::Capability>) -> 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<String>) -> bool {
|
||||
// TODO: Improve the performance of this by reducing the clones
|
||||
let mut needs = needs.clone();
|
||||
needs.sort();
|
||||
|
||||
let mut capabilities: Vec<String> = self
|
||||
let capabilities: Vec<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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<String> = 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));
|
||||
}
|
||||
}
|
||||
|
@ -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<String> = 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<String> = 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<String> = 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<String> = 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));
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ pub async fn project(req: Request<AppState<'_>>) -> Result<Body, tide::Error> {
|
||||
|
||||
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<synchronik::Command> = 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<Agent>) -> 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<synchronik::Command> = 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(())
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@
|
||||
<td>
|
||||
<form method="POST" action="/api/v1/projects/{{this.name}}">
|
||||
<input type="hidden" name="next" value="/project/{{this.name}}"/>
|
||||
<input type="image" value="Execute" src="/static/icons/actions/view-refresh.svg"/>
|
||||
<input type="image" title="Execute" value="Execute" src="/static/icons/actions/view-refresh.svg"/>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Janky - {{name}}</title>
|
||||
<title>Synchronik - {{name}}</title>
|
||||
<link type="text/css" rel="stylesheet" href="/static/bootstrap.min.css"/>
|
||||
<script src="/static/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user