Public Access
initial-commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
pub mod yaml;
|
||||
@@ -0,0 +1,540 @@
|
||||
use crate::charybdis::entities::{Entity, entity::Metadata, entity::Spec};
|
||||
use serde_json::{Value, json};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Create a Backstage Location YAML that lists all entities
|
||||
pub fn create_location_yaml(entities: &[Entity], base_url: &str) -> String {
|
||||
let mut targets = Vec::new();
|
||||
|
||||
for entity in entities {
|
||||
// Generate URL for each entity
|
||||
let entity_url = format!("{}/yaml/entities/{}", base_url, entity.id);
|
||||
targets.push(entity_url);
|
||||
}
|
||||
|
||||
let location = json!({
|
||||
"apiVersion": "backstage.io/v1alpha1",
|
||||
"kind": "Location",
|
||||
"metadata": {
|
||||
"name": "charybdis-all-entities",
|
||||
"description": "Dynamic location managed by Charybdis"
|
||||
},
|
||||
"spec": {
|
||||
"type": "charybdis",
|
||||
"targets": targets
|
||||
}
|
||||
});
|
||||
|
||||
serde_yaml::to_string(&location).unwrap_or_else(|_| String::from("# Error generating YAML"))
|
||||
}
|
||||
|
||||
/// Convert a Charybdis Entity to Backstage YAML format
|
||||
pub fn entity_to_yaml(entity: &Entity) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let mut backstage_entity: HashMap<String, Value> = HashMap::new();
|
||||
|
||||
// API version
|
||||
backstage_entity.insert("apiVersion".to_string(), json!("backstage.io/v1alpha1"));
|
||||
|
||||
// Kind
|
||||
backstage_entity.insert("kind".to_string(), json!(entity.kind));
|
||||
|
||||
// Metadata
|
||||
let metadata = extract_metadata(entity)?;
|
||||
backstage_entity.insert("metadata".to_string(), metadata);
|
||||
|
||||
// Spec
|
||||
if let Some(spec) = extract_spec(entity) {
|
||||
backstage_entity.insert("spec".to_string(), spec);
|
||||
}
|
||||
|
||||
// Convert to YAML
|
||||
Ok(serde_yaml::to_string(&backstage_entity)?)
|
||||
}
|
||||
|
||||
/// Extract metadata from Entity based on its type
|
||||
fn extract_metadata(entity: &Entity) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
let mut metadata = json!({});
|
||||
|
||||
match &entity.metadata {
|
||||
Some(Metadata::ServiceMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::SystemMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::ComponentMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::ApiMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::UserMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::GroupMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::DomainMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::ResourceMetadata(m)) => {
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.labels.is_empty() {
|
||||
metadata["labels"] = json!(m.labels);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
if !m.links.is_empty() {
|
||||
let links: Vec<Value> = m
|
||||
.links
|
||||
.iter()
|
||||
.map(|link| {
|
||||
json!({
|
||||
"url": link.url,
|
||||
"title": link.title,
|
||||
"icon": link.icon
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
metadata["links"] = json!(links);
|
||||
}
|
||||
}
|
||||
Some(Metadata::DefectdojoMetadata(m)) => {
|
||||
// DefectDojo plugin metadata
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.resource_type.is_empty() {
|
||||
metadata["annotations"] = json!({
|
||||
"defectdojo.com/resource-type": m.resource_type
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(Metadata::KeycloakMetadata(m)) => {
|
||||
// Keycloak plugin metadata
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
let mut annotations = serde_json::Map::new();
|
||||
if !m.resource_type.is_empty() {
|
||||
annotations.insert("keycloak.org/resource-type".to_string(), json!(m.resource_type));
|
||||
}
|
||||
if !m.realm.is_empty() {
|
||||
annotations.insert("keycloak.org/realm".to_string(), json!(m.realm));
|
||||
}
|
||||
if !annotations.is_empty() {
|
||||
metadata["annotations"] = Value::Object(annotations);
|
||||
}
|
||||
}
|
||||
Some(Metadata::DependencytrackMetadata(m)) => {
|
||||
// Dependency-Track plugin metadata
|
||||
metadata["name"] = json!(m.name);
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.resource_type.is_empty() {
|
||||
metadata["annotations"] = json!({
|
||||
"dependencytrack.org/resource-type": m.resource_type
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(Metadata::FindingMetadata(m)) => {
|
||||
metadata["name"] = json!(m.title);
|
||||
if !m.namespace.is_empty() {
|
||||
metadata["namespace"] = json!(m.namespace);
|
||||
}
|
||||
if !m.description.is_empty() {
|
||||
metadata["description"] = json!(m.description);
|
||||
}
|
||||
if !m.tags.is_empty() {
|
||||
metadata["tags"] = json!(m.tags);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
return Err("Entity has no metadata".into());
|
||||
}
|
||||
}
|
||||
|
||||
// Add annotations
|
||||
if !entity.annotations.is_empty() {
|
||||
metadata["annotations"] = json!(entity.annotations);
|
||||
}
|
||||
|
||||
Ok(metadata)
|
||||
}
|
||||
|
||||
/// Extract spec from Entity based on its type
|
||||
fn extract_spec(entity: &Entity) -> Option<Value> {
|
||||
match &entity.spec {
|
||||
Some(Spec::ServiceSpec(s)) => {
|
||||
let mut spec = serde_json::Map::new();
|
||||
spec.insert("type".to_string(), json!(s.r#type));
|
||||
spec.insert("lifecycle".to_string(), json!(s.lifecycle));
|
||||
spec.insert("owner".to_string(), json!(s.owner));
|
||||
if !s.system.is_empty() {
|
||||
spec.insert("system".to_string(), json!(s.system));
|
||||
}
|
||||
if !s.depends_on.is_empty() {
|
||||
spec.insert("dependsOn".to_string(), json!(s.depends_on));
|
||||
}
|
||||
if !s.provides_apis.is_empty() {
|
||||
spec.insert("providesApis".to_string(), json!(s.provides_apis));
|
||||
}
|
||||
if !s.consumes_apis.is_empty() {
|
||||
spec.insert("consumesApis".to_string(), json!(s.consumes_apis));
|
||||
}
|
||||
Some(Value::Object(spec))
|
||||
}
|
||||
Some(Spec::SystemSpec(s)) => {
|
||||
let mut spec = serde_json::Map::new();
|
||||
spec.insert("owner".to_string(), json!(s.owner));
|
||||
if !s.domain.is_empty() {
|
||||
spec.insert("domain".to_string(), json!(s.domain));
|
||||
}
|
||||
Some(Value::Object(spec))
|
||||
}
|
||||
Some(Spec::ComponentSpec(s)) => {
|
||||
let mut spec = serde_json::Map::new();
|
||||
spec.insert("type".to_string(), json!(s.r#type));
|
||||
spec.insert("lifecycle".to_string(), json!(s.lifecycle));
|
||||
spec.insert("owner".to_string(), json!(s.owner));
|
||||
if !s.system.is_empty() {
|
||||
spec.insert("system".to_string(), json!(s.system));
|
||||
}
|
||||
if !s.subcomponent_of.is_empty() {
|
||||
spec.insert("subcomponentOf".to_string(), json!(s.subcomponent_of));
|
||||
}
|
||||
if !s.depends_on.is_empty() {
|
||||
spec.insert("dependsOn".to_string(), json!(s.depends_on));
|
||||
}
|
||||
if !s.provides_apis.is_empty() {
|
||||
spec.insert("providesApis".to_string(), json!(s.provides_apis));
|
||||
}
|
||||
if !s.consumes_apis.is_empty() {
|
||||
spec.insert("consumesApis".to_string(), json!(s.consumes_apis));
|
||||
}
|
||||
Some(Value::Object(spec))
|
||||
}
|
||||
Some(Spec::ApiSpec(s)) => {
|
||||
let mut spec = serde_json::Map::new();
|
||||
spec.insert("type".to_string(), json!(s.r#type));
|
||||
spec.insert("lifecycle".to_string(), json!(s.lifecycle));
|
||||
spec.insert("owner".to_string(), json!(s.owner));
|
||||
if !s.system.is_empty() {
|
||||
spec.insert("system".to_string(), json!(s.system));
|
||||
}
|
||||
spec.insert("definition".to_string(), json!(s.definition));
|
||||
Some(Value::Object(spec))
|
||||
}
|
||||
Some(Spec::UserSpec(s)) => {
|
||||
let mut spec = json!({});
|
||||
if let Some(profile) = &s.profile {
|
||||
spec["profile"] = json!({
|
||||
"displayName": profile.display_name,
|
||||
"email": profile.email,
|
||||
"picture": profile.picture,
|
||||
});
|
||||
}
|
||||
if !s.member_of.is_empty() {
|
||||
spec["memberOf"] = json!(s.member_of);
|
||||
}
|
||||
Some(spec)
|
||||
}
|
||||
Some(Spec::GroupSpec(s)) => {
|
||||
let mut spec = json!({
|
||||
"type": s.r#type,
|
||||
});
|
||||
if let Some(profile) = &s.profile {
|
||||
spec["profile"] = json!({
|
||||
"displayName": profile.display_name,
|
||||
"email": profile.email,
|
||||
"picture": profile.picture,
|
||||
});
|
||||
}
|
||||
if !s.parent.is_empty() {
|
||||
spec["parent"] = json!(s.parent);
|
||||
}
|
||||
if !s.children.is_empty() {
|
||||
spec["children"] = json!(s.children);
|
||||
}
|
||||
if !s.members.is_empty() {
|
||||
spec["members"] = json!(s.members);
|
||||
}
|
||||
Some(spec)
|
||||
}
|
||||
Some(Spec::DomainSpec(s)) => Some(json!({
|
||||
"owner": s.owner,
|
||||
})),
|
||||
Some(Spec::ResourceSpec(s)) => Some(json!({
|
||||
"type": s.r#type,
|
||||
"owner": s.owner,
|
||||
"system": if s.system.is_empty() { Value::Null } else { json!(s.system) },
|
||||
"dependsOn": if s.depends_on.is_empty() { Value::Null } else { json!(s.depends_on) },
|
||||
})),
|
||||
Some(Spec::DefectdojoSpec(s)) => {
|
||||
// DefectDojo plugin spec - parse config_json if present
|
||||
let mut spec = json!({});
|
||||
if !s.config_json.is_empty() {
|
||||
if let Ok(config) = serde_json::from_str::<Value>(&s.config_json) {
|
||||
spec = config;
|
||||
}
|
||||
}
|
||||
if !s.sync_status.is_empty() {
|
||||
spec["sync_status"] = json!(s.sync_status);
|
||||
}
|
||||
Some(spec)
|
||||
}
|
||||
Some(Spec::KeycloakSpec(s)) => {
|
||||
// Keycloak plugin spec - parse config_json if present
|
||||
let mut spec = json!({});
|
||||
if !s.config_json.is_empty() {
|
||||
if let Ok(config) = serde_json::from_str::<Value>(&s.config_json) {
|
||||
spec = config;
|
||||
}
|
||||
}
|
||||
if !s.sync_status.is_empty() {
|
||||
spec["sync_status"] = json!(s.sync_status);
|
||||
}
|
||||
if !s.last_sync.is_empty() {
|
||||
spec["last_sync"] = json!(s.last_sync);
|
||||
}
|
||||
if !s.error_message.is_empty() {
|
||||
spec["error_message"] = json!(s.error_message);
|
||||
}
|
||||
Some(spec)
|
||||
}
|
||||
Some(Spec::DependencytrackSpec(s)) => {
|
||||
// Dependency-Track plugin spec - parse config_json if present
|
||||
let mut spec = json!({});
|
||||
if !s.config_json.is_empty() {
|
||||
if let Ok(config) = serde_json::from_str::<Value>(&s.config_json) {
|
||||
spec = config;
|
||||
}
|
||||
}
|
||||
if !s.sync_status.is_empty() {
|
||||
spec["sync_status"] = json!(s.sync_status);
|
||||
}
|
||||
if !s.last_sync.is_empty() {
|
||||
spec["last_sync"] = json!(s.last_sync);
|
||||
}
|
||||
if !s.error_message.is_empty() {
|
||||
spec["error_message"] = json!(s.error_message);
|
||||
}
|
||||
Some(spec)
|
||||
}
|
||||
Some(Spec::FindingSpec(s)) => {
|
||||
let mut spec = json!({});
|
||||
spec["component_ref"] = json!(s.component_ref);
|
||||
spec["lifecycle"] = json!(s.lifecycle);
|
||||
spec["severity"] = json!(s.severity);
|
||||
spec["state"] = json!(s.state);
|
||||
spec["scanner"] = json!(s.scanner);
|
||||
spec["rule_id"] = json!(s.rule_id);
|
||||
if !s.file_path.is_empty() {
|
||||
spec["file_path"] = json!(s.file_path);
|
||||
}
|
||||
if s.line_start > 0 {
|
||||
spec["line_start"] = json!(s.line_start);
|
||||
}
|
||||
if !s.cwe.is_empty() {
|
||||
spec["cwe"] = json!(s.cwe);
|
||||
}
|
||||
if !s.cve.is_empty() {
|
||||
spec["cve"] = json!(s.cve);
|
||||
}
|
||||
Some(spec)
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
use crate::database::EntityRepository;
|
||||
use axum::{
|
||||
Router,
|
||||
extract::{Path, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
mod backstage;
|
||||
|
||||
struct CachedResponse {
|
||||
data: String,
|
||||
cached_at: Instant,
|
||||
}
|
||||
|
||||
/// State shared across YAML adapter handlers
|
||||
#[derive(Clone)]
|
||||
pub struct YamlAdapterState {
|
||||
pub repository: Arc<EntityRepository>,
|
||||
pub base_url: String,
|
||||
cache: Arc<RwLock<Option<CachedResponse>>>,
|
||||
cache_ttl: Duration,
|
||||
}
|
||||
|
||||
impl YamlAdapterState {
|
||||
/// Invalidate the locations cache (for event-based invalidation on entity changes)
|
||||
pub async fn invalidate_cache(&self) {
|
||||
let mut cache = self.cache.write().await;
|
||||
*cache = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// Start the YAML adapter HTTP server
|
||||
pub async fn start_yaml_adapter(
|
||||
host: String,
|
||||
port: u16,
|
||||
repository: Arc<EntityRepository>,
|
||||
base_url: String,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let state = YamlAdapterState {
|
||||
repository,
|
||||
base_url,
|
||||
cache: Arc::new(RwLock::new(None)),
|
||||
cache_ttl: Duration::from_secs(30),
|
||||
};
|
||||
|
||||
let app = Router::new()
|
||||
.route("/yaml/locations", get(get_locations))
|
||||
.route("/yaml/entities/:id", get(get_entity))
|
||||
.with_state(state);
|
||||
|
||||
let addr = format!("{}:{}", host, port);
|
||||
info!("Starting YAML adapter HTTP server on {}", addr);
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// GET /yaml/locations
|
||||
/// Returns a Backstage Location entity listing all entities in Charybdis
|
||||
async fn get_locations(State(state): State<YamlAdapterState>) -> Response {
|
||||
info!("Received GET /yaml/locations request");
|
||||
|
||||
// Check cache first
|
||||
{
|
||||
let cache = state.cache.read().await;
|
||||
if let Some(ref cached) = *cache {
|
||||
if cached.cached_at.elapsed() < state.cache_ttl {
|
||||
debug!("Serving /yaml/locations from cache");
|
||||
return (
|
||||
StatusCode::OK,
|
||||
[("content-type", "text/yaml; charset=utf-8")],
|
||||
cached.data.clone(),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss or expired — fetch from database
|
||||
match state.repository.list_all().await {
|
||||
Ok(entities) => {
|
||||
let location_yaml = backstage::create_location_yaml(&entities, &state.base_url);
|
||||
|
||||
// Update cache
|
||||
{
|
||||
let mut cache = state.cache.write().await;
|
||||
*cache = Some(CachedResponse {
|
||||
data: location_yaml.clone(),
|
||||
cached_at: Instant::now(),
|
||||
});
|
||||
}
|
||||
|
||||
(
|
||||
StatusCode::OK,
|
||||
[("content-type", "text/yaml; charset=utf-8")],
|
||||
location_yaml,
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
error!(error = %e, "Failed to list entities for location");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to retrieve entities",
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// GET /yaml/entities/:id
|
||||
/// Returns a specific entity in Backstage YAML format
|
||||
async fn get_entity(Path(id): Path<String>, State(state): State<YamlAdapterState>) -> Response {
|
||||
info!(entity_id = %id, "Received GET /yaml/entities/:id request");
|
||||
|
||||
match state.repository.get_by_id(&id).await {
|
||||
Ok(Some(entity)) => match backstage::entity_to_yaml(&entity) {
|
||||
Ok(yaml) => (
|
||||
StatusCode::OK,
|
||||
[("content-type", "text/yaml; charset=utf-8")],
|
||||
yaml,
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => {
|
||||
error!(entity_id = %id, error = %e, "Failed to convert entity to YAML");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to convert entity to YAML",
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
},
|
||||
Ok(None) => {
|
||||
info!(entity_id = %id, "Entity not found");
|
||||
(StatusCode::NOT_FOUND, "Entity not found").into_response()
|
||||
}
|
||||
Err(e) => {
|
||||
error!(entity_id = %id, error = %e, "Failed to retrieve entity");
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
"Failed to retrieve entity",
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user