use std::sync::Arc; use async_trait::async_trait; use http::header::{ ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ALLOW, }; use hyper::{Response, StatusCode}; use garage_model::garage::Garage; use garage_rpc::system::ClusterHealthStatus; use crate::admin::api::{CheckDomainRequest, HealthRequest, OptionsRequest}; use crate::admin::api_server::ResBody; use crate::admin::error::*; use crate::admin::EndpointHandler; use crate::helpers::*; #[async_trait] impl EndpointHandler for OptionsRequest { type Response = Response; async fn handle(self, _garage: &Arc) -> Result, Error> { Ok(Response::builder() .status(StatusCode::OK) .header(ALLOW, "OPTIONS,GET,POST") .header(ACCESS_CONTROL_ALLOW_METHODS, "OPTIONS,GET,POST") .header(ACCESS_CONTROL_ALLOW_HEADERS, "authorization,content-type") .header(ACCESS_CONTROL_ALLOW_ORIGIN, "*") .body(empty_body())?) } } #[async_trait] impl EndpointHandler for CheckDomainRequest { type Response = Response; async fn handle(self, garage: &Arc) -> Result, Error> { if check_domain(garage, &self.domain).await? { Ok(Response::builder() .status(StatusCode::OK) .body(string_body(format!( "Domain '{}' is managed by Garage", self.domain )))?) } else { Err(Error::bad_request(format!( "Domain '{}' is not managed by Garage", self.domain ))) } } } async fn check_domain(garage: &Arc, domain: &str) -> Result { // Resolve bucket from domain name, inferring if the website must be activated for the // domain to be valid. let (bucket_name, must_check_website) = if let Some(bname) = garage .config .s3_api .root_domain .as_ref() .and_then(|rd| host_to_bucket(domain, rd)) { (bname.to_string(), false) } else if let Some(bname) = garage .config .s3_web .as_ref() .and_then(|sw| host_to_bucket(domain, sw.root_domain.as_str())) { (bname.to_string(), true) } else { (domain.to_string(), true) }; let bucket_id = match garage .bucket_helper() .resolve_global_bucket_name(&bucket_name) .await? { Some(bucket_id) => bucket_id, None => return Ok(false), }; if !must_check_website { return Ok(true); } let bucket = garage .bucket_helper() .get_existing_bucket(bucket_id) .await?; let bucket_state = bucket.state.as_option().unwrap(); let bucket_website_config = bucket_state.website_config.get(); match bucket_website_config { Some(_v) => Ok(true), None => Ok(false), } } #[async_trait] impl EndpointHandler for HealthRequest { type Response = Response; async fn handle(self, garage: &Arc) -> Result, Error> { let health = garage.system.health(); let (status, status_str) = match health.status { ClusterHealthStatus::Healthy => (StatusCode::OK, "Garage is fully operational"), ClusterHealthStatus::Degraded => ( StatusCode::OK, "Garage is operational but some storage nodes are unavailable", ), ClusterHealthStatus::Unavailable => ( StatusCode::SERVICE_UNAVAILABLE, "Quorum is not available for some/all partitions, reads and writes will fail", ), }; let status_str = format!( "{}\nConsult the full health check API endpoint at /v2/GetClusterHealth for more details\n", status_str ); Ok(Response::builder() .status(status) .header(http::header::CONTENT_TYPE, "text/plain") .body(string_body(status_str))?) } }