multi file work
This commit is contained in:
parent
447194252a
commit
91e4b5dee8
8 changed files with 114 additions and 46 deletions
|
@ -1,3 +0,0 @@
|
||||||
[target.x86_64-unknown-linux-gnu]
|
|
||||||
linker = "clang"
|
|
||||||
rustflags = ["-Clink-arg=-fuse-ld=mold"]
|
|
|
@ -91,13 +91,13 @@ impl ThreadSafeRepository {
|
||||||
Ok(oid)
|
Ok(oid)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit_files(&self, paths: &[&Path], message: &str) -> Result<()> {
|
pub fn commit_files<P: AsRef<Path>>(&self, paths: &[P], message: &str) -> Result<()> {
|
||||||
let repository = self.repository()?;
|
let repository = self.repository()?;
|
||||||
|
|
||||||
// Commit file
|
// Commit file
|
||||||
let mut index = repository.index()?;
|
let mut index = repository.index()?;
|
||||||
for path in paths {
|
for path in paths {
|
||||||
index.add_path(path)?;
|
index.add_path(path.as_ref())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.commit(repository, index, message)?;
|
self.commit(repository, index, message)?;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
@ -8,13 +10,13 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
http::error::{AppError, Result},
|
http::error::{AppError, Result},
|
||||||
stack::{arion, compose},
|
stack::{self, arion, compose, FileList, COMPOSE_FILE},
|
||||||
AppState,
|
AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub(super) struct EditStackForm {
|
pub(super) struct EditStackForm {
|
||||||
source: String,
|
files: HashMap<String, String>,
|
||||||
commit_message: String,
|
commit_message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,21 +31,21 @@ pub(super) async fn edit_stack(
|
||||||
form.commit_message
|
form.commit_message
|
||||||
};
|
};
|
||||||
|
|
||||||
if form.source.trim().is_empty() {
|
if form.files.is_empty() {
|
||||||
return Err(AppError::Client {
|
return Err(AppError::Client {
|
||||||
status: StatusCode::BAD_REQUEST,
|
status: StatusCode::BAD_REQUEST,
|
||||||
code: "invalid-source",
|
code: "invalid-files",
|
||||||
message: "provided stack source is empty".to_string(),
|
message: "no files provided".to_string(),
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
// Cleanup source (like line endings)
|
// Cleanup source (like line endings) in each file
|
||||||
let source = form.source.replace("\r\n", "\n");
|
let mut files = FileList::new();
|
||||||
|
for (name, source) in form.files {
|
||||||
|
files.insert(name, source.replace("\r\n", "\n"));
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure file is ok
|
edit_stack_int(state, &stack_name, &files, &commit_message).await?;
|
||||||
compose::check(&state.arion_bin, &source).await?;
|
|
||||||
|
|
||||||
edit_stack_int(state, &stack_name, &source, &commit_message).await?;
|
|
||||||
|
|
||||||
Ok(Redirect::to("./"))
|
Ok(Redirect::to("./"))
|
||||||
}
|
}
|
||||||
|
@ -51,17 +53,20 @@ pub(super) async fn edit_stack(
|
||||||
pub(super) async fn edit_stack_int(
|
pub(super) async fn edit_stack_int(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
stack_name: &str,
|
stack_name: &str,
|
||||||
source: &str,
|
files: &FileList,
|
||||||
commit_message: &str,
|
commit_message: &str,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
// Get compose
|
||||||
|
if let Some(compose_file) = files.get(COMPOSE_FILE) {
|
||||||
// Make sure file is ok
|
// Make sure file is ok
|
||||||
compose::check(&state.arion_bin, &source).await?;
|
compose::check(&state.arion_bin, compose_file).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Write compose file
|
// Write compose file
|
||||||
compose::write(&state.stack_dir, stack_name, source).await?;
|
stack::write(&state.stack_dir, stack_name, files).await?;
|
||||||
|
|
||||||
// Git commit
|
// Git commit
|
||||||
compose::commit(state.repository, stack_name, commit_message)?;
|
compose::commit(state.repository, stack_name, files, commit_message)?;
|
||||||
|
|
||||||
// Update stack
|
// Update stack
|
||||||
arion::command(
|
arion::command(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
extract::{Path, State},
|
||||||
|
@ -63,10 +65,13 @@ pub(super) async fn restore(
|
||||||
let source = state.repository.get_file_at_commit(&path, &form.oid)?;
|
let source = state.repository.get_file_at_commit(&path, &form.oid)?;
|
||||||
let short_id = form.oid[..6].to_string();
|
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(
|
edit_stack_int(
|
||||||
state,
|
state,
|
||||||
&stack_name,
|
&stack_name,
|
||||||
&source,
|
&files,
|
||||||
format!("Revert to {}", short_id).as_str(),
|
format!("Revert to {}", short_id).as_str(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use anyhow::anyhow;
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::extract::{Path, State};
|
use axum::extract::{Path, State};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -6,14 +7,15 @@ use crate::{
|
||||||
http::response::{reply, HandlerResponse},
|
http::response::{reply, HandlerResponse},
|
||||||
node::container::ContainerInfo,
|
node::container::ContainerInfo,
|
||||||
node::nix::parse_arion_compose,
|
node::nix::parse_arion_compose,
|
||||||
stack, AppState,
|
stack::{self, FileList, COMPOSE_FILE},
|
||||||
|
AppState,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "stack/get-one.html")]
|
#[template(path = "stack/get-one.html")]
|
||||||
struct GetOneTemplate {
|
struct GetOneTemplate {
|
||||||
stack_name: String,
|
stack_name: String,
|
||||||
file_contents: String,
|
files: FileList,
|
||||||
containers: Vec<ContainerInfo>,
|
containers: Vec<ContainerInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,20 +23,24 @@ pub(super) async fn get_one(
|
||||||
Path(stack_name): Path<String>,
|
Path(stack_name): Path<String>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> HandlerResponse {
|
) -> HandlerResponse {
|
||||||
let file_contents = stack::compose::get(&state.stack_dir, &stack_name).await?;
|
let files = stack::get(&state.stack_dir, &stack_name).await?;
|
||||||
let info = parse_arion_compose(&file_contents)?;
|
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?;
|
let containers = stack::list::containers(&state.docker, &info.project).await?;
|
||||||
|
|
||||||
reply(
|
reply(
|
||||||
json!({
|
json!({
|
||||||
"folder": stack_name,
|
"folder": stack_name,
|
||||||
"name": info.project,
|
"name": info.project,
|
||||||
"file": file_contents,
|
"files": files,
|
||||||
"containers": containers,
|
"containers": containers,
|
||||||
}),
|
}),
|
||||||
GetOneTemplate {
|
GetOneTemplate {
|
||||||
stack_name: info.project,
|
stack_name: info.project,
|
||||||
file_contents,
|
files,
|
||||||
containers: containers.iter().map(|c| c.clone().into()).collect(),
|
containers: containers.iter().map(|c| c.clone().into()).collect(),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
node::{error::StackError, nix::parse_arion_compose},
|
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<String> {
|
pub async fn get(base_dir: &Path, stack_name: &str) -> Result<String> {
|
||||||
let dir = base_dir.join(stack_name);
|
let dir = base_dir.join(stack_name);
|
||||||
|
@ -23,18 +23,19 @@ pub async fn get(base_dir: &Path, stack_name: &str) -> Result<String> {
|
||||||
Ok(contents)
|
Ok(contents)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn write(base_dir: &Path, stack_name: &str, contents: &str) -> Result<()> {
|
pub fn commit(
|
||||||
let dir = base_dir.join(stack_name);
|
repository: ThreadSafeRepository,
|
||||||
if !utils::is_stack(&dir).await? {
|
stack_name: &str,
|
||||||
return Err(StackError::NotFound.into());
|
files: &FileList,
|
||||||
}
|
message: &str,
|
||||||
|
) -> Result<()> {
|
||||||
Ok(fs::write(dir.join(COMPOSE_FILE), contents).await?)
|
repository.commit_files(
|
||||||
}
|
&files
|
||||||
|
.keys()
|
||||||
pub fn commit(repository: ThreadSafeRepository, stack_name: &str, message: &str) -> Result<()> {
|
.map(|key| PathBuf::from(format!("{}/{}", stack_name, key)))
|
||||||
let compose_path = path(stack_name);
|
.collect::<Vec<_>>(),
|
||||||
repository.commit_files(&[&compose_path], message)?;
|
message,
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use futures_util::future::join_all;
|
||||||
use serde::Serialize;
|
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 arion;
|
||||||
pub mod compose;
|
pub mod compose;
|
||||||
|
@ -30,7 +34,57 @@ pub struct StackInfo {
|
||||||
pub services: Vec<ServiceInfo>,
|
pub services: Vec<ServiceInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMPOSE_FILE: &str = "arion-compose.nix";
|
pub const COMPOSE_FILE: &str = "arion-compose.nix";
|
||||||
const PACKAGE_FILE: &str = "arion-pkgs.nix";
|
pub const PACKAGE_FILE: &str = "arion-pkgs.nix";
|
||||||
const PACKAGE_CONTENTS: &str = r#"import <nixpkgs> { system = "x86_64-linux"; }
|
const PACKAGE_CONTENTS: &str = r#"import <nixpkgs> { system = "x86_64-linux"; }
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
pub type FileList = HashMap<String, String>;
|
||||||
|
|
||||||
|
pub async fn get(base_dir: &Path, stack_name: &str) -> Result<FileList> {
|
||||||
|
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::<Result<Result<_, _>, _>>()??)
|
||||||
|
}
|
||||||
|
|
||||||
|
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::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
</h2>
|
</h2>
|
||||||
<form method="POST" action="./edit" id="editor-form">
|
<form method="POST" action="./edit" id="editor-form">
|
||||||
<div class="error"></div>
|
<div class="error"></div>
|
||||||
<textarea name="source" id="editor">{{file_contents}}</textarea>
|
<textarea name="source" id="editor">{{files["arion-compose.nix"]}}</textarea>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<input style="flex:1" name="commit_message" type="text"
|
<input style="flex:1" name="commit_message" type="text"
|
||||||
placeholder="What did you change?" />
|
placeholder="What did you change?" />
|
||||||
|
|
Reference in a new issue