pub mod api;
pub mod admission;
pub mod watch;
use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tracing::{info, warn};
pub use api::{ApiServer, ApiServerConfig};
pub use admission::{AdmissionController, AdmissionResult, ValidationWebhook, MutatingWebhook};
pub use watch::{WatchEvent, WatchStream, ResourceVersion};
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ResourceKind {
Workload,
Node,
Service,
ConfigMap,
Secret,
PriorityClass,
Custom(String),
}
impl std::fmt::Display for ResourceKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResourceKind::Workload => write!(f, "workloads"),
ResourceKind::Node => write!(f, "nodes"),
ResourceKind::Service => write!(f, "services"),
ResourceKind::ConfigMap => write!(f, "configmaps"),
ResourceKind::Secret => write!(f, "secrets"),
ResourceKind::PriorityClass => write!(f, "priorityclasses"),
ResourceKind::Custom(name) => write!(f, "{}", name),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectMeta {
pub name: String,
pub namespace: String,
pub uid: String,
pub resource_version: u64,
pub generation: u64,
pub creation_timestamp: chrono::DateTime<chrono::Utc>,
pub deletion_timestamp: Option<chrono::DateTime<chrono::Utc>>,
pub labels: HashMap<String, String>,
pub annotations: HashMap<String, String>,
pub owner_references: Vec<OwnerReference>,
pub finalizers: Vec<String>,
}
impl ObjectMeta {
pub fn new(name: impl Into<String>, namespace: impl Into<String>) -> Self {
Self {
name: name.into(),
namespace: namespace.into(),
uid: uuid::Uuid::new_v4().to_string(),
resource_version: 1,
generation: 1,
creation_timestamp: chrono::Utc::now(),
deletion_timestamp: None,
labels: HashMap::new(),
annotations: HashMap::new(),
owner_references: Vec::new(),
finalizers: Vec::new(),
}
}
pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.labels.insert(key.into(), value.into());
self
}
pub fn with_annotation(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.annotations.insert(key.into(), value.into());
self
}
pub fn with_finalizer(mut self, finalizer: impl Into<String>) -> Self {
self.finalizers.push(finalizer.into());
self
}
pub fn bump_version(&mut self) {
self.resource_version += 1;
}
pub fn bump_generation(&mut self) {
self.generation += 1;
self.bump_version();
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OwnerReference {
pub api_version: String,
pub kind: String,
pub name: String,
pub uid: String,
pub controller: bool,
pub block_owner_deletion: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Resource<T> {
pub api_version: String,
pub kind: ResourceKind,
pub metadata: ObjectMeta,
pub spec: T,
pub status: Option<serde_json::Value>,
}
impl<T> Resource<T> {
pub fn new(kind: ResourceKind, name: impl Into<String>, spec: T) -> Self {
Self {
api_version: "forge.io/v1".to_string(),
kind,
metadata: ObjectMeta::new(name, "default"),
spec,
status: None,
}
}
pub fn in_namespace(mut self, namespace: impl Into<String>) -> Self {
self.metadata.namespace = namespace.into();
self
}
pub fn with_label(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata = self.metadata.with_label(key, value);
self
}
}
pub struct ResourceStore {
resources: RwLock<HashMap<ResourceKind, HashMap<String, serde_json::Value>>>,
version_counter: RwLock<u64>,
watchers: Arc<watch::WatchRegistry>,
}
impl ResourceStore {
pub fn new() -> Self {
Self {
resources: RwLock::new(HashMap::new()),
version_counter: RwLock::new(0),
watchers: Arc::new(watch::WatchRegistry::new()),
}
}
fn next_version(&self) -> u64 {
let mut counter = self.version_counter.write();
*counter += 1;
*counter
}
pub fn create(&self, kind: ResourceKind, key: &str, value: serde_json::Value) -> Result<u64, StoreError> {
let mut resources = self.resources.write();
let kind_map = resources.entry(kind.clone()).or_insert_with(HashMap::new);
if kind_map.contains_key(key) {
return Err(StoreError::AlreadyExists(key.to_string()));
}
let version = self.next_version();
kind_map.insert(key.to_string(), value.clone());
self.watchers.notify(WatchEvent::Added {
kind: kind.clone(),
key: key.to_string(),
value,
version,
});
info!(kind = %kind, key = key, version = version, "Resource created");
Ok(version)
}
pub fn get(&self, kind: &ResourceKind, key: &str) -> Option<serde_json::Value> {
self.resources.read()
.get(kind)
.and_then(|m| m.get(key).cloned())
}
pub fn update(&self, kind: ResourceKind, key: &str, value: serde_json::Value, expected_version: Option<u64>) -> Result<u64, StoreError> {
let mut resources = self.resources.write();
let kind_map = resources.entry(kind.clone()).or_insert_with(HashMap::new);
if !kind_map.contains_key(key) {
return Err(StoreError::NotFound(key.to_string()));
}
if let Some(expected) = expected_version {
let current = *self.version_counter.read();
if current != expected {
return Err(StoreError::Conflict(expected, current));
}
}
let version = self.next_version();
kind_map.insert(key.to_string(), value.clone());
self.watchers.notify(WatchEvent::Modified {
kind: kind.clone(),
key: key.to_string(),
value,
version,
});
Ok(version)
}
pub fn delete(&self, kind: &ResourceKind, key: &str) -> Result<(), StoreError> {
let mut resources = self.resources.write();
if let Some(kind_map) = resources.get_mut(kind) {
if let Some(value) = kind_map.remove(key) {
let version = self.next_version();
self.watchers.notify(WatchEvent::Deleted {
kind: kind.clone(),
key: key.to_string(),
value,
version,
});
info!(kind = %kind, key = key, "Resource deleted");
return Ok(());
}
}
Err(StoreError::NotFound(key.to_string()))
}
pub fn list(&self, kind: &ResourceKind, namespace: Option<&str>) -> Vec<serde_json::Value> {
self.resources.read()
.get(kind)
.map(|m| {
m.iter()
.filter(|(k, _)| {
namespace.map(|ns| k.starts_with(&format!("{}/", ns))).unwrap_or(true)
})
.map(|(_, v)| v.clone())
.collect()
})
.unwrap_or_default()
}
pub fn watch(&self, kind: ResourceKind) -> watch::WatchStream {
self.watchers.subscribe(kind)
}
pub fn current_version(&self) -> u64 {
*self.version_counter.read()
}
}
impl Default for ResourceStore {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, thiserror::Error)]
pub enum StoreError {
#[error("Resource already exists: {0}")]
AlreadyExists(String),
#[error("Resource not found: {0}")]
NotFound(String),
#[error("Version conflict: expected {0}, got {1}")]
Conflict(u64, u64),
#[error("Serialization error: {0}")]
Serialization(String),
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resource_store_crud() {
let store = ResourceStore::new();
let value = serde_json::json!({"name": "test"});
let version = store.create(ResourceKind::Workload, "default/test", value.clone()).unwrap();
assert!(version > 0);
let retrieved = store.get(&ResourceKind::Workload, "default/test").unwrap();
assert_eq!(retrieved, value);
let new_value = serde_json::json!({"name": "updated"});
store.update(ResourceKind::Workload, "default/test", new_value.clone(), None).unwrap();
let retrieved = store.get(&ResourceKind::Workload, "default/test").unwrap();
assert_eq!(retrieved, new_value);
store.delete(&ResourceKind::Workload, "default/test").unwrap();
assert!(store.get(&ResourceKind::Workload, "default/test").is_none());
}
#[test]
fn test_resource_store_conflict() {
let store = ResourceStore::new();
let value = serde_json::json!({"name": "test"});
store.create(ResourceKind::Workload, "default/test", value.clone()).unwrap();
let result = store.create(ResourceKind::Workload, "default/test", value);
assert!(matches!(result, Err(StoreError::AlreadyExists(_))));
}
}