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);
|
||||
if full_path.is_file() {
|
||||
// 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
|
||||
*/
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(tag = "name")]
|
||||
pub struct Git {
|
||||
path: PathBuf,
|
||||
|
@ -65,7 +65,7 @@ impl Capability for Git {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Serialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
#[serde(tag = "name")]
|
||||
pub struct Cargo {
|
||||
path: PathBuf,
|
||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Capability {
|
||||
pub name: String,
|
||||
path: PathBuf,
|
||||
|
@ -24,12 +24,12 @@ impl Capability {
|
|||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
struct CapsRequest {}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct CapsResponse {
|
||||
pub caps: Vec<Capability>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct Command {
|
||||
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 commands: Vec<Command>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
|
||||
pub struct CommandResponse {
|
||||
pub uuid: Uuid,
|
||||
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]
|
||||
extern crate serde_json;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use async_std::sync::{Arc, RwLock};
|
||||
|
@ -13,13 +12,16 @@ use dotenv::dotenv;
|
|||
use gumdrop::Options;
|
||||
use handlebars::Handlebars;
|
||||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::SqlitePool;
|
||||
use url::Url;
|
||||
|
||||
mod dao;
|
||||
mod config;
|
||||
mod models;
|
||||
mod routes;
|
||||
|
||||
use crate::config::*;
|
||||
use crate::models::Project;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AppState<'a> {
|
||||
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)]
|
||||
struct ServerOptions {
|
||||
#[options(help = "print help message")]
|
||||
|
@ -183,11 +104,11 @@ async fn main() -> Result<(), tide::Error> {
|
|||
*/
|
||||
|
||||
for name in config.projects.keys() {
|
||||
match dao::Project::by_name(&name, &pool).await {
|
||||
match Project::by_name(name, &pool).await {
|
||||
Ok(_) => {}
|
||||
Err(sqlx::Error::RowNotFound) => {
|
||||
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) => {
|
||||
return Err(e.into());
|
||||
|
@ -201,11 +122,11 @@ async fn main() -> Result<(), tide::Error> {
|
|||
.await?
|
||||
.json()
|
||||
.await?;
|
||||
state.agents.push(Agent {
|
||||
name: name.clone(),
|
||||
url: agent.url.clone(),
|
||||
capabilities: response.caps,
|
||||
});
|
||||
state.agents.push(Agent::new(
|
||||
name.to_string(),
|
||||
agent.url.clone(),
|
||||
response.caps,
|
||||
));
|
||||
}
|
||||
|
||||
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 tide::{Body, Request};
|
||||
|
||||
use crate::models::Project;
|
||||
|
||||
/**
|
||||
* GET /
|
||||
*/
|
||||
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!({
|
||||
"page": "home",
|
||||
"agents" : req.state().agents,
|
||||
"agents" : agents,
|
||||
"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?;
|
||||
|
@ -38,7 +46,8 @@ pub async fn project(req: Request<AppState<'_>>) -> Result<Body, tide::Error> {
|
|||
}
|
||||
|
||||
pub mod api {
|
||||
use crate::{AppState, JankyYml, Scm};
|
||||
use crate::config::{JankyYml, Scm};
|
||||
use crate::AppState;
|
||||
use log::*;
|
||||
use serde::Deserialize;
|
||||
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}}
|
||||
<tr>
|
||||
<td>
|
||||
<span title="Capabilities: {{#each this.capabilities}}
|
||||
* {{this.name}} {{/each}}">
|
||||
{{this.name}}
|
||||
</span>
|
||||
{{this}}
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
|
|
Loading…
Reference in New Issue