diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index 5909338..0000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[target.x86_64-unknown-linux-gnu] -linker = "clang" -rustflags = ["-Clink-arg=-fuse-ld=mold"] diff --git a/src/git/mod.rs b/src/git/mod.rs index 12a660e..5bf0d2f 100644 --- a/src/git/mod.rs +++ b/src/git/mod.rs @@ -91,13 +91,13 @@ impl ThreadSafeRepository { Ok(oid) } - pub fn commit_files(&self, paths: &[&Path], message: &str) -> Result<()> { + pub fn commit_files>(&self, paths: &[P], message: &str) -> Result<()> { let repository = self.repository()?; // Commit file let mut index = repository.index()?; for path in paths { - index.add_path(path)?; + index.add_path(path.as_ref())?; } self.commit(repository, index, message)?; diff --git a/src/route/stack/edit.rs b/src/route/stack/edit.rs index 07f6dee..c9521fd 100644 --- a/src/route/stack/edit.rs +++ b/src/route/stack/edit.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use axum::{ extract::{Path, State}, http::StatusCode, @@ -8,13 +10,13 @@ use serde::Deserialize; use crate::{ http::error::{AppError, Result}, - stack::{arion, compose}, + stack::{self, arion, compose, FileList, COMPOSE_FILE}, AppState, }; #[derive(Deserialize)] pub(super) struct EditStackForm { - source: String, + files: HashMap, commit_message: String, } @@ -29,21 +31,21 @@ pub(super) async fn edit_stack( form.commit_message }; - if form.source.trim().is_empty() { + if form.files.is_empty() { return Err(AppError::Client { status: StatusCode::BAD_REQUEST, - code: "invalid-source", - message: "provided stack source is empty".to_string(), + code: "invalid-files", + message: "no files provided".to_string(), }); - }; + } - // Cleanup source (like line endings) - let source = form.source.replace("\r\n", "\n"); + // Cleanup source (like line endings) in each file + let mut files = FileList::new(); + for (name, source) in form.files { + files.insert(name, source.replace("\r\n", "\n")); + } - // Make sure file is ok - compose::check(&state.arion_bin, &source).await?; - - edit_stack_int(state, &stack_name, &source, &commit_message).await?; + edit_stack_int(state, &stack_name, &files, &commit_message).await?; Ok(Redirect::to("./")) } @@ -51,17 +53,20 @@ pub(super) async fn edit_stack( pub(super) async fn edit_stack_int( state: AppState, stack_name: &str, - source: &str, + files: &FileList, commit_message: &str, ) -> Result<()> { - // Make sure file is ok - compose::check(&state.arion_bin, &source).await?; + // Get compose + if let Some(compose_file) = files.get(COMPOSE_FILE) { + // Make sure file is ok + compose::check(&state.arion_bin, compose_file).await?; + } // Write compose file - compose::write(&state.stack_dir, stack_name, source).await?; + stack::write(&state.stack_dir, stack_name, files).await?; // Git commit - compose::commit(state.repository, stack_name, commit_message)?; + compose::commit(state.repository, stack_name, files, commit_message)?; // Update stack arion::command( diff --git a/src/route/stack/history.rs b/src/route/stack/history.rs index cca0523..c8a63c7 100644 --- a/src/route/stack/history.rs +++ b/src/route/stack/history.rs @@ -1,3 +1,5 @@ +use std::collections::HashMap; + use askama::Template; use axum::{ extract::{Path, State}, @@ -63,10 +65,13 @@ pub(super) async fn restore( let source = state.repository.get_file_at_commit(&path, &form.oid)?; let short_id = form.oid[..6].to_string(); + let mut files = HashMap::new(); + files.insert(path.to_string_lossy().into_owned(), source); + edit_stack_int( state, &stack_name, - &source, + &files, format!("Revert to {}", short_id).as_str(), ) .await?; diff --git a/src/route/stack/read.rs b/src/route/stack/read.rs index 8c835d7..7b2a3bc 100644 --- a/src/route/stack/read.rs +++ b/src/route/stack/read.rs @@ -1,3 +1,4 @@ +use anyhow::anyhow; use askama::Template; use axum::extract::{Path, State}; use serde_json::json; @@ -6,14 +7,15 @@ use crate::{ http::response::{reply, HandlerResponse}, node::container::ContainerInfo, node::nix::parse_arion_compose, - stack, AppState, + stack::{self, FileList, COMPOSE_FILE}, + AppState, }; #[derive(Template)] #[template(path = "stack/get-one.html")] struct GetOneTemplate { stack_name: String, - file_contents: String, + files: FileList, containers: Vec, } @@ -21,20 +23,24 @@ pub(super) async fn get_one( Path(stack_name): Path, State(state): State, ) -> HandlerResponse { - let file_contents = stack::compose::get(&state.stack_dir, &stack_name).await?; - let info = parse_arion_compose(&file_contents)?; + let files = stack::get(&state.stack_dir, &stack_name).await?; + let compose_file = files + .get(COMPOSE_FILE) + .ok_or(anyhow!("compose file not found in stack"))?; + + let info = parse_arion_compose(compose_file)?; let containers = stack::list::containers(&state.docker, &info.project).await?; reply( json!({ "folder": stack_name, "name": info.project, - "file": file_contents, + "files": files, "containers": containers, }), GetOneTemplate { stack_name: info.project, - file_contents, + files, containers: containers.iter().map(|c| c.clone().into()).collect(), }, ) diff --git a/src/stack/compose.rs b/src/stack/compose.rs index 87569b5..ae00688 100644 --- a/src/stack/compose.rs +++ b/src/stack/compose.rs @@ -11,7 +11,7 @@ use crate::{ node::{error::StackError, nix::parse_arion_compose}, }; -use super::{arion, utils, COMPOSE_FILE, PACKAGE_CONTENTS, PACKAGE_FILE}; +use super::{arion, utils, FileList, COMPOSE_FILE, PACKAGE_CONTENTS, PACKAGE_FILE}; pub async fn get(base_dir: &Path, stack_name: &str) -> Result { let dir = base_dir.join(stack_name); @@ -23,18 +23,19 @@ pub async fn get(base_dir: &Path, stack_name: &str) -> Result { 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 = path(stack_name); - repository.commit_files(&[&compose_path], message)?; +pub fn commit( + repository: ThreadSafeRepository, + stack_name: &str, + files: &FileList, + message: &str, +) -> Result<()> { + repository.commit_files( + &files + .keys() + .map(|key| PathBuf::from(format!("{}/{}", stack_name, key))) + .collect::>(), + message, + )?; Ok(()) } diff --git a/src/stack/mod.rs b/src/stack/mod.rs index 8b6c71d..dd8ad59 100644 --- a/src/stack/mod.rs +++ b/src/stack/mod.rs @@ -1,6 +1,10 @@ +use anyhow::Result; +use futures_util::future::join_all; use serde::Serialize; +use std::{collections::HashMap, path::Path}; +use tokio::fs; -use crate::node::container::ContainerInfo; +use crate::node::{container::ContainerInfo, error::StackError}; pub mod arion; pub mod compose; @@ -30,7 +34,57 @@ pub struct StackInfo { pub services: Vec, } -const COMPOSE_FILE: &str = "arion-compose.nix"; -const PACKAGE_FILE: &str = "arion-pkgs.nix"; +pub const COMPOSE_FILE: &str = "arion-compose.nix"; +pub const PACKAGE_FILE: &str = "arion-pkgs.nix"; const PACKAGE_CONTENTS: &str = r#"import { system = "x86_64-linux"; } "#; + +pub type FileList = HashMap; + +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 mut files = vec![]; + let mut dirs = fs::read_dir(&dir).await?; + while let Some(entry) = dirs.next_entry().await? { + let meta = entry.metadata().await?; + if meta.is_dir() { + continue; + }; + + let path = dir.join(entry.path()); + + files.push(tokio::spawn(async move { + fs::read_to_string(path) + .await + .map(|content| (entry.file_name().to_string_lossy().into_owned(), content)) + })); + } + + Ok(join_all(files) + .await + .into_iter() + .collect::, _>>()??) +} + +pub async fn write(base_dir: &Path, stack_name: &str, files: &FileList) -> Result<()> { + let dir = base_dir.join(stack_name); + if !utils::is_stack(&dir).await? { + return Err(StackError::NotFound.into()); + } + + join_all( + files + .iter() + .map(|(name, content)| (dir.join(name), content)) + .map(|(path, content)| async move { fs::write(path, content).await }), + ) + .await + .into_iter() + .collect::, _>>()?; + + Ok(()) +} diff --git a/templates/stack/get-one.html b/templates/stack/get-one.html index bb87942..5f46e0e 100644 --- a/templates/stack/get-one.html +++ b/templates/stack/get-one.html @@ -62,7 +62,7 @@
- +