From 2970d15532c137ba1583f2268e969d88d0a6527c Mon Sep 17 00:00:00 2001 From: Hamcha Date: Sat, 25 Nov 2023 14:05:21 +0100 Subject: [PATCH] more refactoring --- src/http/accept.rs | 47 +++++- src/http/error.rs | 8 + src/http/response.rs | 31 ++-- src/node/stack.rs | 301 ------------------------------------ src/node/stack/arion.rs | 68 ++++++++ src/node/stack/compose.rs | 72 +++++++++ src/node/stack/list.rs | 95 ++++++++++++ src/node/stack/mod.rs | 36 +++++ src/node/stack/operation.rs | 57 +++++++ src/node/stack/utils.rs | 9 ++ src/route/mod.rs | 2 +- src/route/stack/check.rs | 4 +- src/route/stack/create.rs | 6 +- src/route/stack/delete.rs | 4 +- src/route/stack/edit.rs | 16 +- src/route/stack/mod.rs | 6 +- src/route/stack/read.rs | 10 +- 17 files changed, 432 insertions(+), 340 deletions(-) delete mode 100644 src/node/stack.rs create mode 100644 src/node/stack/arion.rs create mode 100644 src/node/stack/compose.rs create mode 100644 src/node/stack/list.rs create mode 100644 src/node/stack/mod.rs create mode 100644 src/node/stack/operation.rs create mode 100644 src/node/stack/utils.rs diff --git a/src/http/accept.rs b/src/http/accept.rs index 6925981..948e574 100644 --- a/src/http/accept.rs +++ b/src/http/accept.rs @@ -8,6 +8,7 @@ use axum::{ }, }; +/// Extractor for the Accept header pub struct ExtractAccept(pub HeaderValue); #[async_trait] @@ -18,10 +19,50 @@ where type Rejection = (StatusCode, &'static str); async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result { - if let Some(user_agent) = parts.headers.get(ACCEPT) { - Ok(ExtractAccept(user_agent.clone())) + if let Some(accept) = parts.headers.get(ACCEPT) { + Ok(ExtractAccept(accept.clone())) } else { - Err((StatusCode::BAD_REQUEST, "`User-Agent` header is missing")) + Err((StatusCode::NOT_ACCEPTABLE, "`Accept` header is missing")) } } } + +/// Supported content types +pub(super) enum ResponseType { + HTML, + JSON, +} + +/// Parses the Accept header and returns content type to return +pub(super) fn parse_accept(accept: &HeaderValue) -> ResponseType { + let bytes = accept.as_bytes(); + if bytes.starts_with(b"application/json") { + return ResponseType::JSON; + } + ResponseType::HTML +} + +#[cfg(test)] +mod tests { + use axum::{ + extract::FromRequest, + http::{HeaderValue, Request}, + }; + + use super::*; + + #[tokio::test] + async fn test_accept() { + let req = Request::builder() + .header(ACCEPT, "application/json; charset=utf-8") + .body(()) + .unwrap(); + + let extract = ExtractAccept::from_request(req, &()).await.unwrap(); + + assert_eq!( + extract.0, + HeaderValue::from_static("application/json; charset=utf-8") + ); + } +} diff --git a/src/http/error.rs b/src/http/error.rs index 5a55dbe..34301cf 100644 --- a/src/http/error.rs +++ b/src/http/error.rs @@ -5,10 +5,13 @@ use axum::{ }; use thiserror::Error; +/// Result type for HTTP requests pub type Result = core::result::Result; +/// Error type that can be returned from HTTP requests #[derive(Error, Debug)] pub enum AppError { + /// Client error (e.g. bad input, not found) #[error("client error: <{code}> {message}")] Client { status: StatusCode, @@ -16,18 +19,23 @@ pub enum AppError { message: String, }, + /// Server error caused by Docker API errors #[error("docker error: {0}")] DockerError(#[from] bollard::errors::Error), + /// Server error caused by file operation errors #[error("file error: {0}")] FileError(#[from] std::io::Error), + /// Server error caused by unexpected internal errors #[error("unexpected internal error: {0}")] Internal(#[from] anyhow::Error), + /// Errors caused by JSON parsing errors (both incoming and outgoing) #[error("incoming JSON format error: {0}")] JSONFormat(#[from] JsonRejection), + /// Server error caused by template rendering errors #[error("template error: {0}")] Template(#[from] askama::Error), } diff --git a/src/http/response.rs b/src/http/response.rs index ac175df..4d3c265 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -1,4 +1,7 @@ -use super::error::{AppError, ErrorInfo}; +use super::{ + accept::{parse_accept, ResponseType}, + error::{AppError, ErrorInfo}, +}; use askama::Template; use askama_axum::IntoResponse; use axum::{ @@ -32,14 +35,18 @@ pub fn reply(json: serde_json::Value, html: T) -> HandlerResponse { Ok(response) } +/// Handles the multiple return type system of staxman. +/// Handlers might return an Error or a Response object, this will intercept +/// those and return them as either JSON or HTML depending on the Accept header. pub async fn response_interceptor( request: Request, next: Next, ) -> axum::response::Response { - let accept_header = request + let accept = request .headers() .get(&ACCEPT) - .map(|value| value.as_ref().to_owned()); + .map(|header| parse_accept(header)) + .unwrap_or_else(|| ResponseType::HTML); let mut response = next.run(request).await; @@ -49,9 +56,9 @@ pub async fn response_interceptor( message, }) = response.extensions_mut().remove::() { - match accept_header.as_deref() { - Some(b"application/json") => { - return (status, Json(json!({"code": code, "message": message}))).into_response() + match accept { + ResponseType::JSON => { + return (status, Json(json!({"code": code, "message": message}))).into_response(); } _ => { return ( @@ -61,15 +68,19 @@ pub async fn response_interceptor( message, }, ) - .into_response() + .into_response(); } } } if let Some(Response { html, json }) = response.extensions_mut().remove::() { - match accept_header.as_deref() { - Some(b"application/json") => return Json(json).into_response(), - _ => return Html(html).into_response(), + match accept { + ResponseType::JSON => { + return Json(json).into_response(); + } + _ => { + return Html(html).into_response(); + } } } diff --git a/src/node/stack.rs b/src/node/stack.rs deleted file mode 100644 index df5e0f8..0000000 --- a/src/node/stack.rs +++ /dev/null @@ -1,301 +0,0 @@ -use super::{ - container::ContainerInfo, error::StackError, git::ThreadSafeRepository, - nix::parse_arion_compose, -}; -use crate::http::error::{AppError, Result}; -use axum::http::StatusCode; -use bollard::{container::ListContainersOptions, service::ContainerSummary, Docker}; -use futures_util::future::try_join; -use serde::Serialize; -use std::{ - collections::HashMap, - path::{Path, PathBuf}, - process::Command, -}; -use tempfile::tempdir; -use tokio::fs; - -const COMPOSE_FILE: &str = "arion-compose.nix"; -const PACKAGE_FILE: &str = "arion-pkgs.nix"; -const PACKAGE_CONTENTS: &str = r#"import { system = "x86_64-linux"; } -"#; - -async fn is_stack(dir: &Path) -> Result { - Ok(fs::try_exists(dir.join(COMPOSE_FILE)).await?) -} - -pub async fn get_containers(docker: &Docker, stack_name: &str) -> Result> { - Ok(docker - .list_containers(Some(ListContainersOptions { - all: true, - limit: None, - size: true, - filters: HashMap::from([( - "label".to_string(), - vec![format!("com.docker.compose.project={}", stack_name)], - )]), - })) - .await?) -} - -pub async fn get_compose(base_dir: &Path, stack_name: &str) -> Result { - let dir = base_dir.join(stack_name); - if !is_stack(&dir).await? { - return Err(StackError::NotFound.into()); - } - - let contents = tokio::fs::read_to_string(dir.join(COMPOSE_FILE)).await?; - Ok(contents) -} - -#[derive(Serialize)] -pub struct ServiceInfo { - name: String, - container: Option, - running: bool, -} - -#[derive(Serialize)] -pub struct StackInfo { - pub folder: String, - pub name: String, - pub active: bool, - pub services: Vec, -} - -impl StackInfo { - pub fn stats(&self) -> (usize, usize) { - let running = self.services.iter().filter(|s| s.running).count(); - (running, self.services.len() - running) - } -} - -#[derive(Serialize)] -pub struct NodeInfo { - pub stacks: Vec, - pub containers: Vec, -} - -fn get_service(containers: &Vec, stack_name: &str, service: &str) -> ServiceInfo { - let container = containers.iter().find(|cont| { - let labels = cont.labels.clone().unwrap_or_default(); - labels.get("com.docker.compose.project") == Some(&stack_name.to_string()) - && labels.get("com.docker.compose.service") == Some(&service.to_string()) - }); - - match container { - Some(info) => ServiceInfo { - name: service.to_string(), - container: Some(info.name.clone()), - running: info.running(), - }, - _ => ServiceInfo { - name: service.to_string(), - container: None, - running: false, - }, - } -} - -pub async fn list(base_dir: &Path, docker: &Docker) -> Result { - let containers: Vec = docker - .list_containers(Some(ListContainersOptions:: { - all: true, - limit: None, - ..Default::default() - })) - .await? - .iter() - .map(|c| c.clone().into()) - .collect(); - - let mut dirs = fs::read_dir(base_dir).await?; - let mut stacks = vec![]; - while let Some(dir) = dirs.next_entry().await? { - let meta = dir.metadata().await?; - if !meta.is_dir() { - continue; - } - if is_stack(&dir.path()).await? { - let folder = dir.file_name().to_string_lossy().to_string(); - let compose_file = get_compose(base_dir, &folder).await?; - let info = parse_arion_compose(&compose_file)?; - let name = info.project; - // Check status by analyzing containers - let active = containers - .iter() - .any(|cont| cont.state == "running" && cont.stack() == Some(name.clone())); - let services = info - .services - .iter() - .map(|service| get_service(&containers, &name, service)) - .collect(); - stacks.push(StackInfo { - folder, - name, - active, - services, - }) - } - } - - Ok(NodeInfo { stacks, containers }) -} - -pub async fn write_compose(base_dir: &Path, stack_name: &str, contents: &str) -> Result<()> { - let dir = base_dir.join(stack_name); - if !is_stack(&dir).await? { - return Err(StackError::NotFound.into()); - } - - Ok(fs::write(dir.join(COMPOSE_FILE), contents).await?) -} - -pub fn commit_compose( - repository: ThreadSafeRepository, - stack_name: &str, - message: &str, -) -> Result<()> { - let compose_path = format!("{}/{}", stack_name, COMPOSE_FILE); - repository.commit_files(&[&PathBuf::from(compose_path)], message)?; - Ok(()) -} - -pub async fn create_new( - repository: ThreadSafeRepository, - stack_name: &str, - source: &str, -) -> Result<()> { - // Calculate stack directory and create it - let stack_path = repository.path.join(stack_name); - fs::create_dir_all(&stack_path).await?; - - // Create package file and compose file - try_join( - fs::write(stack_path.join(PACKAGE_FILE), PACKAGE_CONTENTS), - fs::write(stack_path.join(COMPOSE_FILE), source), - ) - .await?; - - // Commit everything - repository.commit_files( - &[ - &PathBuf::from(format!("{}/{}", stack_name, PACKAGE_FILE)), - &PathBuf::from(format!("{}/{}", stack_name, COMPOSE_FILE)), - ], - format!("Created stack {}", stack_name).as_str(), - )?; - - Ok(()) -} - -pub async fn remove( - base_dir: &Path, - arion_bin: &Path, - repository: ThreadSafeRepository, - stack_name: &str, -) -> Result<()> { - // Remove all containers and resources - command(base_dir, stack_name, arion_bin, StackCommand::Down).await?; - - // Remove from repository - repository.remove_folder( - &PathBuf::from(stack_name), - format!("Removed stack {}", stack_name).as_str(), - )?; - - // Remove from disk - fs::remove_dir_all(repository.path.join(stack_name)).await?; - - Ok(()) -} - -pub async fn check_compose(arion_bin: &Path, source: &str) -> Result { - // Check that it's a valid nix tree - let info = parse_arion_compose(source).map_err(|err| AppError::Client { - status: StatusCode::NOT_ACCEPTABLE, - code: "failed-nix-parse", - message: format!("Parse error: {}", err), - })?; - - // Create a temporary stack and check that it generates a YAML tree - let dir = tempdir()?; - let path = dir.path(); - - // Create package file and compose file - try_join( - fs::write(path.join(PACKAGE_FILE), PACKAGE_CONTENTS), - fs::write(path.join(COMPOSE_FILE), source), - ) - .await?; - - let cmd = arion(arion_bin, path, StackCommand::Test)?; - - dir.close()?; - - if let CommandStatus::Failure(_, err) = cmd { - Err(AppError::Client { - status: StatusCode::NOT_ACCEPTABLE, - code: "failed-arion-check", - message: format!("Arion {}", err), - }) - } else { - Ok(info.project) - } -} - -pub enum CommandStatus { - Success(String, String), - Failure(String, String), -} - -fn arion(arion_bin: &Path, path: &Path, action: StackCommand) -> Result { - let output = Command::new(arion_bin) - .args(action.command()) - .current_dir(path) - .output()?; - - // Convert stdout and stderr to String - let stdout_str = String::from_utf8_lossy(&output.stdout).to_string(); - let stderr_str = String::from_utf8_lossy(&output.stderr).to_string(); - - if output.status.success() { - Ok(CommandStatus::Success(stdout_str, stderr_str)) - } else { - Ok(CommandStatus::Failure(stdout_str, stderr_str)) - } -} - -pub async fn command( - base_dir: &Path, - stack_name: &str, - arion_bin: &Path, - action: StackCommand, -) -> Result { - let dir = base_dir.join(stack_name); - if !is_stack(&dir).await? { - return Err(StackError::NotFound.into()); - } - - arion(arion_bin, &dir, action) -} - -pub enum StackCommand { - Down, - Start, - Stop, - Restart, - Test, -} - -impl StackCommand { - fn command(&self) -> &[&str] { - match self { - StackCommand::Down => &["down"], - StackCommand::Start => &["up", "-d"], - StackCommand::Stop => &["stop"], - StackCommand::Restart => &["restart"], - StackCommand::Test => &["config"], - } - } -} diff --git a/src/node/stack/arion.rs b/src/node/stack/arion.rs new file mode 100644 index 0000000..918e9f3 --- /dev/null +++ b/src/node/stack/arion.rs @@ -0,0 +1,68 @@ +use anyhow::Result; +use std::path::Path; +use tokio::process::Command; + +use crate::node::error::StackError; + +use super::utils::is_stack; + +pub enum CommandStatus { + Success(String, String), + Failure(String, String), +} + +pub(super) async fn run( + arion_bin: &Path, + path: &Path, + action: StackCommand, +) -> Result { + let output = Command::new(arion_bin) + .args(action.command()) + .current_dir(path) + .output() + .await?; + + // Convert stdout and stderr to String + let stdout_str = String::from_utf8_lossy(&output.stdout).to_string(); + let stderr_str = String::from_utf8_lossy(&output.stderr).to_string(); + + if output.status.success() { + Ok(CommandStatus::Success(stdout_str, stderr_str)) + } else { + Ok(CommandStatus::Failure(stdout_str, stderr_str)) + } +} + +pub async fn command( + base_dir: &Path, + stack_name: &str, + arion_bin: &Path, + action: StackCommand, +) -> Result { + let dir = base_dir.join(stack_name); + if !is_stack(&dir).await? { + return Err(StackError::NotFound.into()); + } + + run(arion_bin, &dir, action).await +} + +pub enum StackCommand { + Down, + Start, + Stop, + Restart, + Test, +} + +impl StackCommand { + fn command(&self) -> &[&str] { + match self { + StackCommand::Down => &["down"], + StackCommand::Start => &["up", "-d"], + StackCommand::Stop => &["stop"], + StackCommand::Restart => &["restart"], + StackCommand::Test => &["config"], + } + } +} diff --git a/src/node/stack/compose.rs b/src/node/stack/compose.rs new file mode 100644 index 0000000..9071c18 --- /dev/null +++ b/src/node/stack/compose.rs @@ -0,0 +1,72 @@ +use anyhow::Result; +use axum::http::StatusCode; +use futures_util::future::try_join; +use std::path::{Path, PathBuf}; +use tempfile::tempdir; +use tokio::fs; + +use crate::{ + http::error::AppError, + node::{error::StackError, git::ThreadSafeRepository, nix::parse_arion_compose}, +}; + +use super::{arion, utils, COMPOSE_FILE, PACKAGE_CONTENTS, PACKAGE_FILE}; + +pub async fn get(base_dir: &Path, stack_name: &str) -> Result { + let dir = base_dir.join(stack_name); + if !utils::is_stack(&dir).await? { + return Err(StackError::NotFound.into()); + } + + let contents = tokio::fs::read_to_string(dir.join(COMPOSE_FILE)).await?; + Ok(contents) +} + +pub async fn write(base_dir: &Path, stack_name: &str, contents: &str) -> Result<()> { + let dir = base_dir.join(stack_name); + if !utils::is_stack(&dir).await? { + return Err(StackError::NotFound.into()); + } + + Ok(fs::write(dir.join(COMPOSE_FILE), contents).await?) +} + +pub fn commit(repository: ThreadSafeRepository, stack_name: &str, message: &str) -> Result<()> { + let compose_path = format!("{}/{}", stack_name, COMPOSE_FILE); + repository.commit_files(&[&PathBuf::from(compose_path)], message)?; + Ok(()) +} + +pub async fn check(arion_bin: &Path, source: &str) -> crate::http::error::Result { + // Check that it's a valid nix tree + let info = parse_arion_compose(source).map_err(|err| AppError::Client { + status: StatusCode::NOT_ACCEPTABLE, + code: "failed-nix-parse", + message: format!("Parse error: {}", err), + })?; + + // Create a temporary stack and check that it generates a YAML tree + let dir = tempdir()?; + let path = dir.path(); + + // Create package file and compose file + try_join( + fs::write(path.join(PACKAGE_FILE), PACKAGE_CONTENTS), + fs::write(path.join(COMPOSE_FILE), source), + ) + .await?; + + let cmd = arion::run(arion_bin, path, arion::StackCommand::Test).await?; + + dir.close()?; + + if let arion::CommandStatus::Failure(_, err) = cmd { + Err(AppError::Client { + status: StatusCode::NOT_ACCEPTABLE, + code: "failed-arion-check", + message: format!("Arion {}", err), + }) + } else { + Ok(info.project) + } +} diff --git a/src/node/stack/list.rs b/src/node/stack/list.rs new file mode 100644 index 0000000..4b06f11 --- /dev/null +++ b/src/node/stack/list.rs @@ -0,0 +1,95 @@ +use anyhow::Result; +use bollard::{container::ListContainersOptions, service::ContainerSummary, Docker}; +use std::{collections::HashMap, path::Path}; +use tokio::fs; + +use crate::node::{container::ContainerInfo, nix::parse_arion_compose}; + +use super::{compose, utils, NodeInfo, ServiceInfo, StackInfo}; + +impl StackInfo { + pub fn stats(&self) -> (usize, usize) { + let running = self.services.iter().filter(|s| s.running).count(); + (running, self.services.len() - running) + } +} + +fn get_service(containers: &Vec, stack_name: &str, service: &str) -> ServiceInfo { + let container = containers.iter().find(|cont| { + let labels = cont.labels.clone().unwrap_or_default(); + labels.get("com.docker.compose.project") == Some(&stack_name.to_string()) + && labels.get("com.docker.compose.service") == Some(&service.to_string()) + }); + + match container { + Some(info) => ServiceInfo { + name: service.to_string(), + container: Some(info.name.clone()), + running: info.running(), + }, + _ => ServiceInfo { + name: service.to_string(), + container: None, + running: false, + }, + } +} + +pub async fn all(base_dir: &Path, docker: &Docker) -> Result { + let containers: Vec = docker + .list_containers(Some(ListContainersOptions:: { + all: true, + limit: None, + ..Default::default() + })) + .await? + .iter() + .map(|c| c.clone().into()) + .collect(); + + let mut dirs = fs::read_dir(base_dir).await?; + let mut stacks = vec![]; + while let Some(dir) = dirs.next_entry().await? { + let meta = dir.metadata().await?; + if !meta.is_dir() { + continue; + } + if utils::is_stack(&dir.path()).await? { + let folder = dir.file_name().to_string_lossy().to_string(); + let compose_file = compose::get(base_dir, &folder).await?; + let info = parse_arion_compose(&compose_file)?; + let name = info.project; + // Check status by analyzing containers + let active = containers + .iter() + .any(|cont| cont.state == "running" && cont.stack() == Some(name.clone())); + let services = info + .services + .iter() + .map(|service| get_service(&containers, &name, service)) + .collect(); + stacks.push(StackInfo { + folder, + name, + active, + services, + }) + } + } + + Ok(NodeInfo { stacks, containers }) +} + +pub async fn containers(docker: &Docker, stack_name: &str) -> Result> { + Ok(docker + .list_containers(Some(ListContainersOptions { + all: true, + limit: None, + size: true, + filters: HashMap::from([( + "label".to_string(), + vec![format!("com.docker.compose.project={}", stack_name)], + )]), + })) + .await?) +} diff --git a/src/node/stack/mod.rs b/src/node/stack/mod.rs new file mode 100644 index 0000000..fda1b1d --- /dev/null +++ b/src/node/stack/mod.rs @@ -0,0 +1,36 @@ +use serde::Serialize; + +use super::container::ContainerInfo; + +pub mod arion; +pub mod compose; +pub mod list; +pub mod operation; + +mod utils; + +#[derive(Serialize)] +pub struct ServiceInfo { + name: String, + container: Option, + running: bool, +} + +#[derive(Serialize)] +pub struct NodeInfo { + pub stacks: Vec, + pub containers: Vec, +} + +#[derive(Serialize)] +pub struct StackInfo { + pub folder: String, + pub name: String, + pub active: bool, + pub services: Vec, +} + +const COMPOSE_FILE: &str = "arion-compose.nix"; +const PACKAGE_FILE: &str = "arion-pkgs.nix"; +const PACKAGE_CONTENTS: &str = r#"import { system = "x86_64-linux"; } +"#; diff --git a/src/node/stack/operation.rs b/src/node/stack/operation.rs new file mode 100644 index 0000000..9e70111 --- /dev/null +++ b/src/node/stack/operation.rs @@ -0,0 +1,57 @@ +use anyhow::Result; +use futures_util::future::try_join; +use std::path::{Path, PathBuf}; +use tokio::fs; + +use crate::node::git::ThreadSafeRepository; + +use super::{arion, COMPOSE_FILE, PACKAGE_CONTENTS, PACKAGE_FILE}; + +pub async fn create( + repository: ThreadSafeRepository, + stack_name: &str, + source: &str, +) -> Result<()> { + // Calculate stack directory and create it + let stack_path = repository.path.join(stack_name); + fs::create_dir_all(&stack_path).await?; + + // Create package file and compose file + try_join( + fs::write(stack_path.join(PACKAGE_FILE), PACKAGE_CONTENTS), + fs::write(stack_path.join(COMPOSE_FILE), source), + ) + .await?; + + // Commit everything + repository.commit_files( + &[ + &PathBuf::from(format!("{}/{}", stack_name, PACKAGE_FILE)), + &PathBuf::from(format!("{}/{}", stack_name, COMPOSE_FILE)), + ], + format!("Created stack {}", stack_name).as_str(), + )?; + + Ok(()) +} + +pub async fn remove( + base_dir: &Path, + arion_bin: &Path, + repository: ThreadSafeRepository, + stack_name: &str, +) -> Result<()> { + // Remove all containers and resources + arion::command(base_dir, stack_name, arion_bin, arion::StackCommand::Down).await?; + + // Remove from repository + repository.remove_folder( + &PathBuf::from(stack_name), + format!("Removed stack {}", stack_name).as_str(), + )?; + + // Remove from disk + fs::remove_dir_all(repository.path.join(stack_name)).await?; + + Ok(()) +} diff --git a/src/node/stack/utils.rs b/src/node/stack/utils.rs new file mode 100644 index 0000000..e8b2227 --- /dev/null +++ b/src/node/stack/utils.rs @@ -0,0 +1,9 @@ +use anyhow::Result; +use std::path::Path; +use tokio::fs; + +use super::COMPOSE_FILE; + +pub(super) async fn is_stack(dir: &Path) -> Result { + Ok(fs::try_exists(dir.join(COMPOSE_FILE)).await?) +} diff --git a/src/route/mod.rs b/src/route/mod.rs index 0c516f3..1324443 100644 --- a/src/route/mod.rs +++ b/src/route/mod.rs @@ -27,7 +27,7 @@ struct HomeTemplate { } async fn home(State(state): State) -> HandlerResponse { - let info = list(&state.stack_dir, &state.docker) + let info = list::all(&state.stack_dir, &state.docker) .await .map_err(AppError::from)?; let system = system_info(); diff --git a/src/route/stack/check.rs b/src/route/stack/check.rs index dad670f..f218ef2 100644 --- a/src/route/stack/check.rs +++ b/src/route/stack/check.rs @@ -1,11 +1,11 @@ use axum::{extract::State, http::StatusCode}; -use crate::{http::error::AppError, node::stack::check_compose, AppState}; +use crate::{http::error::AppError, node::stack::compose, AppState}; pub(super) async fn check_stack_file( State(state): State, body: String, ) -> Result { - check_compose(&state.arion_bin, &body).await?; + compose::check(&state.arion_bin, &body).await?; Ok(StatusCode::NO_CONTENT) } diff --git a/src/route/stack/create.rs b/src/route/stack/create.rs index 308cef3..b14453e 100644 --- a/src/route/stack/create.rs +++ b/src/route/stack/create.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use crate::{ http::error::AppError, - node::stack::{check_compose, create_new}, + node::stack::{compose, operation}, AppState, }; @@ -23,9 +23,9 @@ pub(super) async fn create_stack( Form(form): Form, ) -> Result { // Make sure body is is ok - let name = check_compose(&state.arion_bin, &form.source).await?; + let name = compose::check(&state.arion_bin, &form.source).await?; - create_new(state.repository, &name, &form.source).await?; + operation::create(state.repository, &name, &form.source).await?; Ok(Redirect::to(format!("/stack/{}/", name).as_str())) } diff --git a/src/route/stack/delete.rs b/src/route/stack/delete.rs index 620cc60..8d1eb9c 100644 --- a/src/route/stack/delete.rs +++ b/src/route/stack/delete.rs @@ -5,7 +5,7 @@ use axum::{ response::Redirect, }; -use crate::{http::error::AppError, node::stack::remove, AppState}; +use crate::{http::error::AppError, node::stack::operation, AppState}; #[derive(Template)] #[template(path = "stack/delete-one.html")] @@ -21,7 +21,7 @@ pub(super) async fn delete_stack( Path(stack_name): Path, State(state): State, ) -> Result { - remove( + operation::remove( &state.stack_dir, &state.arion_bin, state.repository, diff --git a/src/route/stack/edit.rs b/src/route/stack/edit.rs index fe5282a..36c6ab6 100644 --- a/src/route/stack/edit.rs +++ b/src/route/stack/edit.rs @@ -6,11 +6,7 @@ use axum::{ }; use serde::Deserialize; -use crate::{ - http::error::AppError, - node::stack::{check_compose, command, commit_compose, write_compose, StackCommand}, - AppState, -}; +use crate::{http::error::AppError, node::stack::arion, node::stack::compose, AppState}; #[derive(Deserialize)] pub(super) struct EditStackForm { @@ -41,20 +37,20 @@ pub(super) async fn edit_stack( let source = form.source.replace("\r\n", "\n"); // Make sure file is ok - check_compose(&state.arion_bin, &source).await?; + compose::check(&state.arion_bin, &source).await?; // Write compose file - write_compose(&state.stack_dir, &stack_name, &source).await?; + compose::write(&state.stack_dir, &stack_name, &source).await?; // Git commit - commit_compose(state.repository, &stack_name, &commit_message)?; + compose::commit(state.repository, &stack_name, &commit_message)?; // Update stack - command( + arion::command( &state.stack_dir, &stack_name, &state.arion_bin, - StackCommand::Start, + arion::StackCommand::Start, ) .await?; diff --git a/src/route/stack/mod.rs b/src/route/stack/mod.rs index 39bf3a1..6b60e02 100644 --- a/src/route/stack/mod.rs +++ b/src/route/stack/mod.rs @@ -5,7 +5,11 @@ use axum::{ Router, }; -use crate::{http::error::AppError, node::stack::command, node::stack::StackCommand, AppState}; +use crate::{ + http::error::AppError, + node::stack::arion::{command, StackCommand}, + AppState, +}; use axum::routing::get; diff --git a/src/route/stack/read.rs b/src/route/stack/read.rs index 63c0a28..d88433b 100644 --- a/src/route/stack/read.rs +++ b/src/route/stack/read.rs @@ -4,11 +4,7 @@ use serde_json::json; use crate::{ http::response::{reply, HandlerResponse}, - node::{ - container::ContainerInfo, - nix::parse_arion_compose, - stack::{get_compose, get_containers}, - }, + node::{container::ContainerInfo, nix::parse_arion_compose, stack}, AppState, }; @@ -25,9 +21,9 @@ pub(super) async fn get_one( Path(stack_name): Path, State(state): State, ) -> HandlerResponse { - let file_contents = get_compose(&state.stack_dir, &stack_name).await?; + let file_contents = stack::compose::get(&state.stack_dir, &stack_name).await?; let info = parse_arion_compose(&file_contents)?; - let containers = get_containers(&state.docker, &info.project).await?; + let containers = stack::list::containers(&state.docker, &info.project).await?; reply( json!({