216 lines
5.9 KiB
Rust
216 lines
5.9 KiB
Rust
|
|
//! Core extension registry service implementation
|
||
|
|
|
||
|
|
use std::collections::HashMap;
|
||
|
|
use std::net::SocketAddr;
|
||
|
|
use std::sync::Arc;
|
||
|
|
|
||
|
|
use anyhow::Result;
|
||
|
|
use serde::{Deserialize, Serialize};
|
||
|
|
use sha2::Digest;
|
||
|
|
use tokio::sync::RwLock;
|
||
|
|
|
||
|
|
/// OCI image manifest
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct ImageManifest {
|
||
|
|
/// Schema version
|
||
|
|
pub schema_version: u32,
|
||
|
|
/// Media type
|
||
|
|
pub media_type: String,
|
||
|
|
/// Image config digest (sha256:...)
|
||
|
|
pub config: ManifestConfig,
|
||
|
|
/// Image layers
|
||
|
|
pub layers: Vec<Layer>,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Image configuration reference
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct ManifestConfig {
|
||
|
|
/// Digest (sha256:...)
|
||
|
|
pub digest: String,
|
||
|
|
/// Size in bytes
|
||
|
|
pub size: u64,
|
||
|
|
/// Media type
|
||
|
|
pub media_type: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Image layer
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct Layer {
|
||
|
|
/// Layer digest (sha256:...)
|
||
|
|
pub digest: String,
|
||
|
|
/// Size in bytes
|
||
|
|
pub size: u64,
|
||
|
|
/// Media type
|
||
|
|
pub media_type: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Push blob request
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct PushBlobRequest {
|
||
|
|
/// Blob content
|
||
|
|
pub content: Vec<u8>,
|
||
|
|
/// Content digest for validation
|
||
|
|
pub digest: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Push blob response
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct PushBlobResponse {
|
||
|
|
/// Blob digest
|
||
|
|
pub digest: String,
|
||
|
|
/// Blob size in bytes
|
||
|
|
pub size: u64,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Pull manifest response
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct PullManifestResponse {
|
||
|
|
/// Image manifest
|
||
|
|
pub manifest: ImageManifest,
|
||
|
|
/// Manifest digest
|
||
|
|
pub digest: String,
|
||
|
|
/// Content type
|
||
|
|
pub content_type: String,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Extension metadata
|
||
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
|
|
pub struct ExtensionMetadata {
|
||
|
|
/// Extension name
|
||
|
|
pub name: String,
|
||
|
|
/// Extension version
|
||
|
|
pub version: String,
|
||
|
|
/// Extension description
|
||
|
|
pub description: String,
|
||
|
|
/// Supported platforms (e.g., ["linux/amd64", "linux/arm64"])
|
||
|
|
pub platforms: Vec<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Core extension registry service
|
||
|
|
pub struct ExtensionRegistry {
|
||
|
|
/// Server address
|
||
|
|
addr: SocketAddr,
|
||
|
|
/// Stored blobs (digest -> content)
|
||
|
|
blobs: Arc<RwLock<HashMap<String, Vec<u8>>>>,
|
||
|
|
/// Stored manifests (repo:tag -> manifest)
|
||
|
|
manifests: Arc<RwLock<HashMap<String, ImageManifest>>>,
|
||
|
|
/// Extension metadata registry
|
||
|
|
extensions: Arc<RwLock<HashMap<String, ExtensionMetadata>>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl ExtensionRegistry {
|
||
|
|
/// Create new extension registry
|
||
|
|
pub fn new(addr: SocketAddr) -> Self {
|
||
|
|
Self {
|
||
|
|
addr,
|
||
|
|
blobs: Arc::new(RwLock::new(HashMap::new())),
|
||
|
|
manifests: Arc::new(RwLock::new(HashMap::new())),
|
||
|
|
extensions: Arc::new(RwLock::new(HashMap::new())),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get service address
|
||
|
|
pub fn addr(&self) -> SocketAddr {
|
||
|
|
self.addr
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if blob exists
|
||
|
|
pub async fn blob_exists(&self, digest: &str) -> Result<bool> {
|
||
|
|
let blobs = self.blobs.read().await;
|
||
|
|
Ok(blobs.contains_key(digest))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Push blob to registry
|
||
|
|
pub async fn push_blob(&self, req: PushBlobRequest) -> Result<PushBlobResponse> {
|
||
|
|
let size = req.content.len() as u64;
|
||
|
|
let mut blobs = self.blobs.write().await;
|
||
|
|
blobs.insert(req.digest.clone(), req.content);
|
||
|
|
|
||
|
|
Ok(PushBlobResponse {
|
||
|
|
digest: req.digest,
|
||
|
|
size,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Pull blob from registry
|
||
|
|
pub async fn pull_blob(&self, digest: &str) -> Result<Vec<u8>> {
|
||
|
|
let blobs = self.blobs.read().await;
|
||
|
|
blobs
|
||
|
|
.get(digest)
|
||
|
|
.cloned()
|
||
|
|
.ok_or_else(|| anyhow::anyhow!("Blob not found: {}", digest))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Check if manifest exists
|
||
|
|
pub async fn manifest_exists(&self, reference: &str) -> Result<bool> {
|
||
|
|
let manifests = self.manifests.read().await;
|
||
|
|
Ok(manifests.contains_key(reference))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Put image manifest
|
||
|
|
pub async fn put_manifest(&self, reference: String, manifest: ImageManifest) -> Result<String> {
|
||
|
|
let digest = format!(
|
||
|
|
"sha256:{}",
|
||
|
|
hex::encode(sha2::Sha256::digest(&serde_json::to_vec(&manifest)?))
|
||
|
|
);
|
||
|
|
|
||
|
|
let mut manifests = self.manifests.write().await;
|
||
|
|
manifests.insert(reference, manifest);
|
||
|
|
|
||
|
|
Ok(digest)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get image manifest
|
||
|
|
pub async fn get_manifest(&self, reference: &str) -> Result<PullManifestResponse> {
|
||
|
|
let manifests = self.manifests.read().await;
|
||
|
|
let manifest = manifests
|
||
|
|
.get(reference)
|
||
|
|
.cloned()
|
||
|
|
.ok_or_else(|| anyhow::anyhow!("Manifest not found: {}", reference))?;
|
||
|
|
|
||
|
|
let digest = format!(
|
||
|
|
"sha256:{}",
|
||
|
|
hex::encode(sha2::Sha256::digest(&serde_json::to_vec(&manifest)?))
|
||
|
|
);
|
||
|
|
|
||
|
|
Ok(PullManifestResponse {
|
||
|
|
manifest,
|
||
|
|
digest,
|
||
|
|
content_type: "application/vnd.docker.distribution.manifest.v2+json".to_string(),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Register extension
|
||
|
|
pub async fn register_extension(&self, metadata: ExtensionMetadata) -> Result<()> {
|
||
|
|
let mut extensions = self.extensions.write().await;
|
||
|
|
extensions.insert(metadata.name.clone(), metadata);
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Get extension metadata
|
||
|
|
pub async fn get_extension(&self, name: &str) -> Result<ExtensionMetadata> {
|
||
|
|
let extensions = self.extensions.read().await;
|
||
|
|
extensions
|
||
|
|
.get(name)
|
||
|
|
.cloned()
|
||
|
|
.ok_or_else(|| anyhow::anyhow!("Extension not found: {}", name))
|
||
|
|
}
|
||
|
|
|
||
|
|
/// List all extensions
|
||
|
|
pub async fn list_extensions(&self) -> Result<Vec<ExtensionMetadata>> {
|
||
|
|
let extensions = self.extensions.read().await;
|
||
|
|
Ok(extensions.values().cloned().collect())
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Health check endpoint
|
||
|
|
pub async fn health_check(&self) -> Result<()> {
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Default for ExtensionRegistry {
|
||
|
|
fn default() -> Self {
|
||
|
|
Self::new("127.0.0.1:8084".parse().unwrap())
|
||
|
|
}
|
||
|
|
}
|