refactor(container): drop unused dependency_resolver module
DependencyResolver had zero call sites in prod or tests outside the module itself. The actual install-time dependency check lives in install.rs::detect_running_deps + check_install_deps; this DAG-walk solver was never wired up. -268 LoC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6603227874
commit
ba2eece9aa
@ -1,268 +0,0 @@
|
|||||||
use crate::manifest::{AppManifest, Dependency};
|
|
||||||
use indexmap::IndexMap;
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum DependencyError {
|
|
||||||
#[error("Circular dependency detected: {0}")]
|
|
||||||
CircularDependency(String),
|
|
||||||
#[error("Missing dependency: {0}")]
|
|
||||||
MissingDependency(String),
|
|
||||||
#[error("Version conflict: {0}")]
|
|
||||||
VersionConflict(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct DependencyResolver {
|
|
||||||
manifests: IndexMap<String, AppManifest>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DependencyResolver {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
manifests: IndexMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_manifest(&mut self, manifest: AppManifest) {
|
|
||||||
self.manifests.insert(manifest.app.id.clone(), manifest);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve_dependencies(&self, app_id: &str) -> Result<Vec<String>, DependencyError> {
|
|
||||||
let mut visited = HashSet::new();
|
|
||||||
let mut visiting = HashSet::new();
|
|
||||||
let mut result = Vec::new();
|
|
||||||
|
|
||||||
self.resolve_recursive(app_id, &mut visited, &mut visiting, &mut result)?;
|
|
||||||
|
|
||||||
// Result is already in installation order (dependencies first)
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_recursive(
|
|
||||||
&self,
|
|
||||||
app_id: &str,
|
|
||||||
visited: &mut HashSet<String>,
|
|
||||||
visiting: &mut HashSet<String>,
|
|
||||||
result: &mut Vec<String>,
|
|
||||||
) -> Result<(), DependencyError> {
|
|
||||||
if visited.contains(app_id) {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if visiting.contains(app_id) {
|
|
||||||
return Err(DependencyError::CircularDependency(format!(
|
|
||||||
"Circular dependency detected involving: {}",
|
|
||||||
app_id
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
|
|
||||||
visiting.insert(app_id.to_string());
|
|
||||||
|
|
||||||
let manifest = self.manifests.get(app_id).ok_or_else(|| {
|
|
||||||
DependencyError::MissingDependency(format!("App not found: {}", app_id))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Resolve all dependencies first
|
|
||||||
for dep in &manifest.app.dependencies {
|
|
||||||
match dep {
|
|
||||||
Dependency::App {
|
|
||||||
app_id: dep_id,
|
|
||||||
version: _,
|
|
||||||
} => {
|
|
||||||
self.resolve_recursive(dep_id, visited, visiting, result)?;
|
|
||||||
}
|
|
||||||
Dependency::Storage { storage: _ } => {
|
|
||||||
// Storage dependencies are checked but don't require other apps
|
|
||||||
}
|
|
||||||
Dependency::Simple(dep_id) => {
|
|
||||||
self.resolve_recursive(dep_id, visited, visiting, result)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
visiting.remove(app_id);
|
|
||||||
visited.insert(app_id.to_string());
|
|
||||||
|
|
||||||
if !result.contains(&app_id.to_string()) {
|
|
||||||
result.push(app_id.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn check_conflicts(&self, app_id: &str) -> Result<(), DependencyError> {
|
|
||||||
let manifest = self.manifests.get(app_id).ok_or_else(|| {
|
|
||||||
DependencyError::MissingDependency(format!("App not found: {}", app_id))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Check for port conflicts
|
|
||||||
let mut port_usage: HashMap<u16, String> = HashMap::new();
|
|
||||||
|
|
||||||
for (id, m) in &self.manifests {
|
|
||||||
if id == app_id {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for port in &m.app.ports {
|
|
||||||
if let Some(existing) = port_usage.get(&port.host) {
|
|
||||||
return Err(DependencyError::VersionConflict(format!(
|
|
||||||
"Port {} already used by {}",
|
|
||||||
port.host, existing
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
port_usage.insert(port.host, id.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for new app's ports
|
|
||||||
for port in &manifest.app.ports {
|
|
||||||
if let Some(existing) = port_usage.get(&port.host) {
|
|
||||||
return Err(DependencyError::VersionConflict(format!(
|
|
||||||
"Port {} already used by {}",
|
|
||||||
port.host, existing
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calculate_resources(&self, app_ids: &[String]) -> ResourceRequirements {
|
|
||||||
let mut total = ResourceRequirements {
|
|
||||||
cpu: 0,
|
|
||||||
memory_mb: 0,
|
|
||||||
disk_gb: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
for app_id in app_ids {
|
|
||||||
if let Some(manifest) = self.manifests.get(app_id) {
|
|
||||||
if let Some(cpu) = manifest.app.resources.cpu_limit {
|
|
||||||
total.cpu += cpu;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(memory) = &manifest.app.resources.memory_limit {
|
|
||||||
// Parse memory string (e.g., "1Gi", "512Mi")
|
|
||||||
if let Ok(mb) = parse_memory(memory) {
|
|
||||||
total.memory_mb += mb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(disk) = &manifest.app.resources.disk_limit {
|
|
||||||
// Parse disk string (e.g., "10Gi", "500Mi")
|
|
||||||
if let Ok(gb) = parse_disk(disk) {
|
|
||||||
total.disk_gb += gb;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_memory(s: &str) -> Result<u32, ()> {
|
|
||||||
let s = s.trim().to_lowercase();
|
|
||||||
if s.ends_with("gi") {
|
|
||||||
let num: f64 = s.trim_end_matches("gi").parse().map_err(|_| ())?;
|
|
||||||
Ok((num * 1024.0) as u32)
|
|
||||||
} else if s.ends_with("mi") {
|
|
||||||
let num: f64 = s.trim_end_matches("mi").parse().map_err(|_| ())?;
|
|
||||||
Ok(num as u32)
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_disk(s: &str) -> Result<u32, ()> {
|
|
||||||
let s = s.trim().to_lowercase();
|
|
||||||
if s.ends_with("gi") {
|
|
||||||
let num: f64 = s.trim_end_matches("gi").parse().map_err(|_| ())?;
|
|
||||||
Ok(num as u32)
|
|
||||||
} else if s.ends_with("ti") {
|
|
||||||
let num: f64 = s.trim_end_matches("ti").parse().map_err(|_| ())?;
|
|
||||||
Ok((num * 1024.0) as u32)
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ResourceRequirements {
|
|
||||||
pub cpu: u32,
|
|
||||||
pub memory_mb: u32,
|
|
||||||
pub disk_gb: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DependencyResolver {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::manifest::{AppDefinition, AppManifest, ContainerConfig};
|
|
||||||
|
|
||||||
fn create_test_manifest(id: &str, deps: Vec<Dependency>) -> AppManifest {
|
|
||||||
AppManifest {
|
|
||||||
app: AppDefinition {
|
|
||||||
id: id.to_string(),
|
|
||||||
name: format!("Test {}", id),
|
|
||||||
version: "1.0.0".to_string(),
|
|
||||||
description: None,
|
|
||||||
container: ContainerConfig {
|
|
||||||
image: Some(format!("test/{}:latest", id)),
|
|
||||||
image_signature: None,
|
|
||||||
pull_policy: "if-not-present".to_string(),
|
|
||||||
build: None,
|
|
||||||
network: None,
|
|
||||||
custom_args: vec![],
|
|
||||||
entrypoint: None,
|
|
||||||
derived_env: vec![],
|
|
||||||
secret_env: vec![],
|
|
||||||
data_uid: None,
|
|
||||||
},
|
|
||||||
dependencies: deps,
|
|
||||||
resources: Default::default(),
|
|
||||||
security: Default::default(),
|
|
||||||
ports: vec![],
|
|
||||||
volumes: vec![],
|
|
||||||
environment: vec![],
|
|
||||||
health_check: None,
|
|
||||||
devices: vec![],
|
|
||||||
extensions: Default::default(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_simple_dependency() {
|
|
||||||
let mut resolver = DependencyResolver::new();
|
|
||||||
resolver.add_manifest(create_test_manifest("app1", vec![]));
|
|
||||||
resolver.add_manifest(create_test_manifest(
|
|
||||||
"app2",
|
|
||||||
vec![Dependency::Simple("app1".to_string())],
|
|
||||||
));
|
|
||||||
|
|
||||||
let deps = resolver.resolve_dependencies("app2").unwrap();
|
|
||||||
assert_eq!(deps, vec!["app1", "app2"]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_circular_dependency() {
|
|
||||||
let mut resolver = DependencyResolver::new();
|
|
||||||
resolver.add_manifest(create_test_manifest(
|
|
||||||
"app1",
|
|
||||||
vec![Dependency::Simple("app2".to_string())],
|
|
||||||
));
|
|
||||||
resolver.add_manifest(create_test_manifest(
|
|
||||||
"app2",
|
|
||||||
vec![Dependency::Simple("app1".to_string())],
|
|
||||||
));
|
|
||||||
|
|
||||||
let result = resolver.resolve_dependencies("app1");
|
|
||||||
assert!(result.is_err());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
pub mod bitcoin_simulator;
|
pub mod bitcoin_simulator;
|
||||||
pub mod dependency_resolver;
|
|
||||||
pub mod health_monitor;
|
pub mod health_monitor;
|
||||||
pub mod manifest;
|
pub mod manifest;
|
||||||
pub mod podman_client;
|
pub mod podman_client;
|
||||||
@ -7,7 +6,6 @@ pub mod port_manager;
|
|||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
|
||||||
pub use bitcoin_simulator::{BitcoinSimulationMode, BitcoinSimulator};
|
pub use bitcoin_simulator::{BitcoinSimulationMode, BitcoinSimulator};
|
||||||
pub use dependency_resolver::DependencyResolver;
|
|
||||||
pub use health_monitor::HealthMonitor;
|
pub use health_monitor::HealthMonitor;
|
||||||
pub use manifest::{
|
pub use manifest::{
|
||||||
AppManifest, BuildConfig, ContainerConfig, Dependency, DerivedEnv, HealthCheck, HostFacts,
|
AppManifest, BuildConfig, ContainerConfig, Dependency, DerivedEnv, HealthCheck, HostFacts,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user