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:
@@ -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 dependency_resolver;
|
||||
pub mod health_monitor;
|
||||
pub mod manifest;
|
||||
pub mod podman_client;
|
||||
@@ -7,7 +6,6 @@ pub mod port_manager;
|
||||
pub mod runtime;
|
||||
|
||||
pub use bitcoin_simulator::{BitcoinSimulationMode, BitcoinSimulator};
|
||||
pub use dependency_resolver::DependencyResolver;
|
||||
pub use health_monitor::HealthMonitor;
|
||||
pub use manifest::{
|
||||
AppManifest, BuildConfig, ContainerConfig, Dependency, DerivedEnv, HealthCheck, HostFacts,
|
||||
|
||||
Reference in New Issue
Block a user