use std::sync::Arc;
use endhost_api_discovery_client::client::{
CrpcEndhostApiDiscoveryClient, EndhostApiDiscoveryClient,
};
use endhost_api_discovery_models::{EndhostApiGroup, EndhostApiInfo};
use snap_control::reexport::TokenSource;
use url::Url;
pub mod models {
pub use endhost_api_discovery_models::*;
}
#[derive(Debug, thiserror::Error)]
#[error("Failed to retrieve Endhost APIs: {error}")]
pub struct EndhostApiSourceError {
pub error: anyhow::Error,
pub transient: bool,
}
#[async_trait::async_trait]
pub trait EndhostApiSource: Send + Sync + 'static {
async fn endhost_apis(&self) -> Result<Vec<EndhostApiGroup>, EndhostApiSourceError>;
}
pub struct StaticEndhostApiDiscovery {
discovery_apis: Vec<Url>,
}
impl StaticEndhostApiDiscovery {
const GLOBAL_DISCOVERY_APIS: &[&'static str] = &["https://discovery.scion.anapaya.net"];
pub fn new(discovery_apis: Vec<Url>) -> Self {
Self { discovery_apis }
}
pub fn global() -> Self {
let discovery_apis = Self::GLOBAL_DISCOVERY_APIS
.iter()
.map(|url_str| Url::parse(url_str).expect("Invalid URL in GLOBAL_DISCOVERY_APIS"))
.collect();
Self { discovery_apis }
}
}
#[async_trait::async_trait]
impl EndhostApiSource for StaticEndhostApiDiscovery {
async fn endhost_apis(&self) -> Result<Vec<EndhostApiGroup>, EndhostApiSourceError> {
if self.discovery_apis.is_empty() {
return Err(EndhostApiSourceError {
error: anyhow::anyhow!(
"No Endhost API discovery APIs configured in StaticEndhostApiDiscovery"
),
transient: false,
});
}
discover_endhost_apis(self.discovery_apis.clone(), None).await
}
}
#[derive(Default)]
pub struct StaticEndhostApis {
groups: Vec<EndhostApiGroup>,
}
impl StaticEndhostApis {
pub fn new() -> Self {
Self { groups: Vec::new() }
}
pub fn add_group(mut self, group: Vec<Url>) -> Self {
self.groups.push(EndhostApiGroup {
apis: group
.into_iter()
.map(|url| EndhostApiInfo { address: url })
.collect(),
});
self
}
}
#[async_trait::async_trait]
impl EndhostApiSource for StaticEndhostApis {
async fn endhost_apis(&self) -> Result<Vec<EndhostApiGroup>, EndhostApiSourceError> {
Ok(self.groups.clone())
}
}
async fn discover_endhost_apis(
discovery_apis: Vec<Url>,
token_source: Option<Arc<dyn TokenSource>>,
) -> Result<Vec<EndhostApiGroup>, EndhostApiSourceError> {
let mut last_error = None;
for discovery_api in discovery_apis.iter() {
let client = {
let mut client = match CrpcEndhostApiDiscoveryClient::new(discovery_api) {
Ok(client) => client,
Err(e) => {
tracing::warn!(%discovery_api, error = ?e, "Failed to create Endhost API discovery client");
last_error = Some(EndhostApiSourceError {
error: e.context(format!(
"Failed to create Endhost API discovery client for {}",
discovery_api
)),
transient: false,
});
continue;
}
};
if let Some(token_source) = token_source.clone() {
client.use_token_source(token_source);
}
client
};
match client.discover_endhost_apis().await {
Ok(discovered_apis) => {
tracing::debug!(%discovery_api, "Successfully discovered Endhost APIs");
return Ok(discovered_apis);
}
Err(e) => {
tracing::warn!(%discovery_api, error = ?e, "Failed to discover Endhost APIs");
last_error = Some(EndhostApiSourceError {
error: anyhow::Error::new(e),
transient: true,
});
continue;
}
}
}
match last_error {
Some(e) => {
let transient = e.transient;
Err(EndhostApiSourceError {
error: anyhow::Error::new(e)
.context("Failed to discover Endhost APIs using any configured discovery APIs"),
transient,
})
}
None => {
Err(EndhostApiSourceError {
error: anyhow::anyhow!(
"Attempted to discover Endhost APIs with empty list of discovery APIs"
),
transient: false,
})
}
}
}