use std::any::Any; use std::fmt::Debug; use std::process::Command; pub trait Capability {} pub trait CommandCapability: Capability { fn execute_command(&self, command: &str, args: &Vec) -> Result; } pub trait KubernetesCapability: Capability { fn apply_manifest(&self, manifest: &str) -> Result<(), String>; fn get_resource(&self, resource_type: &str, name: &str) -> Result; } pub trait Topology { fn name(&self) -> &str; } pub trait Interpret { fn execute(&self, topology: &T) -> Result; } // --- Score Definition Structs (Concrete) --- // CommandScore struct remains the same #[derive(Debug, Clone)] // Added Debug/Clone for easier handling pub struct CommandScore { name: String, command: String, args: Vec, } impl CommandScore { pub fn new(name: String, command: String, args: Vec) -> Self { Self { name, command, args } } } // K8sResourceScore struct remains the same #[derive(Debug, Clone)] pub struct K8sResourceScore { name: String, manifest: String, } impl K8sResourceScore { pub fn new(name: String, manifest: String) -> Self { Self { name, manifest } } } // --- Metadata / Base Score Trait (Non-Generic) --- // Trait for common info and enabling downcasting later if needed pub trait ScoreDefinition: Debug + Send + Sync { fn name(&self) -> &str; // Method to allow downcasting fn as_any(&self) -> &dyn Any; // Optional: Could add methods for description, parameters etc. // fn description(&self) -> &str; // Optional but potentially useful: A way to clone the definition fn box_clone(&self) -> Box; } // Implement Clone for Box impl Clone for Box { fn clone(&self) -> Self { self.box_clone() } } // Implement ScoreDefinition for your concrete score types impl ScoreDefinition for CommandScore { fn name(&self) -> &str { &self.name } fn as_any(&self) -> &dyn Any { self } fn box_clone(&self) -> Box { Box::new(self.clone()) } } impl ScoreDefinition for K8sResourceScore { fn name(&self) -> &str { &self.name } fn as_any(&self) -> &dyn Any { self } fn box_clone(&self) -> Box { Box::new(self.clone()) } } // --- Score Compatibility Trait (Generic over T) --- // This remains largely the same, ensuring compile-time checks pub trait Score: ScoreDefinition { // No need for name() here, it's in ScoreDefinition fn compile(&self) -> Result>, String>; } // --- Implementations of Score (Crucial Link) --- // CommandScore implements Score for any T with CommandCapability impl Score for CommandScore where T: Topology + CommandCapability + 'static, // Added 'static bound often needed for Box // Self: ScoreDefinition // This bound is implicit now { fn compile(&self) -> Result>, String> { // Pass necessary data from self to CommandInterpret Ok(Box::new(CommandInterpret { command: self.command.clone(), args: self.args.clone(), })) } } // K8sResourceScore implements Score for any T with KubernetesCapability impl Score for K8sResourceScore where T: Topology + KubernetesCapability + 'static, // Self: ScoreDefinition { fn compile(&self) -> Result>, String> { Ok(Box::new(K8sResourceInterpret { manifest: self.manifest.clone(), // Pass needed data })) } } // --- Interpret Implementations --- // Need to hold the actual data now struct CommandInterpret { command: String, args: Vec, // Or owned Strings if lifetime is tricky } impl<'a, T> Interpret for CommandInterpret where T: Topology + CommandCapability, { fn execute(&self, topology: &T) -> Result { // Now uses data stored in self topology.execute_command(&self.command, &self.args) } } struct K8sResourceInterpret { manifest: String, } impl Interpret for K8sResourceInterpret { fn execute(&self, topology: &T) -> Result { topology.apply_manifest(&self.manifest)?; // apply_manifest returns Result<(), String>, adapt if needed Ok(format!("Applied manifest for {}", topology.name())) // Example success message } } // --- Maestro --- // Maestro remains almost identical, leveraging the Score bound pub struct Maestro { topology: T, // Stores Score trait objects, ensuring compatibility scores: Vec>>, } impl Maestro { // Often need T: 'static here pub fn new(topology: T) -> Self { Self { topology, scores: Vec::new(), } } // This method signature is key - it takes a concrete S // and the compiler checks if S implements Score pub fn register_score(&mut self, score: S) -> Result<(), String> where S: Score + ScoreDefinition + Clone + 'static, // Ensure S is a Score for *this* T // We might need S: Clone if we want to store Box::new(score) // Alternatively, accept Box and try to downcast/wrap { println!( "Registering score '{}' for topology '{}'", score.name(), self.topology.name() ); // The compiler has already guaranteed that S implements Score // We need to box it as dyn Score self.scores.push(Box::new(score)); Ok(()) } // Alternative registration if you have Box pub fn register_score_definition(&mut self, score_def: Box) -> Result<(), String> where T: Topology + CommandCapability + KubernetesCapability + 'static, // Example: list all needed caps here, or use generics + downcasting { println!( "Attempting to register score '{}' for topology '{}'", score_def.name(), self.topology.name() ); // Downcast to check concrete type and then check compatibility if let Some(cs) = score_def.as_any().downcast_ref::() { // Check if T satisfies CommandScore's requirements (CommandCapability) // This check is somewhat manual or needs restructuring if we avoid listing all caps // A simpler way is to just try to create the Box> let boxed_score: Box> = Box::new(cs.clone()); // This relies on the blanket impls self.scores.push(boxed_score); Ok(()) } else if let Some(ks) = score_def.as_any().downcast_ref::() { // Check if T satisfies K8sResourceScore's requirements (KubernetesCapability) let boxed_score: Box> = Box::new(ks.clone()); self.scores.push(boxed_score); Ok(()) } else { Err(format!("Score '{}' is of an unknown type or incompatible", score_def.name())) } // This downcasting approach in Maestro slightly undermines the full compile-time // check unless designed carefully. The generic `register_score>` is safer. } pub fn orchestrate(&self) -> Result<(), String> { println!("Orchestrating topology '{}'", self.topology.name()); for score in &self.scores { println!("Compiling score '{}'", score.name()); // Use name() from ScoreDefinition let interpret = score.compile()?; println!("Executing score '{}'", score.name()); interpret.execute(&self.topology)?; } Ok(()) } } // --- TUI Example --- struct ScoreItem { // Holds the definition/metadata, NOT the Score trait object definition: Box, } struct HarmonyTui { // List of available score *definitions* available_scores: Vec, // Example: Maybe maps topology names to Maestros // maestros: HashMap>, // Storing Maestros generically is another challenge! } impl HarmonyTui { fn new() -> Self { HarmonyTui { available_scores: vec![] } } fn add_available_score(&mut self, score_def: Box) { self.available_scores.push(ScoreItem { definition: score_def }); } fn display_scores(&self) { println!("Available Scores:"); for (i, item) in self.available_scores.iter().enumerate() { println!("{}: {}", i, item.definition.name()); } } fn execute_score(&self, score: ScoreItem) { score.definition. } // Example: Function to add a selected score to a specific Maestro // This function would need access to the Maestros and handle the types fn add_selected_score_to_maestro( &self, score_index: usize, maestro: &mut Maestro ) -> Result<(), String> where T: Topology + CommandCapability + KubernetesCapability + 'static, // Adjust bounds as needed { let score_item = self.available_scores.get(score_index) .ok_or("Invalid score index")?; // We have Box, need to add to Maestro // Easiest is to downcast and call the generic register_score if let Some(cs) = score_item.definition.as_any().downcast_ref::() { // Compiler checks if CommandScore: Score via register_score's bound maestro.register_score(cs.clone())?; Ok(()) } else if let Some(ks) = score_item.definition.as_any().downcast_ref::() { // Compiler checks if K8sResourceScore: Score via register_score's bound maestro.register_score(ks.clone())?; Ok(()) } else { Err(format!("Cannot add score '{}': Unknown type or check Maestro compatibility", score_item.definition.name())) } } } pub struct K3DTopology { name: String, linux_host: LinuxHostTopology, cluster_name: String, } impl Capability for K3DTopology {} impl K3DTopology { pub fn new(name: String, linux_host: LinuxHostTopology, cluster_name: String) -> Self { Self { name, linux_host, cluster_name, } } } impl Topology for K3DTopology { fn name(&self) -> &str { &self.name } } impl CommandCapability for K3DTopology { fn execute_command(&self, command: &str, args: &Vec) -> Result { self.linux_host.execute_command(command, args) } } impl KubernetesCapability for K3DTopology { fn apply_manifest(&self, manifest: &str) -> Result<(), String> { println!("Applying manifest to K3D cluster '{}'", self.cluster_name); // Write manifest to a temporary file let temp_file = format!("/tmp/manifest-harmony-temp.yaml"); // Use the linux_host directly to avoid capability trait bounds self.linux_host .execute_command("bash", &Vec::from(["-c".to_string(), format!("cat > {}", temp_file)]))?; // Apply with kubectl self.linux_host.execute_command("kubectl", &Vec::from([ "--context".to_string(), format!("k3d-{}", self.cluster_name), "apply".to_string(), "-f".to_string(), temp_file.to_string(), ]))?; 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", &Vec::from([ "--context".to_string(), format!("k3d-{}", self.cluster_name), "get".to_string(), resource_type.to_string(), name.to_string(), "-o".to_string(), "yaml".to_string(), ])) } } pub struct LinuxHostTopology { name: String, host: String, } impl Capability for LinuxHostTopology {} impl LinuxHostTopology { pub fn new(name: String, host: String) -> Self { Self { name, host } } } impl Topology for LinuxHostTopology { fn name(&self) -> &str { &self.name } } impl CommandCapability for LinuxHostTopology { fn execute_command(&self, command: &str, args: &Vec) -> Result { println!("Executing on {}: {} {:?}", self.host, command, args); // In a real implementation, this would SSH to the host and execute the command let output = Command::new(command) .args(args) .output() .map_err(|e| e.to_string())?; if output.status.success() { Ok(String::from_utf8_lossy(&output.stdout).to_string()) } else { Err(String::from_utf8_lossy(&output.stderr).to_string()) } } } // --- Main Function Adapated --- fn main() { // --- Linux Host --- let linux_host = LinuxHostTopology::new("dev-machine".to_string(), "localhost".to_string()); let mut linux_maestro = Maestro::new(linux_host); let df_score = CommandScore::new( "check-disk".to_string(), "df".to_string(), vec!["-h".to_string()], ); // Registration uses the generic method, compiler checks CommandScore: Score linux_maestro.register_score(df_score.clone()).unwrap(); // clone needed if df_score used later // --- K3D Host --- 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(), ); let mut k3d_maestro = Maestro::new(k3d_topology); let nodes_score = CommandScore::new( "check-nodes".to_string(), "kubectl".to_string(), vec!["get".to_string(), "nodes".to_string()], ); let nginx_score = K8sResourceScore::new( "deploy-nginx".to_string(), // ... manifest string ... r#"..."#.to_string(), ); // Compiler checks CommandScore: Score k3d_maestro.register_score(nodes_score.clone()).unwrap(); // Compiler checks K8sResourceScore: Score k3d_maestro.register_score(nginx_score.clone()).unwrap(); // --- TUI Example Usage --- let mut tui = HarmonyTui::new(); // Add score *definitions* to the TUI tui.add_available_score(Box::new(df_score)); tui.add_available_score(Box::new(nodes_score)); tui.add_available_score(Box::new(nginx_score)); tui.display_scores(); // Simulate user selecting score 0 (check-disk) and adding to linux_maestro match tui.add_selected_score_to_maestro(0, &mut linux_maestro) { Ok(_) => println!("Successfully registered check-disk to linux_maestro via TUI selection"), Err(e) => println!("Failed: {}", e), // Should succeed } // Simulate user selecting score 2 (deploy-nginx) and adding to linux_maestro match tui.add_selected_score_to_maestro(2, &mut linux_maestro) { Ok(_) => println!("Successfully registered deploy-nginx to linux_maestro via TUI selection"), // Should fail! Err(e) => println!("Correctly failed to add deploy-nginx to linux_maestro: {}", e), // The failure happens inside add_selected_score_to_maestro because the // maestro.register_score(ks.clone()) call fails the trait bound check // K8sResourceScore: Score is false. } // Simulate user selecting score 2 (deploy-nginx) and adding to k3d_maestro match tui.add_selected_score_to_maestro(2, &mut k3d_maestro) { Ok(_) => println!("Successfully registered deploy-nginx to k3d_maestro via TUI selection"), // Should succeed Err(e) => println!("Failed: {}", e), } // --- Orchestration --- println!("\n--- Orchestrating Linux Maestro ---"); linux_maestro.orchestrate().unwrap(); println!("\n--- Orchestrating K3D Maestro ---"); k3d_maestro.orchestrate().unwrap(); }