diff --git a/typhon-types/src/lib.rs b/typhon-types/src/lib.rs index 5f4a6e08..e423ee1a 100644 --- a/typhon-types/src/lib.rs +++ b/typhon-types/src/lib.rs @@ -328,3 +328,41 @@ pub enum Event { BuildNew(handles::Build), BuildFinished(handles::Build), } + +pub mod actions { + pub mod webhooks { + use crate::handles; + use crate::requests; + use serde::{Deserialize, Serialize}; + use std::collections::HashMap; + + #[derive(Clone, Serialize)] + pub struct Input { + pub headers: HashMap, + pub body: String, + } + + #[derive(Clone, Deserialize)] + #[serde(tag = "type")] + pub enum Command { + UpdateJobsets, + EvaluateJobset(String), + } + + impl Command { + pub fn lift(self, project: handles::Project) -> requests::Request { + match self { + Command::UpdateJobsets => { + requests::Request::Project(project, requests::Project::UpdateJobsets) + } + Command::EvaluateJobset(jobset) => requests::Request::Jobset( + handles::Jobset { project, jobset }, + requests::Jobset::Evaluate(true), + ), + } + } + } + + pub type Output = Vec; + } +} diff --git a/typhon/run.sh b/typhon/run.sh new file mode 100755 index 00000000..64cc8a8c --- /dev/null +++ b/typhon/run.sh @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +password="hello" +hashed_password=$(echo -n $password | sha256sum | head -c 64) + +cargo run -- -p $hashed_password -j 'null' -w "" diff --git a/typhon/src/api.rs b/typhon/src/api.rs index c9ec1498..24fdb66a 100644 --- a/typhon/src/api.rs +++ b/typhon/src/api.rs @@ -1,3 +1,5 @@ +use typhon_types::actions::webhooks; + use crate::listeners::Session; use crate::requests::*; use crate::{handle_request, handles, Response, ResponseError, User}; @@ -5,7 +7,8 @@ use crate::{BUILD_LOGS, SETTINGS}; use actix_cors::Cors; use actix_files::NamedFile; use actix_web::{ - body::EitherBody, guard, http::StatusCode, web, Error, HttpRequest, HttpResponse, Responder, + body::EitherBody, guard, http::StatusCode, web, web::Bytes, Error, HttpRequest, HttpResponse, + Responder, }; use actix_web_actors::ws; @@ -233,6 +236,39 @@ async fn events(req: HttpRequest, stream: web::Payload) -> Result, + req: HttpRequest, + body: Bytes, +) -> Result { + let project_handle = handles::project(path.into_inner().to_string()); + let project = crate::models::Project::get(&project_handle) + .await + .map_err(|_| ResponseErrorWrapper(ResponseError::BadRequest("TODO".to_string())))?; + let input = webhooks::Input { + headers: req + .headers() + .into_iter() + .map(|(name, value)| { + ( + name.as_str().to_string(), + std::str::from_utf8(value.as_bytes()).unwrap().to_string(), + ) + }) + .collect(), + body: std::str::from_utf8(&body).unwrap().to_string(), + }; + let requests = project + .webhook(input) + .await + .map_err(|_| ResponseErrorWrapper(ResponseError::BadRequest("TODO".to_string())))? + .into_iter(); + for req in requests { + let _ = handle_request(User::Admin, req).await; + } + Ok(HttpResponse::Ok().finish()) +} + pub fn config(cfg: &mut web::ServiceConfig) { let cors = Cors::permissive(); // TODO: configure cfg.service( @@ -250,6 +286,7 @@ pub fn config(cfg: &mut web::ServiceConfig) { .route("/update_jobsets", web::post().to(project_update_jobsets)) .route("/set_decl", web::post().to(project_set_decl)) .route("/set_private_key", web::post().to(project_set_private_key)) + .route("/webhook", web::post().to(webhook)) .service( web::scope("/jobsets/{jobset}") .route("", web::get().to(jobset_info)) diff --git a/typhon/src/projects.rs b/typhon/src/projects.rs index a6ae1ce8..50b05628 100644 --- a/typhon/src/projects.rs +++ b/typhon/src/projects.rs @@ -251,4 +251,33 @@ impl Project { Ok(decls.into_keys().collect()) } + + pub async fn webhook( + &self, + input: typhon_types::actions::webhooks::Input, + ) -> Result, Error> { + match &self.project_actions_path { + Some(path) => { + if Path::new(&format!("{}/webhooks", path)).exists() { + let action_input = serde_json::to_value(input).unwrap(); + let (action_output, _) = actions::run( + &self.project_key, + &format!("{}/webhooks", path), + &format!("{}/secrets", path), + &action_input, + ) + .await?; + let commands: typhon_types::actions::webhooks::Output = + serde_json::from_str(&action_output).map_err(|_| Error::Todo)?; + Ok(commands + .into_iter() + .map(|cmd| cmd.lift(self.handle())) + .collect()) + } else { + Ok(Vec::new()) + } + } + None => Ok(Vec::new()), + } + } } diff --git a/typhon/test.sh b/typhon/test.sh new file mode 100755 index 00000000..6eb862c2 --- /dev/null +++ b/typhon/test.sh @@ -0,0 +1,46 @@ +#!/bin/sh + +token="hello" +url="http://localhost:8000/api" +curl_opts=( -s -H "token: $token" ) + +list_projects () { + curl "${curl_opts[@]}" "$url/projects" | jq +} +project_create () { + curl "${curl_opts[@]}" --json "\"$2\"" "$url/projects/$1/create" +} +project_delete () { + curl "${curl_opts[@]}" -X POST "$url/projects/$1/delete" +} +project_info () { + curl "${curl_opts[@]}" "$url/projects/$1" +} +project_refresh () { + curl "${curl_opts[@]}" -X POST "$url/projects/$1/refresh" +} +project_update_jobsets () { + curl "${curl_opts[@]}" -X POST "$url/projects/$1/update_jobsets" +} +jobset_info() { + + curl "${curl_opts[@]}" "$url/projects/$1/jobsets/$2" +} +jobset_evaluate () { + curl "${curl_opts[@]}" -X POST "$url/projects/$1/jobsets/$2/evaluate" +} +evaluation_info () { + curl "${curl_opts[@]}" "$url/projects/$1/jobsets/$2/evaluations/$3" +} + +project_delete "test" +echo "" +project_create "test" "github:typhon-ci/test" +echo "" +project_refresh "test" +echo "" +project_update_jobsets "test" +echo "" +jobset_evaluate "test" "main" +echo "" +evaluation_info "test" "main" "1"