commit c02a4cd1d1e52e947c1ce4510c6086eb3a490d09 Author: Timo Ley Date: Sun Jan 30 22:18:09 2022 +0100 Init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ede170d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +Cargo.lock +config.toml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..03cf6b9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "droneconf" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1.0", features = ["full"] } +axum = { version = "0.2.8", features = ["headers", "multipart"] } +hyper = "0.14.16" +tower = { version = "0.4", features = ["util", "timeout"] } +tower-http = { version = "0.1", features = ["add-extension"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.51" +structopt = "0.3.22" +toml = "0.5.8" +reqwest = { version = "0.11", features = ["stream", "multipart", "json"] } +thiserror = "1.0.30" +url = {version = "2.2.2", features = ["serde"]} \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..b5d2d8f --- /dev/null +++ b/src/config.rs @@ -0,0 +1,11 @@ +use std::net::SocketAddr; + +use reqwest::Url; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Config { + pub addr: SocketAddr, + pub gitea_url: Url, + pub config_repo_name: String, +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..b1099f9 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,31 @@ +use std::convert::Infallible; + +use axum::{ + body::{Bytes, Empty}, + response::IntoResponse, +}; +use hyper::StatusCode; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("File read error: {0}")] + Read(#[from] std::io::Error), + #[error("Deserialize error: {0}")] + Deserialize(#[from] toml::de::Error), + #[error("Axum error: {0}")] + Axum(#[from] hyper::Error), + #[error("Reqwest error: {0}")] + Reqwest(#[from] reqwest::Error), + #[error("No content error")] + NoContent, +} + +impl IntoResponse for Error { + type Body = Empty; + + type BodyError = Infallible; + + fn into_response(self) -> hyper::Response { + StatusCode::NO_CONTENT.into_response() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..267c7b1 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,79 @@ +use std::{path::PathBuf, time::Duration}; + +use axum::{ + extract::Extension, handler::post, response::IntoResponse, AddExtensionLayer, Json, Router, +}; +use config::Config; +use error::Error; +use model::{APIConfig, Request}; +use serde_json::Value; +use structopt::StructOpt; + +use crate::model::Response; + +mod config; +mod error; +mod model; + +#[derive(StructOpt)] +struct Opt { + #[structopt( + short, + long, + help = "config file to use", + default_value = "./config.toml" + )] + config: PathBuf, +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let opt = Opt::from_args(); + let config = std::fs::read(&opt.config)?; + let config = toml::from_slice::(&config)?; + let api_conf = APIConfig(config.gitea_url, config.config_repo_name); + + let app = Router::new() + .route("/", post(on_request)) + .layer(AddExtensionLayer::new(api_conf)); + + axum::Server::bind(&config.addr) + .serve(app.into_make_service()) + .await?; + + Ok(()) +} + +async fn on_request( + Json(body): Json, + Extension(APIConfig(base_url, repo_name)): Extension, +) -> Result { + let client = reqwest::ClientBuilder::new() + .user_agent("curl") + .timeout(Duration::from_secs(30)) + .build()?; + let index_url = format!( + "{}/api/v1/repos/{}/{}/raw/index.json", + base_url.to_string(), + body.namespace(), + &repo_name + ); + let res: Value = client.get(index_url).send().await?.json().await?; + + if let Value::Object(obj) = res { + let v = obj.get(&body.name()).ok_or(Error::NoContent)?; + if let Value::String(path) = v { + let conf_url = format!( + "{}/api/v1/repos/{}/{}/raw/{}", + base_url.to_string(), + body.namespace(), + &repo_name, + path + ); + let drone_config = client.get(conf_url).send().await?.text().await?; + let response = Response { data: drone_config }; + return Ok(Json(response)); + } + } + Err(Error::NoContent) +} diff --git a/src/model.rs b/src/model.rs new file mode 100644 index 0000000..a947ce1 --- /dev/null +++ b/src/model.rs @@ -0,0 +1,31 @@ +use reqwest::Url; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize)] +pub struct Request { + pub repo: Repository, +} + +#[derive(Deserialize)] +pub struct Repository { + pub name: String, + pub namespace: String, +} + +#[derive(Serialize)] +pub struct Response { + pub data: String, +} + +#[derive(Clone)] +pub struct APIConfig(pub Url, pub String); + +impl Request { + pub fn namespace(&self) -> String { + self.repo.namespace.clone() + } + + pub fn name(&self) -> String { + self.repo.name.clone() + } +}