use crate::Result; use crate::cloud::{Cloud, CloudOptions}; use crate::config::{HazeConfig, ProxyConfig}; use crate::service::lookup_server::GSS_JWT_KEY; use crate::service::{LookupServer, ServiceTrait}; use bollard::Docker; use bollard::query_parameters::ListContainersOptions; use maplit::hashmap; use miette::{IntoDiagnostic, Report}; use serde_json::Value; use std::collections::HashMap; /// Cloud id of the most recently started lookup server. async fn master_cloud_id(docker: &Docker) -> Result { let mut lookup_servers: Vec<_> = docker .list_containers(Some(ListContainersOptions { all: true, ..Default::default() })) .await .into_diagnostic()? .iter() .filter_map(|container| { let labels = container.labels.as_ref()?; if labels.get("haze-type").map(String::as_str) != Some(LookupServer.name()) { return None; } let cloud_id = labels.get("haze-cloud-id")?.clone(); Some((container.created.unwrap_or_default(), cloud_id)) }) .collect(); lookup_servers.sort_by(|a, b| a.0.cmp(&b.0).reverse()); lookup_servers.into_iter().map(|(_created, id)| id).next().ok_or_else(|| { Report::msg("No haze instance with a lookup server is running, start one with `haze start lookup`") }) } /// Latest cloud started with a lookup server. async fn master_instance(docker: &Docker, config: &HazeConfig) -> Result { let id = master_cloud_id(docker).await?; Cloud::list(docker, Some(id.clone()), config) .await? .into_iter() .find(|cloud| cloud.id == id) .ok_or_else(|| Report::msg(format!("Failed to get the address of master instance {id}"))) } #[derive(Debug, Clone, Eq, PartialEq)] pub struct GlobalScaleSlave; #[async_trait::async_trait] impl ServiceTrait for GlobalScaleSlave { fn name(&self) -> &str { "slave" } async fn spawn( &self, docker: &Docker, _cloud_id: &str, _network: &str, config: &HazeConfig, _options: &CloudOptions, ) -> Result> { master_instance(docker, config).await?; Ok(Vec::new()) } fn apps(&self) -> &'static [&'static str] { &["globalsiteselector"] } fn config( &self, _docker: &Docker, _cloud_id: &str, _config: &HazeConfig, ) -> Result> { Ok(hashmap! { "gs.enabled".into() => Value::Bool(true), "gss.mode".into() => Value::String("slave".into()), "gss.jwt.key".into() => Value::String(GSS_JWT_KEY.into()), }) } async fn post_setup( &self, docker: &Docker, _cloud_id: &str, config: &HazeConfig, ) -> Result>> { let master = master_instance(docker, config).await?; let lookup_server = LookupServer .container_name(&master.id) .expect("lookup server has a container name"); Ok(vec![ vec![ "occ".into(), "config:system:set".into(), "--silent".into(), "lookup_server".into(), "--value".into(), format!("http://{lookup_server}"), ], vec![ "occ".into(), "config:system:set".into(), "--silent".into(), "gss.master.url".into(), "--value".into(), master.address.clone(), ], vec!["occ".into(), "globalsiteselector:users:update".into()], ]) } async fn start_message( &self, docker: &Docker, _cloud_id: &str, _proxy: &ProxyConfig, ) -> Result> { let master = master_cloud_id(docker).await?; Ok(Some(format!("Nextcloud was setup as slave of {master}."))) } }