aboutsummaryrefslogtreecommitdiff
path: root/src/api/admin/special.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/api/admin/special.rs')
-rw-r--r--src/api/admin/special.rs132
1 files changed, 132 insertions, 0 deletions
diff --git a/src/api/admin/special.rs b/src/api/admin/special.rs
new file mode 100644
index 00000000..da3764d9
--- /dev/null
+++ b/src/api/admin/special.rs
@@ -0,0 +1,132 @@
+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<ResBody>;
+
+ async fn handle(self, _garage: &Arc<Garage>) -> Result<Response<ResBody>, 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<ResBody>;
+
+ async fn handle(self, garage: &Arc<Garage>) -> Result<Response<ResBody>, 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<Garage>, domain: &str) -> Result<bool, Error> {
+ // 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<ResBody>;
+
+ async fn handle(self, garage: &Arc<Garage>) -> Result<Response<ResBody>, 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))?)
+ }
+}