Compare commits
4 Commits
9506a78da9
...
c02f87ae19
Author | SHA1 | Date |
---|---|---|
R Tyler Croy | c02f87ae19 | |
R Tyler Croy | 1339c8c2e8 | |
R Tyler Croy | dbfd92614a | |
R Tyler Croy | 6d8e7d1b6b |
|
@ -36,7 +36,7 @@ fn locate_on_path(bin: &str) -> Option<PathBuf> {
|
||||||
let full_path = path_dir.join(bin);
|
let full_path = path_dir.join(bin);
|
||||||
if full_path.is_file() {
|
if full_path.is_file() {
|
||||||
// TODO: Should check to see if the path is executable
|
// TODO: Should check to see if the path is executable
|
||||||
return Some(PathBuf::from(full_path));
|
return Some(full_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ fn locate_on_path(bin: &str) -> Option<PathBuf> {
|
||||||
/*
|
/*
|
||||||
* Git capability will determine whether `git` exists on the system
|
* Git capability will determine whether `git` exists on the system
|
||||||
*/
|
*/
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||||
#[serde(tag = "name")]
|
#[serde(tag = "name")]
|
||||||
pub struct Git {
|
pub struct Git {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -65,7 +65,7 @@ impl Capability for Git {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||||
#[serde(tag = "name")]
|
#[serde(tag = "name")]
|
||||||
pub struct Cargo {
|
pub struct Cargo {
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct Capability {
|
pub struct Capability {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
path: PathBuf,
|
path: PathBuf,
|
||||||
|
@ -24,12 +24,12 @@ impl Capability {
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||||
struct CapsRequest {}
|
struct CapsRequest {}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct CapsResponse {
|
pub struct CapsResponse {
|
||||||
pub caps: Vec<Capability>,
|
pub caps: Vec<Capability>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct Command {
|
pub struct Command {
|
||||||
pub script: String,
|
pub script: String,
|
||||||
}
|
}
|
||||||
|
@ -42,12 +42,12 @@ impl Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct CommandRequest {
|
pub struct CommandRequest {
|
||||||
pub commands: Vec<Command>,
|
pub commands: Vec<Command>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub struct CommandResponse {
|
pub struct CommandResponse {
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub stream: Option<Url>,
|
pub stream: Option<Url>,
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Representation of the Janky YAML format
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct JankyYml {
|
||||||
|
pub needs: Vec<String>,
|
||||||
|
pub commands: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub enum Scm {
|
||||||
|
GitHub {
|
||||||
|
owner: String,
|
||||||
|
repo: String,
|
||||||
|
#[serde(rename = "ref")]
|
||||||
|
scm_ref: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
pub struct Project {
|
||||||
|
description: String,
|
||||||
|
pub filename: String,
|
||||||
|
#[serde(with = "serde_yaml::with::singleton_map")]
|
||||||
|
pub scm: Scm,
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal representation of an Agent that has been "loaded" by the server
|
||||||
|
*
|
||||||
|
* Loaded meaning the server has pinged the agent and gotten necessary bootstrap
|
||||||
|
* information
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct Agent {
|
||||||
|
pub name: String,
|
||||||
|
pub url: Url,
|
||||||
|
pub capabilities: Vec<janky::Capability>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Agent {
|
||||||
|
pub fn new(name: String, url: Url, capabilities: Vec<janky::Capability>) -> Self {
|
||||||
|
Self {
|
||||||
|
name,
|
||||||
|
url,
|
||||||
|
capabilities,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_compact(&self, _state: &AppState<'_>) -> String {
|
||||||
|
"".into()
|
||||||
|
//let data = serde_json::to_str(self).unwrap_or(serde_json::Value::Array);
|
||||||
|
|
||||||
|
//state.render("views/components/agent/compact.hbs",
|
||||||
|
// data: data).await.unwrap_or("".into())
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
.capabilities
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.name.to_lowercase())
|
||||||
|
.collect();
|
||||||
|
capabilities.sort();
|
||||||
|
capabilities == needs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct AgentConfig {
|
||||||
|
pub url: Url,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub agents: HashMap<String, AgentConfig>,
|
||||||
|
pub projects: HashMap<String, Project>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerConfig {
|
||||||
|
pub fn has_project(&self, name: &str) -> bool {
|
||||||
|
self.projects.contains_key(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServerConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
agents: HashMap::default(),
|
||||||
|
projects: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,268 +0,0 @@
|
||||||
/*
|
|
||||||
* The DAO module contains all the necessary structs for interacting with the database
|
|
||||||
*/
|
|
||||||
|
|
||||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
|
||||||
use serde::Serialize;
|
|
||||||
use sqlx::sqlite::SqliteQueryResult;
|
|
||||||
use sqlx::{Sqlite, SqlitePool, Transaction};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct Project {
|
|
||||||
uuid: String,
|
|
||||||
name: String,
|
|
||||||
created_at: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Project {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
name: "Default Project".into(),
|
|
||||||
created_at: Utc::now().naive_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Project {
|
|
||||||
pub fn new(name: &str) -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
name: name.into(),
|
|
||||||
created_at: Utc::now().naive_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn by_name(name: &str, pool: &SqlitePool) -> Result<Project, sqlx::Error> {
|
|
||||||
sqlx::query_as!(Project, "SELECT * FROM projects WHERE name = ?", name)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list(pool: &SqlitePool) -> Result<Vec<Project>, sqlx::Error> {
|
|
||||||
sqlx::query_as!(Project, "SELECT * FROM projects")
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn create(
|
|
||||||
project: &Project,
|
|
||||||
tx: &SqlitePool,
|
|
||||||
) -> Result<SqliteQueryResult, sqlx::Error> {
|
|
||||||
sqlx::query!(
|
|
||||||
r#"INSERT INTO projects (uuid, name, created_at) VALUES (?, ?, ?)"#,
|
|
||||||
project.uuid,
|
|
||||||
project.name,
|
|
||||||
project.created_at,
|
|
||||||
)
|
|
||||||
.execute(tx)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct Run {
|
|
||||||
run: RunRow,
|
|
||||||
project: Project,
|
|
||||||
scm_info: ScmInfo,
|
|
||||||
definition: RunDefinition,
|
|
||||||
}
|
|
||||||
/* The basic implementation for Run has all the database access operations
|
|
||||||
*/
|
|
||||||
impl Run {
|
|
||||||
/*
|
|
||||||
* Create the Run in the database given the appropriate struct
|
|
||||||
*/
|
|
||||||
async fn create(run: &Run, pool: &SqlitePool) -> Result<(), sqlx::Error> {
|
|
||||||
let mut tx = pool.begin().await?;
|
|
||||||
sqlx::query!(
|
|
||||||
r#"INSERT INTO scm_info (uuid, git_url, ref, created_at) VALUES (?, ?, ?, ?)"#,
|
|
||||||
run.scm_info.uuid,
|
|
||||||
run.scm_info.git_url,
|
|
||||||
run.scm_info.r#ref,
|
|
||||||
run.scm_info.created_at
|
|
||||||
)
|
|
||||||
.execute(&mut tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
|
||||||
r#"INSERT INTO run_definition (uuid, definition, created_at) VALUES (?, ?, ?)"#,
|
|
||||||
run.definition.uuid,
|
|
||||||
run.definition.definition,
|
|
||||||
run.definition.created_at,
|
|
||||||
)
|
|
||||||
.execute(&mut tx)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
sqlx::query!(
|
|
||||||
"INSERT INTO runs (uuid, num, status, log_url, definition, scm_info, project) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
run.run.uuid,
|
|
||||||
run.run.num,
|
|
||||||
run.run.status,
|
|
||||||
run.run.log_url,
|
|
||||||
run.definition.uuid,
|
|
||||||
run.scm_info.uuid,
|
|
||||||
run.project.uuid,
|
|
||||||
)
|
|
||||||
.execute(&mut tx)
|
|
||||||
.await?;
|
|
||||||
tx.commit().await
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Allow finding a Run by the given Uuid
|
|
||||||
*/
|
|
||||||
async fn find_by(uuid: &str, pool: &SqlitePool) -> Result<Run, sqlx::Error> {
|
|
||||||
let row = sqlx::query_as!(RunRow, "SELECT * FROM runs WHERE uuid = ?", uuid)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
let scm_info = sqlx::query_as!(
|
|
||||||
ScmInfo,
|
|
||||||
"SELECT * FROM scm_info WHERE uuid = ?",
|
|
||||||
row.scm_info
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let project = sqlx::query_as!(
|
|
||||||
Project,
|
|
||||||
"SELECT * FROM projects WHERE uuid = ?",
|
|
||||||
row.project
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let definition = sqlx::query_as!(
|
|
||||||
RunDefinition,
|
|
||||||
"SELECT * FROM run_definition WHERE uuid = ?",
|
|
||||||
row.definition
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Run {
|
|
||||||
run: row,
|
|
||||||
scm_info,
|
|
||||||
project,
|
|
||||||
definition,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Run {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
run: RunRow::default(),
|
|
||||||
project: Project::default(),
|
|
||||||
scm_info: ScmInfo::default(),
|
|
||||||
definition: RunDefinition::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The RunRow is the struct for the deserialization/serialization of the runs table
|
|
||||||
* unfortunately this is a little bit of misdirection due to the inability to make
|
|
||||||
* nested structs with sqlx work well
|
|
||||||
*/
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct RunRow {
|
|
||||||
// Unique identifier for the Run
|
|
||||||
uuid: String,
|
|
||||||
// User-identifiable number for the Run, monotonically increasing
|
|
||||||
num: i64,
|
|
||||||
// Unix status return code from the run, zero is success
|
|
||||||
status: i64,
|
|
||||||
// Globally resolvable URL for fetching raw logs
|
|
||||||
log_url: String,
|
|
||||||
// Foreign key to projects
|
|
||||||
project: String,
|
|
||||||
// Foreign key to run_definition
|
|
||||||
definition: String,
|
|
||||||
// Foreign key to scm_info
|
|
||||||
scm_info: String,
|
|
||||||
created_at: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RunRow {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
num: 42,
|
|
||||||
status: 0,
|
|
||||||
log_url: "https://example.com/console.log".into(),
|
|
||||||
definition: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
project: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
scm_info: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
created_at: Utc::now().naive_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct ScmInfo {
|
|
||||||
uuid: String,
|
|
||||||
git_url: String,
|
|
||||||
r#ref: String,
|
|
||||||
created_at: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ScmInfo {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
git_url: "https://example.com/some/repo.git".into(),
|
|
||||||
r#ref: "main".into(),
|
|
||||||
created_at: Utc::now().naive_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct RunDefinition {
|
|
||||||
uuid: String,
|
|
||||||
definition: String,
|
|
||||||
created_at: NaiveDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RunDefinition {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: Uuid::new_v4().hyphenated().to_string(),
|
|
||||||
definition: String::new(),
|
|
||||||
created_at: Utc::now().naive_utc(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use sqlx::SqlitePool;
|
|
||||||
|
|
||||||
async fn setup_database() -> SqlitePool {
|
|
||||||
let pool = SqlitePool::connect(":memory:")
|
|
||||||
.await
|
|
||||||
.expect("Failed to setup_database()");
|
|
||||||
sqlx::migrate!()
|
|
||||||
.run(&pool)
|
|
||||||
.await
|
|
||||||
.expect("Failed to run migrations in a test");
|
|
||||||
pool
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_std::test]
|
|
||||||
async fn test_create_a_run() {
|
|
||||||
pretty_env_logger::try_init();
|
|
||||||
let pool = setup_database().await;
|
|
||||||
let project = Project::new("test");
|
|
||||||
Project::create(&project, &pool).await.unwrap();
|
|
||||||
|
|
||||||
let mut run = Run::default();
|
|
||||||
run.project = project;
|
|
||||||
let result = Run::create(&run, &pool).await.unwrap();
|
|
||||||
let fetched_run = Run::find_by(&run.run.uuid, &pool).await.unwrap();
|
|
||||||
assert_eq!(run.run.uuid, fetched_run.run.uuid);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,6 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use async_std::sync::{Arc, RwLock};
|
use async_std::sync::{Arc, RwLock};
|
||||||
|
@ -13,13 +12,16 @@ use dotenv::dotenv;
|
||||||
use gumdrop::Options;
|
use gumdrop::Options;
|
||||||
use handlebars::Handlebars;
|
use handlebars::Handlebars;
|
||||||
use log::*;
|
use log::*;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
mod dao;
|
mod config;
|
||||||
|
mod models;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
|
use crate::config::*;
|
||||||
|
use crate::models::Project;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct AppState<'a> {
|
pub struct AppState<'a> {
|
||||||
pub db: SqlitePool,
|
pub db: SqlitePool,
|
||||||
|
@ -60,87 +62,6 @@ impl AppState<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
|
||||||
struct JankyYml {
|
|
||||||
needs: Vec<String>,
|
|
||||||
commands: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
enum Scm {
|
|
||||||
GitHub {
|
|
||||||
owner: String,
|
|
||||||
repo: String,
|
|
||||||
#[serde(rename = "ref")]
|
|
||||||
scm_ref: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "lowercase")]
|
|
||||||
struct Project {
|
|
||||||
description: String,
|
|
||||||
filename: String,
|
|
||||||
#[serde(with = "serde_yaml::with::singleton_map")]
|
|
||||||
scm: Scm,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Internal representation of an Agent that has been "loaded" by the server
|
|
||||||
*
|
|
||||||
* Loaded meaning the server has pinged the agent and gotten necessary bootstrap
|
|
||||||
* information
|
|
||||||
*/
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
|
||||||
pub struct Agent {
|
|
||||||
name: String,
|
|
||||||
url: Url,
|
|
||||||
capabilities: Vec<janky::Capability>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Agent {
|
|
||||||
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
|
|
||||||
.capabilities
|
|
||||||
.iter()
|
|
||||||
.map(|c| c.name.to_lowercase())
|
|
||||||
.collect();
|
|
||||||
capabilities.sort();
|
|
||||||
capabilities == needs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
struct AgentConfig {
|
|
||||||
url: Url,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
pub struct ServerConfig {
|
|
||||||
agents: HashMap<String, AgentConfig>,
|
|
||||||
projects: HashMap<String, Project>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServerConfig {
|
|
||||||
fn has_project(&self, name: &str) -> bool {
|
|
||||||
self.projects.contains_key(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for ServerConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
agents: HashMap::default(),
|
|
||||||
projects: HashMap::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Options)]
|
#[derive(Debug, Options)]
|
||||||
struct ServerOptions {
|
struct ServerOptions {
|
||||||
#[options(help = "print help message")]
|
#[options(help = "print help message")]
|
||||||
|
@ -183,11 +104,11 @@ async fn main() -> Result<(), tide::Error> {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
for name in config.projects.keys() {
|
for name in config.projects.keys() {
|
||||||
match dao::Project::by_name(&name, &pool).await {
|
match Project::by_name(name, &pool).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(sqlx::Error::RowNotFound) => {
|
Err(sqlx::Error::RowNotFound) => {
|
||||||
debug!("Project not found in database, creating: {}", name);
|
debug!("Project not found in database, creating: {}", name);
|
||||||
dao::Project::create(&dao::Project::new(&name), &pool).await?;
|
Project::create(&Project::new(name), &pool).await?;
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return Err(e.into());
|
return Err(e.into());
|
||||||
|
@ -201,11 +122,11 @@ async fn main() -> Result<(), tide::Error> {
|
||||||
.await?
|
.await?
|
||||||
.json()
|
.json()
|
||||||
.await?;
|
.await?;
|
||||||
state.agents.push(Agent {
|
state.agents.push(Agent::new(
|
||||||
name: name.clone(),
|
name.to_string(),
|
||||||
url: agent.url.clone(),
|
agent.url.clone(),
|
||||||
capabilities: response.caps,
|
response.caps,
|
||||||
});
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
mod project;
|
||||||
|
mod run;
|
||||||
|
mod rundefinition;
|
||||||
|
mod runrow;
|
||||||
|
mod scminfo;
|
||||||
|
|
||||||
|
pub use self::project::Project;
|
||||||
|
pub use self::run::Run;
|
||||||
|
pub use self::rundefinition::RunDefinition;
|
||||||
|
pub use self::runrow::RunRow;
|
||||||
|
pub use self::scminfo::ScmInfo;
|
|
@ -0,0 +1,58 @@
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use serde::Serialize;
|
||||||
|
use sqlx::sqlite::SqliteQueryResult;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
pub struct Project {
|
||||||
|
pub uuid: String,
|
||||||
|
pub name: String,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Project {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
name: "Default Project".into(),
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Project {
|
||||||
|
pub fn new(name: &str) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
name: name.into(),
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn by_name(name: &str, pool: &SqlitePool) -> Result<Project, sqlx::Error> {
|
||||||
|
sqlx::query_as!(Project, "SELECT * FROM projects WHERE name = ?", name)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list(pool: &SqlitePool) -> Result<Vec<Project>, sqlx::Error> {
|
||||||
|
sqlx::query_as!(Project, "SELECT * FROM projects")
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create(
|
||||||
|
project: &Project,
|
||||||
|
tx: &SqlitePool,
|
||||||
|
) -> Result<SqliteQueryResult, sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"INSERT INTO projects (uuid, name, created_at) VALUES (?, ?, ?)"#,
|
||||||
|
project.uuid,
|
||||||
|
project.name,
|
||||||
|
project.created_at,
|
||||||
|
)
|
||||||
|
.execute(tx)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
use crate::models::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Run {
|
||||||
|
run: RunRow,
|
||||||
|
project: Project,
|
||||||
|
scm_info: ScmInfo,
|
||||||
|
definition: RunDefinition,
|
||||||
|
}
|
||||||
|
/* The basic implementation for Run has all the database access operations
|
||||||
|
*/
|
||||||
|
impl Run {
|
||||||
|
/*
|
||||||
|
* Create the Run in the database given the appropriate struct
|
||||||
|
*/
|
||||||
|
async fn create(run: &Run, pool: &SqlitePool) -> Result<(), sqlx::Error> {
|
||||||
|
let mut tx = pool.begin().await?;
|
||||||
|
sqlx::query!(
|
||||||
|
r#"INSERT INTO scm_info (uuid, git_url, ref, created_at) VALUES (?, ?, ?, ?)"#,
|
||||||
|
run.scm_info.uuid,
|
||||||
|
run.scm_info.git_url,
|
||||||
|
run.scm_info.r#ref,
|
||||||
|
run.scm_info.created_at
|
||||||
|
)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
r#"INSERT INTO run_definition (uuid, definition, created_at) VALUES (?, ?, ?)"#,
|
||||||
|
run.definition.uuid,
|
||||||
|
run.definition.definition,
|
||||||
|
run.definition.created_at,
|
||||||
|
)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"INSERT INTO runs (uuid, num, status, log_url, definition, scm_info, project) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
run.run.uuid,
|
||||||
|
run.run.num,
|
||||||
|
run.run.status,
|
||||||
|
run.run.log_url,
|
||||||
|
run.definition.uuid,
|
||||||
|
run.scm_info.uuid,
|
||||||
|
run.project.uuid,
|
||||||
|
)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await?;
|
||||||
|
tx.commit().await
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow finding a Run by the given Uuid
|
||||||
|
*/
|
||||||
|
async fn find_by(uuid: &str, pool: &SqlitePool) -> Result<Run, sqlx::Error> {
|
||||||
|
let row = sqlx::query_as!(RunRow, "SELECT * FROM runs WHERE uuid = ?", uuid)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
let scm_info = sqlx::query_as!(
|
||||||
|
ScmInfo,
|
||||||
|
"SELECT * FROM scm_info WHERE uuid = ?",
|
||||||
|
row.scm_info
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let project = sqlx::query_as!(
|
||||||
|
Project,
|
||||||
|
"SELECT * FROM projects WHERE uuid = ?",
|
||||||
|
row.project
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let definition = sqlx::query_as!(
|
||||||
|
RunDefinition,
|
||||||
|
"SELECT * FROM run_definition WHERE uuid = ?",
|
||||||
|
row.definition
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Run {
|
||||||
|
run: row,
|
||||||
|
scm_info,
|
||||||
|
project,
|
||||||
|
definition,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Run {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
run: RunRow::default(),
|
||||||
|
project: Project::default(),
|
||||||
|
scm_info: ScmInfo::default(),
|
||||||
|
definition: RunDefinition::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
async fn setup_database() -> SqlitePool {
|
||||||
|
let pool = SqlitePool::connect(":memory:")
|
||||||
|
.await
|
||||||
|
.expect("Failed to setup_database()");
|
||||||
|
sqlx::migrate!()
|
||||||
|
.run(&pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to run migrations in a test");
|
||||||
|
pool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_std::test]
|
||||||
|
async fn test_create_a_run() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let pool = setup_database().await;
|
||||||
|
let project = crate::models::Project::new("test");
|
||||||
|
Project::create(&project, &pool).await.unwrap();
|
||||||
|
|
||||||
|
let mut run = Run::default();
|
||||||
|
run.project = project;
|
||||||
|
Run::create(&run, &pool).await.unwrap();
|
||||||
|
let fetched_run = Run::find_by(&run.run.uuid, &pool).await.unwrap();
|
||||||
|
assert_eq!(run.run.uuid, fetched_run.run.uuid);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RunDefinition {
|
||||||
|
pub uuid: String,
|
||||||
|
pub definition: String,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RunDefinition {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
definition: String::new(),
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The RunRow is the struct for the deserialization/serialization of the runs table
|
||||||
|
* unfortunately this is a little bit of misdirection due to the inability to make
|
||||||
|
* nested structs with sqlx work well
|
||||||
|
*/
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RunRow {
|
||||||
|
// Unique identifier for the Run
|
||||||
|
pub uuid: String,
|
||||||
|
// User-identifiable number for the Run, monotonically increasing
|
||||||
|
pub num: i64,
|
||||||
|
// Unix status return code from the run, zero is success
|
||||||
|
pub status: i64,
|
||||||
|
// Globally resolvable URL for fetching raw logs
|
||||||
|
pub log_url: String,
|
||||||
|
// Foreign key to projects
|
||||||
|
pub project: String,
|
||||||
|
// Foreign key to run_definition
|
||||||
|
pub definition: String,
|
||||||
|
// Foreign key to scm_info
|
||||||
|
pub scm_info: String,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RunRow {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
num: 42,
|
||||||
|
status: 0,
|
||||||
|
log_url: "https://example.com/console.log".into(),
|
||||||
|
definition: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
project: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
scm_info: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ScmInfo {
|
||||||
|
pub uuid: String,
|
||||||
|
pub git_url: String,
|
||||||
|
pub r#ref: String,
|
||||||
|
pub created_at: NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ScmInfo {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
uuid: Uuid::new_v4().hyphenated().to_string(),
|
||||||
|
git_url: "https://example.com/some/repo.git".into(),
|
||||||
|
r#ref: "main".into(),
|
||||||
|
created_at: Utc::now().naive_utc(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,15 +7,23 @@
|
||||||
use crate::AppState;
|
use crate::AppState;
|
||||||
use tide::{Body, Request};
|
use tide::{Body, Request};
|
||||||
|
|
||||||
|
use crate::models::Project;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GET /
|
* GET /
|
||||||
*/
|
*/
|
||||||
pub async fn index(req: Request<AppState<'_>>) -> Result<Body, tide::Error> {
|
pub async fn index(req: Request<AppState<'_>>) -> Result<Body, tide::Error> {
|
||||||
|
let agents: Vec<String> = req
|
||||||
|
.state()
|
||||||
|
.agents
|
||||||
|
.iter()
|
||||||
|
.map(|a| a.render_compact(req.state()))
|
||||||
|
.collect();
|
||||||
let params = json!({
|
let params = json!({
|
||||||
"page": "home",
|
"page": "home",
|
||||||
"agents" : req.state().agents,
|
"agents" : agents,
|
||||||
"config" : req.state().config,
|
"config" : req.state().config,
|
||||||
"projects" : crate::dao::Project::list(&req.state().db).await?,
|
"projects" : Project::list(&req.state().db).await?,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut body = req.state().render("index", ¶ms).await?;
|
let mut body = req.state().render("index", ¶ms).await?;
|
||||||
|
@ -38,7 +46,8 @@ pub async fn project(req: Request<AppState<'_>>) -> Result<Body, tide::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod api {
|
pub mod api {
|
||||||
use crate::{AppState, JankyYml, Scm};
|
use crate::config::{JankyYml, Scm};
|
||||||
|
use crate::AppState;
|
||||||
use log::*;
|
use log::*;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use tide::{Request, Response, StatusCode};
|
use tide::{Request, Response, StatusCode};
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
<span title="Capabilities: {{#each this.capabilities}}
|
||||||
|
* {{this.name}} {{/each}}">
|
||||||
|
{{this.name}}
|
||||||
|
</span>
|
|
@ -20,10 +20,7 @@
|
||||||
{{#each agents}}
|
{{#each agents}}
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<span title="Capabilities: {{#each this.capabilities}}
|
{{this}}
|
||||||
* {{this.name}} {{/each}}">
|
|
||||||
{{this.name}}
|
|
||||||
</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
Loading…
Reference in New Issue