diff --git a/examples/topology/src/main.rs b/examples/topology/src/main.rs index f3091c4..308cdd3 100644 --- a/examples/topology/src/main.rs +++ b/examples/topology/src/main.rs @@ -1,47 +1,31 @@ +use rand::Rng; use std::process::Command; -use rand::Rng; // Add rand dependency -// ===== Capability Traits ===== - -/// Base trait for all capabilities pub trait Capability {} -/// Capability for executing shell commands on a host pub trait CommandCapability: Capability { fn execute_command(&self, command: &str, args: &[&str]) -> Result; } -/// Capability for interacting with a Kubernetes cluster pub trait KubernetesCapability: Capability { fn apply_manifest(&self, manifest: &str) -> Result<(), String>; fn get_resource(&self, resource_type: &str, name: &str) -> Result; } -// ===== Topology Traits ===== - -/// Base trait for all topologies pub trait Topology { - // Base topology methods that don't depend on capabilities fn name(&self) -> &str; } -// ===== Score Traits ===== - -/// Generic Score trait with an associated Capability type pub trait Score { - fn apply(&self, topology: &T) -> Result<(), String>; + fn compile(&self) -> Result>, String>; fn name(&self) -> &str; } -// ===== Concrete Topologies ===== - -/// A topology representing a Linux host pub struct LinuxHostTopology { name: String, host: String, } -// Implement the base Capability trait for LinuxHostTopology impl Capability for LinuxHostTopology {} impl LinuxHostTopology { @@ -64,7 +48,7 @@ impl CommandCapability for LinuxHostTopology { .args(args) .output() .map_err(|e| e.to_string())?; - + if output.status.success() { Ok(String::from_utf8_lossy(&output.stdout).to_string()) } else { @@ -73,19 +57,17 @@ impl CommandCapability for LinuxHostTopology { } } -/// A topology representing a K3D Kubernetes cluster pub struct K3DTopology { name: String, linux_host: LinuxHostTopology, cluster_name: String, } -// Implement the base Capability trait for K3DTopology impl Capability for K3DTopology {} impl K3DTopology { pub fn new(name: String, linux_host: LinuxHostTopology, cluster_name: String) -> Self { - Self { + Self { name, linux_host, cluster_name, @@ -101,7 +83,6 @@ impl Topology for K3DTopology { impl CommandCapability for K3DTopology { fn execute_command(&self, command: &str, args: &[&str]) -> Result { - // Delegate to the underlying Linux host self.linux_host.execute_command(command, args) } } @@ -112,39 +93,40 @@ impl KubernetesCapability for K3DTopology { // Write manifest to a temporary file //let temp_file = format!("/tmp/manifest-{}.yaml", rand::thread_rng().gen::()); let temp_file = format!("/tmp/manifest-TODO_RANDOM_NUMBER.yaml"); - + // Use the linux_host directly to avoid capability trait bounds - self.linux_host.execute_command("bash", &["-c", &format!("cat > {}", temp_file)])?; - + self.linux_host + .execute_command("bash", &["-c", &format!("cat > {}", temp_file)])?; + // Apply with kubectl - self.linux_host.execute_command( - "kubectl", - &["--context", &format!("k3d-{}", self.cluster_name), "apply", "-f", &temp_file] - )?; - + self.linux_host.execute_command("kubectl", &[ + "--context", + &format!("k3d-{}", self.cluster_name), + "apply", + "-f", + &temp_file, + ])?; + Ok(()) } fn get_resource(&self, resource_type: &str, name: &str) -> Result { - println!("Getting resource {}/{} from K3D cluster '{}'", resource_type, name, self.cluster_name); - self.linux_host.execute_command( - "kubectl", - &[ - "--context", - &format!("k3d-{}", self.cluster_name), - "get", - resource_type, - name, - "-o", - "yaml", - ] - ) + println!( + "Getting resource {}/{} from K3D cluster '{}'", + resource_type, name, self.cluster_name + ); + self.linux_host.execute_command("kubectl", &[ + "--context", + &format!("k3d-{}", self.cluster_name), + "get", + resource_type, + name, + "-o", + "yaml", + ]) } } -// ===== Concrete Scores ===== - -/// A score that executes commands on a topology pub struct CommandScore { name: String, command: String, @@ -153,19 +135,35 @@ pub struct CommandScore { impl CommandScore { pub fn new(name: String, command: String, args: Vec) -> Self { - Self { name, command, args } + Self { + name, + command, + args, + } } } -impl Score for CommandScore -where - T: Topology + CommandCapability +pub trait Interpret { + fn execute(&self, topology: &T) -> Result; +} + +struct CommandInterpret; + +impl Interpret for CommandInterpret +where + T: Topology + CommandCapability, { - fn apply(&self, topology: &T) -> Result<(), String> { - println!("Applying CommandScore '{}' to topology '{}'", self.name, topology.name()); - let args_refs: Vec<&str> = self.args.iter().map(|s| s.as_str()).collect(); - topology.execute_command(&self.command, &args_refs)?; - Ok(()) + fn execute(&self, topology: &T) -> Result { + todo!() + } +} + +impl Score for CommandScore +where + T: Topology + CommandCapability, +{ + fn compile(&self) -> Result>, String> { + Ok(Box::new(CommandInterpret {})) } fn name(&self) -> &str { @@ -173,7 +171,8 @@ where } } -/// A score that applies Kubernetes resources to a topology + +#[derive(Clone)] pub struct K8sResourceScore { name: String, manifest: String, @@ -185,13 +184,24 @@ impl K8sResourceScore { } } -impl Score for K8sResourceScore -where - T: Topology + KubernetesCapability +struct K8sResourceInterpret { + score: K8sResourceScore, +} + +impl Interpret for K8sResourceInterpret { + fn execute(&self, topology: &T) -> Result { + todo!() + } +} + +impl Score for K8sResourceScore +where + T: Topology + KubernetesCapability, { - fn apply(&self, topology: &T) -> Result<(), String> { - println!("Applying K8sResourceScore '{}' to topology '{}'", self.name, topology.name()); - topology.apply_manifest(&self.manifest) + fn compile(&self) -> Result + 'static)>, String> { + Ok(Box::new(K8sResourceInterpret { + score: self.clone(), + })) } fn name(&self) -> &str { @@ -199,35 +209,11 @@ where } } -// ===== Maestro Orchestrator ===== - -/// Type-safe orchestrator that enforces capability requirements at compile time pub struct Maestro { topology: T, - scores: Vec>>, + scores: Vec>>, } -/// A trait object wrapper that hides the specific Score type but preserves its -/// capability requirements -trait ScoreWrapper { - fn apply(&self, topology: &T) -> Result<(), String>; - fn name(&self) -> &str; -} - -/// Implementation of ScoreWrapper for any Score that works with topology T -impl ScoreWrapper for S -where - T: Topology, - S: Score + 'static -{ - fn apply(&self, topology: &T) -> Result<(), String> { - >::apply(self, topology) - } - - fn name(&self) -> &str { - >::name(self) - } -} impl Maestro { pub fn new(topology: T) -> Self { @@ -237,70 +223,66 @@ impl Maestro { } } - /// Register a score that is compatible with this topology's capabilities - pub fn register_score(&mut self, score: S) - where - S: Score + 'static + pub fn register_score(&mut self, score: S) + where + S: Score + 'static, { - println!("Registering score '{}' for topology '{}'", score.name(), self.topology.name()); + println!( + "Registering score '{}' for topology '{}'", + score.name(), + self.topology.name() + ); self.scores.push(Box::new(score)); } - /// Apply all registered scores to the topology pub fn orchestrate(&self) -> Result<(), String> { println!("Orchestrating topology '{}'", self.topology.name()); for score in &self.scores { - score.apply(&self.topology)?; + let interpret = score.compile()?; + interpret.execute(&self.topology)?; } Ok(()) } } -// ===== Example Usage ===== - fn main() { - // Create a Linux host topology - let linux_host = LinuxHostTopology::new( - "dev-machine".to_string(), - "localhost".to_string() - ); - - // Create a maestro for the Linux host + let linux_host = LinuxHostTopology::new("dev-machine".to_string(), "localhost".to_string()); + let mut linux_maestro = Maestro::new(linux_host); - - // Register a command score that works with any topology having CommandCapability + linux_maestro.register_score(CommandScore::new( "check-disk".to_string(), "df".to_string(), - vec!["-h".to_string()] + vec!["-h".to_string()], )); - + linux_maestro.orchestrate().unwrap(); + // This would fail to compile if we tried to register a K8sResourceScore // because LinuxHostTopology doesn't implement KubernetesCapability - // linux_maestro.register_score(K8sResourceScore::new(...)); - + //linux_maestro.register_score(K8sResourceScore::new( + // "...".to_string(), + // "...".to_string(), + //)); + // Create a K3D topology which has both Command and Kubernetes capabilities - let k3d_host = LinuxHostTopology::new( - "k3d-host".to_string(), - "localhost".to_string() - ); - + let k3d_host = LinuxHostTopology::new("k3d-host".to_string(), "localhost".to_string()); + let k3d_topology = K3DTopology::new( "dev-cluster".to_string(), k3d_host, - "devcluster".to_string() + "devcluster".to_string(), ); - + // Create a maestro for the K3D topology let mut k3d_maestro = Maestro::new(k3d_topology); - + // We can register both command scores and kubernetes scores k3d_maestro.register_score(CommandScore::new( "check-nodes".to_string(), "kubectl".to_string(), - vec!["get".to_string(), "nodes".to_string()] + vec!["get".to_string(), "nodes".to_string()], )); - + k3d_maestro.register_score(K8sResourceScore::new( "deploy-nginx".to_string(), r#" @@ -323,9 +305,10 @@ fn main() { image: nginx:latest ports: - containerPort: 80 - "#.to_string() + "# + .to_string(), )); - + // Orchestrate both topologies linux_maestro.orchestrate().unwrap(); k3d_maestro.orchestrate().unwrap();