use crate::Result; use crate::cloud::CloudOptions; use crate::config::HazeConfig; use crate::image::pull_image; use crate::service::ServiceTrait; use bollard::Docker; use bollard::models::{ ContainerCreateBody, ContainerState, EndpointSettings, HostConfig, NetworkingConfig, }; use bollard::query_parameters::CreateContainerOptions; use maplit::hashmap; use miette::{IntoDiagnostic, Report}; #[derive(Debug, Clone, Eq, PartialEq)] pub struct Office; #[async_trait::async_trait] impl ServiceTrait for Office { fn name(&self) -> &str { "office" } fn env(&self) -> &[&str] { &["COOL_HOST=office"] } async fn spawn( &self, docker: &Docker, cloud_id: &str, network: &str, config: &HazeConfig, _options: &CloudOptions, ) -> Result> { let network_info = docker .inspect_network(network, None) .await .into_diagnostic()?; let gateway = network_info .ipam .as_ref() .ok_or_else(|| Report::msg("Network has no ip info"))? .config .as_deref() .ok_or_else(|| Report::msg("Network has no ip info"))? .first() .ok_or_else(|| Report::msg("Network has no ip info"))? .gateway .as_deref() .ok_or_else(|| Report::msg("Network has no ip info"))?; let image = "collabora/code"; pull_image(docker, image).await?; let container_id = self.container_name(cloud_id).unwrap(); let options = Some(CreateContainerOptions { name: Some(container_id.clone()), ..CreateContainerOptions::default() }); let mut env = vec![r#"extra_params=--o:ssl.enable=false --o:ssl.termination=true --o:net.frame_ancestors=*"#.to_string()]; let proxy_base = &config.proxy.address; let clean_id = container_id.strip_prefix("haze-").unwrap_or(&container_id); if !proxy_base.is_empty() { env.push(format!("server_name={clean_id}.{}", config.proxy.address)); } let clean_cloud_id = cloud_id.strip_prefix("haze-").unwrap_or(cloud_id); let hosts = if proxy_base.is_empty() { vec![] } else { vec![ format!("{proxy_base}:{gateway}"), format!("{clean_cloud_id}.{proxy_base}:{gateway}"), ] }; let config = ContainerCreateBody { image: Some(image.into()), env: Some(env), host_config: Some(HostConfig { network_mode: Some(network.to_string()), extra_hosts: Some(hosts), ..Default::default() }), labels: Some(hashmap! { "haze-type".into() => self.name().into(), "haze-cloud-id".into() => cloud_id.into(), }), networking_config: Some(NetworkingConfig { endpoints_config: Some(hashmap! { network.into() => EndpointSettings { aliases: Some(vec![self.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]) } fn container_name(&self, cloud_id: &str) -> Option { Some(format!("{}-office", cloud_id)) } fn apps(&self) -> &'static [&'static str] { &["richdocuments"] } async fn post_setup( &self, docker: &Docker, cloud_id: &str, config: &HazeConfig, ) -> Result>> { let container = &self.container_name(cloud_id).unwrap(); let info = docker .inspect_container(container, None) .await .into_diagnostic()?; let ip = if matches!( info.state, Some(ContainerState { running: Some(true), .. }) ) { info.network_settings .unwrap() .networks .unwrap() .values() .next() .unwrap() .ip_address .clone() .unwrap() .parse() .into_diagnostic()? } else { return Err(Report::msg("office not started")); }; let public = config .proxy .addr_with_port(container, ip, self.proxy_port()); Ok(vec![ vec![ "occ".into(), "config:app:set".into(), "richdocuments".into(), "public_wopi_url".into(), "--value".into(), public, ], vec![ "occ".into(), "richdocuments:setup".into(), "--wopi-url".into(), "http://office:9980".into(), "--callback-url".into(), "http://cloud".into(), ], ]) } fn proxy_port(&self) -> u16 { 9980 } }