some refactor

This commit is contained in:
Michael Zhang 2023-08-07 03:00:32 -05:00
parent 0043739228
commit cc2bb42c11
6 changed files with 195 additions and 168 deletions

52
src/auth.rs Normal file
View file

@ -0,0 +1,52 @@
use axum::{
http::{
header::{AUTHORIZATION, WWW_AUTHENTICATE},
Request,
},
middleware::Next,
response::Response,
};
use base64::{prelude::BASE64_STANDARD, Engine};
pub async fn http_basic_auth<B>(
request: Request<B>,
next: Next<B>,
) -> Response {
// do something with `request`...
let headers = request.headers();
let auth_fail_response = Response::builder()
.status(401)
.header(WWW_AUTHENTICATE, "Basic")
.body(axum::body::boxed(String::from("")))
.unwrap();
let authorization = match headers.get(AUTHORIZATION) {
Some(v) => v.to_str().unwrap(),
None => return auth_fail_response,
};
let parts = authorization.split_ascii_whitespace().collect::<Vec<_>>();
let base64part = match &parts[..] {
["Basic", base64part] => *base64part,
_ => return auth_fail_response,
};
let decoded = match BASE64_STANDARD
.decode(base64part.as_bytes())
.map_err(|e| anyhow::Error::from(e))
.and_then(|e| String::from_utf8(e).map_err(|e| e.into()))
{
Ok(v) => v,
Err(_) => return auth_fail_response,
};
let parts = decoded.split(":").collect::<Vec<_>>();
let (user, pass) = match &parts[..] {
[user, pass] => (*user, *pass),
_ => return auth_fail_response,
};
if !(user == "root" && pass == "hellosu") {
return auth_fail_response;
}
let response = next.run(request).await;
response
}

View file

@ -1,4 +1,4 @@
use std::{collections::HashMap, hash::Hash}; use std::{collections::HashMap};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Config { pub struct Config {

23
src/jobs.rs Normal file
View file

@ -0,0 +1,23 @@
use futures::Future;
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
pub type Job = Box<dyn Future<Output = ()>>;
pub type JobSender = UnboundedSender<Job>;
pub struct Scheduler {
rx: UnboundedReceiver<Job>,
}
impl Scheduler {
pub fn new() -> (Self, JobSender) {
let (tx, rx) = mpsc::unbounded_channel();
let scheduler = Scheduler { rx };
(scheduler, tx)
}
pub async fn run(self) {
loop {
// Get the next job, or if a new thing comes in
}
}
}

15
src/lib.rs Normal file
View file

@ -0,0 +1,15 @@
#[macro_use]
extern crate serde;
use sea_orm::DatabaseConnection;
pub mod auth;
pub mod config;
pub mod error;
pub mod jobs;
pub mod upload;
#[derive(Clone)]
pub struct AppState {
pub conn: DatabaseConnection,
}

View file

@ -1,38 +1,15 @@
#[macro_use] use aah::{
extern crate serde; auth::http_basic_auth, upload::handle_upload_configuration, AppState,
};
mod config; use anyhow::Result;
mod error;
use anyhow::{anyhow, Result};
use axum::{ use axum::{
extract::{Multipart, State}, middleware::from_fn,
http::{ response::Html,
header::{AUTHORIZATION, WWW_AUTHENTICATE},
HeaderName, Request,
},
middleware::{from_fn, Next},
response::{Html, Response},
routing::{get, post}, routing::{get, post},
Form, Json, Router, Router,
}; };
use axum_auth::AuthBasic;
use base64::{prelude::BASE64_STANDARD, Engine};
use bollard::{
auth,
container::{
Config as ContainerConfig, CreateContainerOptions, StartContainerOptions,
},
image::CreateImageOptions,
Docker,
};
use entity::service;
use error::AppError;
use futures::{StreamExt, TryStreamExt};
use migration::{Migrator, MigratorTrait};
use sea_orm::{ActiveModelTrait, DatabaseConnection, Set};
use crate::config::{Config, SourceConfig}; use migration::{Migrator, MigratorTrait};
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@ -59,139 +36,3 @@ async fn main() -> Result<()> {
webserver_handle.await??; webserver_handle.await??;
Ok(()) Ok(())
} }
async fn http_basic_auth<B>(request: Request<B>, next: Next<B>) -> Response {
// do something with `request`...
let headers = request.headers();
let auth_fail_response = Response::builder()
.status(401)
.header(WWW_AUTHENTICATE, "Basic")
.body(axum::body::boxed(String::from("")))
.unwrap();
let authorization = match headers.get(AUTHORIZATION) {
Some(v) => v.to_str().unwrap(),
None => return auth_fail_response,
};
let parts = authorization.split_ascii_whitespace().collect::<Vec<_>>();
let base64part = match &parts[..] {
["Basic", base64part] => *base64part,
_ => return auth_fail_response,
};
let decoded = match BASE64_STANDARD
.decode(base64part.as_bytes())
.map_err(|e| anyhow::Error::from(e))
.and_then(|e| String::from_utf8(e).map_err(|e| e.into()))
{
Ok(v) => v,
Err(_) => return auth_fail_response,
};
let parts = decoded.split(":").collect::<Vec<_>>();
let (user, pass) = match &parts[..] {
[user, pass] => (*user, *pass),
_ => return auth_fail_response,
};
if !(user == "root" && pass == "hellosu") {
return auth_fail_response;
}
let response = next.run(request).await;
response
}
#[derive(Clone)]
struct AppState {
conn: DatabaseConnection,
}
#[derive(Deserialize)]
struct UploadForm {
name: String,
config: String,
}
async fn handle_upload_configuration(
AuthBasic((id, password)): AuthBasic,
state: State<AppState>,
mut form: Multipart,
// Form(upload_form): Form<UploadForm>,
) -> Result<(), AppError> {
if id != "root" || password.as_ref().unwrap() != "hellosu" {
return Err(anyhow!("error").into());
}
let (name, config) = {
let mut name = None;
let mut config = None::<Config>;
while let Some(field) = form.next_field().await.unwrap() {
let key = field.name().unwrap().to_owned();
let value =
String::from_utf8(field.bytes().await.unwrap().to_vec()).unwrap();
match key.as_ref() {
"name" => name = Some(value),
"config" => config = Some(serde_yaml::from_str(&value).unwrap()),
_ => return Err(anyhow!("error").into()),
}
}
(name.unwrap(), config.unwrap())
};
println!("Loading config: {config:?}");
let docker = Docker::connect_with_local_defaults().unwrap();
for (service_name, service_config) in config.service.iter() {
let SourceConfig::Image { image: from_image } = &service_config.source;
let result_stream = docker.create_image(
Some(CreateImageOptions {
from_image: from_image.to_owned(),
..Default::default()
}),
None,
None,
);
result_stream
.try_for_each(|x| async move {
println!("{x:?}");
Ok(())
})
.await
.unwrap();
println!("completed pulling.");
// create a container
let result = docker
.create_container(
Some(CreateContainerOptions::<String> {
..Default::default()
}),
ContainerConfig {
image: Some("hello-world"),
cmd: Some(vec!["/hello"]),
..Default::default()
},
)
.await
.unwrap();
docker
.start_container(&result.id, None::<StartContainerOptions<String>>)
.await
.unwrap();
println!("result: {result:?}");
}
service::ActiveModel {
name: Set(name),
config: Set(serde_json::to_string(&config).unwrap()),
..Default::default()
}
.save(&state.conn)
.await
.unwrap();
Ok(())
}

96
src/upload.rs Normal file
View file

@ -0,0 +1,96 @@
use anyhow::anyhow;
use axum::extract::{Multipart, State};
use bollard::{
container::{
Config as ContainerConfig, CreateContainerOptions, StartContainerOptions,
},
image::CreateImageOptions,
Docker,
};
use entity::service;
use futures::TryStreamExt;
use sea_orm::{ActiveModelTrait, Set};
use crate::{
config::{Config, SourceConfig},
error::AppError,
AppState,
};
pub async fn handle_upload_configuration(
state: State<AppState>,
mut form: Multipart,
) -> Result<(), AppError> {
let (name, config) = {
let mut name = None;
let mut config = None::<Config>;
while let Some(field) = form.next_field().await.unwrap() {
let key = field.name().unwrap().to_owned();
let value =
String::from_utf8(field.bytes().await.unwrap().to_vec()).unwrap();
match key.as_ref() {
"name" => name = Some(value),
"config" => config = Some(serde_yaml::from_str(&value).unwrap()),
_ => return Err(anyhow!("error").into()),
}
}
(name.unwrap(), config.unwrap())
};
println!("Loading config: {config:?}");
let docker = Docker::connect_with_local_defaults().unwrap();
for (_service_name, service_config) in config.service.iter() {
let SourceConfig::Image { image: from_image } = &service_config.source;
let result_stream = docker.create_image(
Some(CreateImageOptions {
from_image: from_image.to_owned(),
..Default::default()
}),
None,
None,
);
result_stream
.try_for_each(|x| async move {
println!("{x:?}");
Ok(())
})
.await
.unwrap();
println!("completed pulling.");
// create a container
let result = docker
.create_container(
Some(CreateContainerOptions::<String> {
..Default::default()
}),
ContainerConfig {
image: Some(from_image.to_owned()),
..Default::default()
},
)
.await
.unwrap();
docker
.start_container(&result.id, None::<StartContainerOptions<String>>)
.await
.unwrap();
println!("result: {result:?}");
}
service::ActiveModel {
name: Set(name),
config: Set(serde_json::to_string(&config).unwrap()),
..Default::default()
}
.save(&state.conn)
.await
.unwrap();
Ok(())
}