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
|
* Used for optionally defining an inline Yml configuration
|
||||||
*/
|
*/
|
||||||
inline: Option<Yml>,
|
pub inline: Option<Yml>,
|
||||||
pub filename: Option<String>,
|
pub filename: Option<String>,
|
||||||
#[serde(default = "default_scm", with = "serde_yaml::with::singleton_map")]
|
#[serde(default = "default_scm", with = "serde_yaml::with::singleton_map")]
|
||||||
pub scm: Scm,
|
pub scm: Scm,
|
||||||
|
@ -58,13 +58,23 @@ fn default_scm() -> Scm {
|
||||||
* Loaded meaning the server has pinged the agent and gotten necessary bootstrap
|
* Loaded meaning the server has pinged the agent and gotten necessary bootstrap
|
||||||
* information
|
* information
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
pub struct Agent {
|
pub struct Agent {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub url: Url,
|
pub url: Url,
|
||||||
pub capabilities: Vec<synchronik::Capability>,
|
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 {
|
impl Agent {
|
||||||
pub fn new(name: String, url: Url, capabilities: Vec<synchronik::Capability>) -> Self {
|
pub fn new(name: String, url: Url, capabilities: Vec<synchronik::Capability>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -82,18 +92,22 @@ impl Agent {
|
||||||
// data: data).await.unwrap_or("".into())
|
// data: data).await.unwrap_or("".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Determine if this agent can meet the specified needs
|
||||||
|
*/
|
||||||
pub fn can_meet(&self, needs: &Vec<String>) -> bool {
|
pub fn can_meet(&self, needs: &Vec<String>) -> bool {
|
||||||
// TODO: Improve the performance of this by reducing the clones
|
let capabilities: Vec<String> = self
|
||||||
let mut needs = needs.clone();
|
|
||||||
needs.sort();
|
|
||||||
|
|
||||||
let mut capabilities: Vec<String> = self
|
|
||||||
.capabilities
|
.capabilities
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| c.name.to_lowercase())
|
.map(|c| c.name.to_lowercase())
|
||||||
.collect();
|
.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 {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use synchronik::Capability;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_serverconfig_from_filepath() {
|
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?;
|
app.listen(opts.listen).await?;
|
||||||
Ok(())
|
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 {
|
pub mod api {
|
||||||
use crate::config::{Scm, Yml};
|
use crate::config::{Scm, Yml};
|
||||||
|
use crate::Agent;
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use log::*;
|
use log::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -74,7 +75,16 @@ pub mod api {
|
||||||
|
|
||||||
if let Some(project) = state.config.projects.get(&name) {
|
if let Some(project) = state.config.projects.get(&name) {
|
||||||
match &project.scm {
|
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 {
|
Scm::GitHub {
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
|
@ -91,36 +101,9 @@ pub mod api {
|
||||||
.await?;
|
.await?;
|
||||||
let config_file: Yml = serde_yaml::from_str(&res.text().await?)?;
|
let config_file: Yml = serde_yaml::from_str(&res.text().await?)?;
|
||||||
debug!("text: {:?}", config_file);
|
debug!("text: {:?}", config_file);
|
||||||
|
execute_commands(&config_file, &state.agents).await?;
|
||||||
for agent in &state.agents {
|
if let Some(red) = &next.next {
|
||||||
if agent.can_meet(&config_file.needs) {
|
return Ok(tide::Redirect::new(red).into());
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,4 +111,32 @@ pub mod api {
|
||||||
}
|
}
|
||||||
Ok(Response::new(StatusCode::InternalServerError))
|
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>
|
<td>
|
||||||
<form method="POST" action="/api/v1/projects/{{this.name}}">
|
<form method="POST" action="/api/v1/projects/{{this.name}}">
|
||||||
<input type="hidden" name="next" value="/project/{{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>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Janky - {{name}}</title>
|
<title>Synchronik - {{name}}</title>
|
||||||
<link type="text/css" rel="stylesheet" href="/static/bootstrap.min.css"/>
|
<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>
|
<script src="/static/bootstrap.bundle.min.js" integrity="sha384-w76AqPfDkMBDXo30jS1Sgez6pr3x5MlQ1ZAGC+nuZB+EYdgRZgiwxhTBTkF7CXvN" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue