use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::exec::exec; use crate::image::pull_image; use crate::service::{split_cmnd, ServiceTrait}; use crate::Result; use bollard::models::{ ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, }; use bollard::query_parameters::CreateContainerOptions; use bollard::Docker; use maplit::hashmap; use miette::{IntoDiagnostic, WrapErr}; use serde_json::Value; use std::collections::HashMap; use std::fs::{create_dir_all, write}; #[derive(Debug, Clone, Eq, PartialEq)] pub enum ObjectStore { S3, S3s, S3m, S3mb, Azure, } impl ObjectStore { fn image(&self) -> &str { match self { ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => { "minio/minio:RELEASE.2024-07-16T23-46-41Z" } ObjectStore::Azure => "arafato/azurite:2.6.5", } } fn self_env(&self) -> Vec<&str> { match self { ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => { vec!["MINIO_ACCESS_KEY=minio", "MINIO_SECRET_KEY=minio123"] } ObjectStore::Azure => vec![], } } fn host_name(&self) -> &str { match self { ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => "s3", ObjectStore::Azure => "azure", } } fn args(&self) -> &[&str] { match self { ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => { &["server", "/data"] } _ => &[], } } fn volumes(&self, config: &HazeConfig) -> Option> { match self { ObjectStore::S3s => { let cert_dir = config.work_dir.join("certificates/s3"); create_dir_all(&cert_dir) .into_diagnostic() .wrap_err("Failed to create redis certificate directory") .unwrap(); let s3_cert_path = config.work_dir.join("certificates/s3/public.crt"); let s3_key_path = config.work_dir.join("certificates/s3/private.key"); if !s3_cert_path.exists() { write( &s3_cert_path, include_bytes!("../../certificates/s3/public.crt"), ) .into_diagnostic() .wrap_err("Failed to write s3 certificate") .unwrap(); } if !s3_key_path.exists() { write( &s3_key_path, include_bytes!("../../certificates/s3/private.key"), ) .into_diagnostic() .wrap_err("Failed to write s3 key") .unwrap(); } Some(vec![format!("{cert_dir}:/root/.minio/certs:ro")]) } _ => None, } } } #[async_trait::async_trait] impl ServiceTrait for ObjectStore { fn name(&self) -> &str { match self { ObjectStore::S3 => "s3", ObjectStore::S3s => "s3s", ObjectStore::S3m => "s3m", ObjectStore::S3mb => "s3mb", ObjectStore::Azure => "azure", } } fn env(&self) -> &[&str] { match self { ObjectStore::S3 => &["S3=1"], ObjectStore::S3s => &["S3S=1"], ObjectStore::S3m => &["S3M=1"], ObjectStore::S3mb => &["S3MB =1"], ObjectStore::Azure => &["AZURE=1"], } } async fn spawn( &self, docker: &Docker, cloud_id: &str, network: &str, config: &HazeConfig, _options: &CloudOptions, ) -> Result> { pull_image(docker, self.image()).await?; let options = Some(CreateContainerOptions { name: Some(format!("{}-object", cloud_id)), ..CreateContainerOptions::default() }); let config = ContainerCreateBody { image: Some(self.image().into()), env: Some(self.self_env().into_iter().map(String::from).collect()), host_config: Some(HostConfig { network_mode: Some(network.to_string()), binds: self.volumes(config), ..Default::default() }), labels: Some(hashmap! { "haze-type".into() => self.name().into(), "haze-cloud-id".into() => cloud_id.into(), }), cmd: Some(self.args().iter().copied().map(String::from).collect()), networking_config: Some(NetworkingConfig { endpoints_config: Some(hashmap! { network.into() => EndpointSettings { aliases: Some(vec![self.host_name().to_string()]), ..Default::default() } }), }), ..Default::default() }; let id = docker .create_container(options, config) .await .into_diagnostic()? .id; docker.start_container(&id, None).await.into_diagnostic()?; Ok(vec![id]) } async fn is_healthy( &self, docker: &Docker, cloud_id: &str, _options: &CloudOptions, ) -> Result { if !self.is_running(docker, cloud_id).await? { return Ok(false); } match self { ObjectStore::S3 | ObjectStore::S3mb => { let mut output = Vec::new(); let exit = exec( docker, format!("{}-object", cloud_id), "root", vec!["curl", "localhost:9000/minio/health/ready"], Vec::::default(), Some(&mut output), ) .await?; Ok(exit.is_ok()) } _ => { let info = docker .inspect_container(&self.container_name(cloud_id).unwrap(), None) .await .into_diagnostic()?; Ok(matches!( info.state, Some(ContainerState { running: Some(true), .. }) )) } } } fn container_name(&self, cloud_id: &str) -> Option { Some(format!("{}-object", cloud_id)) } fn apps(&self) -> &'static [&'static str] { &["files_external"] } fn config( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, ) -> Result> { match self { ObjectStore::S3s => Ok(hashmap![ "default_certificates_bundle_path".into() => Value::String("/var/www/html/data/ca-bundle.crt".into()), ]), _ => Ok(HashMap::default()), } } fn pre_setup( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, ) -> Result>> { match self { ObjectStore::S3s => Ok(vec![ vec!["mkdir".into(), "-p".into(), "/var/www/html/data".into()], vec![ "sh".into(), "-c".into(), "cat /var/www/html/resources/config/ca-bundle.crt /certificates/s3/public.crt > /var/www/html/data/ca-bundle.crt".into(), ], ]), _ => Ok(Vec::new()), } } async fn post_setup( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, ) -> Result>> { match self { ObjectStore::S3 => Ok(vec![ split_cmnd("occ files_external:create s3 amazons3 amazons3::accesskey"), split_cmnd("occ files_external:config 1 bucket ext"), split_cmnd("occ files_external:config 1 hostname s3"), split_cmnd("occ files_external:config 1 port 9000"), split_cmnd("occ files_external:config 1 use_ssl false"), split_cmnd("occ files_external:config 1 use_path_style true"), split_cmnd("occ files_external:config 1 key minio"), split_cmnd("occ files_external:config 1 secret minio123"), split_cmnd("mc alias set s3 http://s3:9000 minio minio123"), ]), // ObjectStore::S3s => Ok(vec![ // "occ files_external:create s3 amazons3 amazons3::accesskey".into(), // "occ files_external:config 1 bucket ext".into(), // "occ files_external:config 1 hostname s3".into(), // "occ files_external:config 1 port 9000".into(), // "occ files_external:config 1 use_ssl true".into(), // "occ files_external:config 1 use_path_style true".into(), // "occ files_external:config 1 key minio".into(), // "occ files_external:config 1 secret minio123".into(), // "mc alias set s3 https://s3:9000 minio minio123".into(), // ]), _ => Ok(Vec::new()), } } fn proxy_port(&self) -> u16 { match self { ObjectStore::S3 | ObjectStore::S3m | ObjectStore::S3mb | ObjectStore::S3s => 9000, ObjectStore::Azure => 10000, } } }