use crate::config::{HazeConfig, HazeVolumeConfig}; use camino::{Utf8Path, Utf8PathBuf}; use miette::{IntoDiagnostic, Result}; use std::borrow::Cow; use tokio::fs::{create_dir_all, write}; #[derive(Debug)] pub struct Mapping<'a> { source_type: MappingSourceType, pub source: Cow<'a, Utf8Path>, pub target: Cow<'a, Utf8Path>, mapping_type: MappingType, read_only: bool, map: bool, create: bool, } impl<'a> Mapping<'a> { pub fn new( source_type: MappingSourceType, source: Source, target: Target, ) -> Self where Target: Into<&'a Utf8Path>, Source: Into<&'a Utf8Path>, { Mapping { source_type, source: Cow::Borrowed(source.into()), target: Cow::Borrowed(target.into()), mapping_type: MappingType::Folder, read_only: false, map: true, create: true, } } pub fn owned( source_type: MappingSourceType, source: Source, target: Target, ) -> Self where Target: Into>, Source: Into>, { Mapping { source_type, source: source.into(), target: target.into(), mapping_type: MappingType::Folder, read_only: false, map: true, create: true, } } pub fn read_only(self) -> Self { Self { read_only: true, ..self } } pub fn dont_map(self) -> Self { Self { map: false, ..self } } pub fn dont_create(self) -> Self { Self { create: false, ..self } } pub fn file(self) -> Self { Self { mapping_type: MappingType::File, ..self } } pub async fn create(&self, id: &str, config: &HazeConfig) -> Result<()> { if !self.create { return Ok(()); } let source = match self.source_type { MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source.as_ref()), MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source.as_ref()), MappingSourceType::Sources => return Ok(()), MappingSourceType::Absolute => self.source.as_ref().into(), }; match self.mapping_type { MappingType::Folder => create_dir_all(source).await.into_diagnostic()?, MappingType::File => write(source, "").await.into_diagnostic()?, } Ok(()) } pub fn source(&self, id: &str, config: &HazeConfig, source_root: &Utf8Path) -> Utf8PathBuf { match self.source_type { MappingSourceType::WorkDir => config.work_dir.join(id).join(self.source.as_ref()), MappingSourceType::GlobalWorkDir => config.work_dir.join(self.source.as_ref()), MappingSourceType::Sources => source_root.join(self.source.as_ref()), MappingSourceType::Absolute => self.source.as_ref().into(), } } pub fn get_volume_arg( &self, id: &str, config: &HazeConfig, source_root: &Utf8Path, ) -> Option { if !self.map { return None; } let source = self.source(id, config, source_root); Some(if self.read_only { format!("{}:{}:ro", source, self.target) } else { format!("{}:{}", source, self.target) }) } } pub fn default_mappings<'a>() -> impl IntoIterator> { use MappingSourceType::*; let mappings = [ Mapping::new(Sources, "", "/var/www/html"), Mapping::new(WorkDir, "data", "/var/www/html/data"), Mapping::new(WorkDir, "config", "/var/www/html/config"), Mapping::new(WorkDir, "store_apps", "/var/www/store_apps"), Mapping::new(WorkDir, "data-autotest", "/var/www/html/data-autotest"), Mapping::new(WorkDir, "skeleton", "/var/www/html/core/skeleton"), Mapping::new( Sources, "core/skeleton/welcome.txt", "/var/www/html/core/skeleton/welcome.txt", ) .file() .read_only() .dont_create(), Mapping::new( WorkDir, "integration/vendor", "/var/www/html/build/integration/vendor", ), Mapping::new( WorkDir, "integration/work", "/var/www/html/build/integration/work", ), Mapping::new( WorkDir, "integration/output", "/var/www/html/build/integration/output", ), Mapping::new( WorkDir, "integration/composer.lock", "/var/www/html/build/integration/composer.lock", ) .file(), Mapping::new( GlobalWorkDir, "composer/cache", "/home/haze/.composer/cache", ), Mapping::new(GlobalWorkDir, "node_modules", "/var/www/html/node_modules"), Mapping::new( GlobalWorkDir, "phpunit-cache", "/var/www/html/tests/.phpunit.result.cache", ) .file(), Mapping::new(WorkDir, "config/CAN_INSTALL", "") .file() .dont_map(), Mapping::new(Sources, ".htaccess", "/var/www/html/.htaccess") .file() .read_only(), Mapping::new(Absolute, "/var/run/docker.sock", "/var/run/docker.sock") .file() .dont_create(), Mapping::new(WorkDir, "xdebug", "/tmp/xdebug"), Mapping::new(WorkDir, "profiling", "/tmp/profiling"), Mapping::new(WorkDir, "php-config", "/config"), ]; IntoIterator::into_iter(mappings) } pub fn for_config<'a>(config: &'a HazeConfig) -> impl Iterator> { let app_dir_mappings = config.app_directories.iter().map(|dir| { Mapping::owned( MappingSourceType::Absolute, dir.as_path(), Cow::Owned(Utf8PathBuf::from(format!( "/var/www/html/{}", dir.file_name().unwrap() ))), ) }); config .volume .iter() .map(Mapping::from) .chain(app_dir_mappings) .chain(default_mappings()) } #[derive(Debug, Copy, Clone)] pub enum MappingSourceType { Sources, WorkDir, GlobalWorkDir, Absolute, } #[derive(Debug, Copy, Clone)] pub enum MappingType { Folder, File, } impl<'a> From<&'a HazeVolumeConfig> for Mapping<'a> { fn from(config: &'a HazeVolumeConfig) -> Self { let ty = if config.source.is_dir() { MappingType::Folder } else { MappingType::File }; Mapping { source_type: MappingSourceType::Absolute, source: Cow::Borrowed(config.source.as_path()), target: Cow::Borrowed(config.target.as_path()), mapping_type: ty, read_only: config.read_only, map: true, create: config.create, } } }