1
0
Fork 0
mirror of https://codeberg.org/icewind/haze.git synced 2026-10-01 08:44:09 +02:00

add haze scripts

fixes #27
This commit is contained in:
Robin Appelman 2026-08-17 17:54:27 +02:00
commit 125b68e08a
7 changed files with 207 additions and 9 deletions

View file

@ -1,5 +1,6 @@
use crate::cloud::CloudOptions;
use crate::config::{HazeConfig, Preset};
use crate::script::ScriptMode;
use crate::service::{Service, ServiceTrait};
use miette::{IntoDiagnostic, Report, Result};
use std::fmt::{Debug, Display};
@ -102,6 +103,10 @@ pub enum HazeArgs {
Reload {
filter: Option<String>,
},
Script {
mode: Option<ScriptMode>,
path: String,
},
}
#[derive(
@ -397,6 +402,23 @@ impl HazeArgs {
.into(),
}),
HazeCommand::Reload => Ok(HazeArgs::Reload { filter }),
HazeCommand::Script => {
let mut args = args.peekable();
let mode = match args.peek().map(S::as_ref).map(ScriptMode::from_str) {
Some(Ok(mode)) => {
let _ = args.next();
Some(mode)
}
_ => None,
};
Ok(HazeArgs::Script {
mode,
path: args
.next()
.ok_or_else(|| Report::msg("No path provided"))?
.into(),
})
}
}
}
}
@ -490,6 +512,9 @@ pub enum HazeCommand {
Details = "note: you can overwrite <yellow>php.ini</yellow> settings with <literal>haze</literal> <arg>[filter]</arg> <literal>edit /config/php.ini</literal>"
))]
Reload,
/// Run a haze script
#[strum(props(Args = "[command] path of the haze script to run"))]
Script,
}
impl SubCommand for HazeCommand {

View file

@ -1,16 +1,16 @@
use crate::config::{HazeConfig, HazeVolumeConfig};
use crate::database::Database;
use crate::exec::{exec, exec_io, exec_tty, ExitCode};
use crate::exec::{ExitCode, exec, exec_io, exec_tty};
use crate::git::{create_worktree, find_app_repo, init_submodules, remove_worktree};
use crate::mapping::{for_config, Mapping};
use crate::mapping::{Mapping, for_config};
use crate::php::PhpVersion;
use crate::service::Service;
use crate::service::ServiceTrait;
use crate::sources::download_nc;
use bollard::Docker;
use bollard::config::NetworkCreateRequest;
use bollard::models::ContainerState;
use bollard::query_parameters::{ListContainersOptions, RemoveContainerOptions};
use bollard::Docker;
use camino::{Utf8Path, Utf8PathBuf};
use flate2::read::GzDecoder;
use futures_util::future::try_join_all;
@ -22,8 +22,8 @@ use std::collections::HashMap;
use std::fmt::Display;
use std::fs;
use std::fs::{read_to_string, write};
use std::io::{stdout, Cursor, Read, Stdout, Write};
use std::iter::{once, Peekable};
use std::io::{Cursor, Read, Stdout, Write, stdout};
use std::iter::{Peekable, once};
use std::net::IpAddr;
use std::os::unix::fs::MetadataExt;
use std::str::FromStr;
@ -177,6 +177,7 @@ pub struct CloudOptions {
pub services: Vec<Service>,
pub app_packages: Vec<Utf8PathBuf>,
pub version: Option<String>,
pub mappings: Vec<Mapping<'static>>,
pub detach: bool,
}
@ -191,6 +192,7 @@ impl CloudOptions {
services: vec![],
app_packages: vec![],
version: None,
mappings: vec![],
detach: false,
}
}
@ -243,6 +245,7 @@ impl CloudOptions {
.unwrap_or_default(),
services,
app_packages: app_package,
mappings: vec![],
version,
detach,
})
@ -414,6 +417,7 @@ impl Cloud {
let mappings = for_config(config)
.chain(source_volumes.iter().map(Mapping::from))
.chain(app_volumes.iter().map(Mapping::from))
.chain(options.mappings.iter().cloned())
.collect::<Vec<_>>();
for mapping in &mappings {
mapping
@ -874,6 +878,7 @@ impl Cloud {
db,
services: found_services,
app_packages: vec![],
mappings: vec![],
version,
detach,
},

View file

@ -11,6 +11,7 @@ use crate::image::update_image;
use crate::network::clear_networks;
use crate::php::PhpVersion;
use crate::proxy::proxy;
use crate::script::run_script;
use crate::service::ServiceTrait;
use crate::service::{RedisTls, Service};
use crate::sources::Sources;
@ -35,6 +36,7 @@ mod mapping;
mod network;
mod php;
mod proxy;
mod script;
mod service;
mod sources;
@ -88,7 +90,9 @@ async fn main() -> Result<ExitCode> {
for cache_dir in config.work_dir.read_dir().into_diagnostic()? {
let cache_dir = cache_dir.into_diagnostic()?;
if let Some(id) = cache_dir.file_name().to_str() && id.starts_with("haze-") {
if let Some(id) = cache_dir.file_name().to_str()
&& id.starts_with("haze-")
{
if !retain.iter().any(|cloud| cloud.id == id) {
let path = cache_dir.path();
// By default, the worktrees live within the work dir, we do not want to delete it here.
@ -556,6 +560,9 @@ async fn main() -> Result<ExitCode> {
)
.await?;
}
HazeArgs::Script { path, mode } => {
run_script(path, &config, &docker, mode).await?;
}
};
Ok(ExitCode::SUCCESS)

View file

@ -4,7 +4,7 @@ use miette::{Context, IntoDiagnostic, Result};
use std::borrow::Cow;
use tokio::fs::{create_dir_all, write};
#[derive(Debug)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Mapping<'a> {
source_type: MappingSourceType,
pub source: Cow<'a, Utf8Path>,
@ -244,7 +244,7 @@ pub fn for_config<'a>(config: &'a HazeConfig) -> impl Iterator<Item = Mapping<'a
.chain(default_mappings())
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MappingSourceType {
Sources,
WorkDir,
@ -252,7 +252,7 @@ pub enum MappingSourceType {
Absolute,
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum MappingType {
Folder,
File,

94
src/script.rs Normal file
View file

@ -0,0 +1,94 @@
use std::fs::read_to_string;
use bollard::Docker;
use camino::Utf8PathBuf;
use miette::{Context, IntoDiagnostic, Report, Result};
use strum::EnumString;
use crate::{
cloud::CloudOptions,
config::HazeConfig,
get_forward_env,
mapping::{Mapping, MappingSourceType},
setup,
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, EnumString)]
#[strum(serialize_all = "lowercase")]
pub enum ScriptMode {
Shell,
Start,
}
pub async fn run_script(
path: String,
config: &HazeConfig,
docker: &Docker,
mode_override: Option<ScriptMode>,
) -> Result<()> {
let path: Utf8PathBuf = Utf8PathBuf::from(path)
.canonicalize()
.into_diagnostic()
.wrap_err("Failed to normalize script path")?
.try_into()
.into_diagnostic()
.wrap_err("Non-utf8 script path")?;
let contents = read_to_string(&path)
.into_diagnostic()
.wrap_err_with(|| format!("Failed to read script file {path}"))?;
let (mut options, mode) = parse_script(&contents, config)
.wrap_err_with(|| format!("Failed to parse script file {path}"))?;
let mode = mode_override.unwrap_or(mode);
let target_path = Utf8PathBuf::from(format!("/{}", path.file_name().unwrap()));
let shell = if path.extension() == Some("nu") {
"nu"
} else {
"bash"
};
options.mappings.push(
Mapping::owned(
MappingSourceType::Absolute,
path.clone(),
target_path.clone(),
)
.read_only()
.dont_create(),
);
let cloud = setup(docker, options, &config).await?;
cloud
.exec(
&docker,
vec![shell.to_string(), target_path.into_string()],
true,
get_forward_env(),
)
.await?;
if mode == ScriptMode::Shell {
cloud.destroy(&docker, &config).await?;
}
Ok(())
}
fn parse_script(script: &str, config: &HazeConfig) -> Result<(CloudOptions, ScriptMode)> {
let (options, mode) = script
.lines()
.find_map(|line| line.strip_prefix("#! haze "))
.and_then(
|line| match (line.strip_prefix("shell"), line.strip_prefix("start")) {
(Some(shell_options), None) => Some((shell_options.trim(), ScriptMode::Shell)),
(None, Some(script_options)) => Some((script_options.trim(), ScriptMode::Start)),
_ => None,
},
)
.ok_or_else(|| Report::msg("No '#! haze shell' or '#! haze start' found in script"))?;
let mut options = options.split(' ').peekable();
let options =
CloudOptions::parse(config, &mut options).wrap_err("Failed to parse shell options")?;
Ok((options, mode))
}