Add more Topology samples with various architectures
This commit is contained in:
		
							parent
							
								
									fda007f014
								
							
						
					
					
						commit
						6e9bf3a4be
					
				| @ -1,315 +1,232 @@ | ||||
| use rand::Rng; | ||||
| use std::process::Command; | ||||
| // Basic traits from your example
 | ||||
| trait Topology {} | ||||
| 
 | ||||
| pub trait Capability {} | ||||
| 
 | ||||
| pub trait CommandCapability: Capability { | ||||
|     fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String>; | ||||
| trait Score: Clone + std::fmt::Debug { | ||||
|     fn get_interpret<T: Topology>(&self) -> Box<dyn Interpret<T>>; | ||||
|     fn name(&self) -> String; | ||||
| } | ||||
| 
 | ||||
| pub trait KubernetesCapability: Capability { | ||||
|     fn apply_manifest(&self, manifest: &str) -> Result<(), String>; | ||||
|     fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String>; | ||||
| trait Interpret<T: Topology> { | ||||
|     fn execute(&self); | ||||
| } | ||||
| 
 | ||||
| pub trait Topology { | ||||
|     fn name(&self) -> &str; | ||||
| struct Maestro<T: Topology> { | ||||
|     topology: T | ||||
| } | ||||
| 
 | ||||
| pub trait Score<T: Topology> { | ||||
|     fn compile(&self) -> Result<Box<dyn Interpret<T>>, String>; | ||||
|     fn name(&self) -> &str; | ||||
| } | ||||
| 
 | ||||
| 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<T: Topology> Maestro<T> { | ||||
|     pub fn new(topology: T) -> Self { | ||||
|         Maestro { topology } | ||||
|     } | ||||
|     
 | ||||
|     pub fn register_score<S: Score + 'static>(&self, score: S) { | ||||
|         println!("Registering score: {}", score.name()); | ||||
|     } | ||||
|     
 | ||||
|     pub fn execute_score<S: Score + 'static>(&self, score: S) { | ||||
|         println!("Executing score: {}", score.name()); | ||||
|         score.get_interpret::<T>().execute(); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Topology for LinuxHostTopology { | ||||
|     fn name(&self) -> &str { | ||||
|         &self.name | ||||
| // Capability traits - these are used to enforce requirements
 | ||||
| trait CommandExecution { | ||||
|     fn execute_command(&self, command: &[String]) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| trait FileSystem { | ||||
|     fn read_file(&self, path: &str) -> Result<String, String>; | ||||
|     fn write_file(&self, path: &str, content: &str) -> Result<(), String>; | ||||
| } | ||||
| 
 | ||||
| // A concrete topology implementation
 | ||||
| #[derive(Clone, Debug)] | ||||
| struct LinuxHostTopology { | ||||
|     hostname: String, | ||||
| } | ||||
| 
 | ||||
| impl Topology for LinuxHostTopology {} | ||||
| 
 | ||||
| // Implement the capabilities for LinuxHostTopology
 | ||||
| impl CommandExecution for LinuxHostTopology { | ||||
|     fn execute_command(&self, command: &[String]) -> Result<String, String> { | ||||
|         println!("Executing command on {}: {:?}", self.hostname, command); | ||||
|         // In a real implementation, this would use std::process::Command
 | ||||
|         Ok(format!("Command executed successfully on {}", self.hostname)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl CommandCapability for LinuxHostTopology { | ||||
|     fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String> { | ||||
|         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()) | ||||
|         } | ||||
| impl FileSystem for LinuxHostTopology { | ||||
|     fn read_file(&self, path: &str) -> Result<String, String> { | ||||
|         println!("Reading file {} on {}", path, self.hostname); | ||||
|         Ok(format!("Content of {} on {}", path, self.hostname)) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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: &[&str]) -> Result<String, String> { | ||||
|         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-{}.yaml", rand::thread_rng().gen::<u32>());
 | ||||
|         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)])?; | ||||
| 
 | ||||
|         // Apply with kubectl
 | ||||
|         self.linux_host.execute_command("kubectl", &[ | ||||
|             "--context", | ||||
|             &format!("k3d-{}", self.cluster_name), | ||||
|             "apply", | ||||
|             "-f", | ||||
|             &temp_file, | ||||
|         ])?; | ||||
| 
 | ||||
|     fn write_file(&self, path: &str, content: &str) -> Result<(), String> { | ||||
|         println!("Writing to file {} on {}: {}", path, self.hostname, content); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String> { | ||||
|         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", | ||||
|         ]) | ||||
| // Another topology that doesn't support command execution
 | ||||
| #[derive(Clone, Debug)] | ||||
| struct BareMetalTopology { | ||||
|     device_id: String, | ||||
| } | ||||
| 
 | ||||
| impl Topology for BareMetalTopology {} | ||||
| 
 | ||||
| impl FileSystem for BareMetalTopology { | ||||
|     fn read_file(&self, path: &str) -> Result<String, String> { | ||||
|         println!("Reading file {} on device {}", path, self.device_id); | ||||
|         Ok(format!("Content of {} on device {}", path, self.device_id)) | ||||
|     } | ||||
| 
 | ||||
|     fn write_file(&self, path: &str, content: &str) -> Result<(), String> { | ||||
|         println!("Writing to file {} on device {}: {}", path, self.device_id, content); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct CommandScore { | ||||
| // CommandScore implementation
 | ||||
| #[derive(Clone, Debug)] | ||||
| struct CommandScore { | ||||
|     name: String, | ||||
|     command: String, | ||||
|     args: Vec<String>, | ||||
| } | ||||
| 
 | ||||
| impl CommandScore { | ||||
|     pub fn new(name: String, command: String, args: Vec<String>) -> Self { | ||||
|         Self { | ||||
|             name, | ||||
|             command, | ||||
|             args, | ||||
|     pub fn new(name: String, args: Vec<String>) -> Self { | ||||
|         CommandScore { name, args } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Score for CommandScore { | ||||
|     fn get_interpret<T: Topology + CommandExecution + 'static>(&self) -> Box<dyn Interpret<T>> { | ||||
|         // This is the key part: we constrain T to implement CommandExecution
 | ||||
|         // If T doesn't implement CommandExecution, this will fail to compile
 | ||||
|         Box::new(CommandInterpret::<T>::new(self.clone())) | ||||
|     } | ||||
|     
 | ||||
|     fn name(&self) -> String { | ||||
|         self.name.clone() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // CommandInterpret implementation
 | ||||
| struct CommandInterpret<T: Topology + CommandExecution> { | ||||
|     score: CommandScore, | ||||
|     _marker: std::marker::PhantomData<T>, | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology + CommandExecution> CommandInterpret<T> { | ||||
|     pub fn new(score: CommandScore) -> Self { | ||||
|         CommandInterpret { | ||||
|             score, | ||||
|             _marker: std::marker::PhantomData, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait Interpret<T: Topology> { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| struct CommandInterpret; | ||||
| 
 | ||||
| impl<T> Interpret<T> for CommandInterpret | ||||
| where | ||||
|     T: Topology + CommandCapability, | ||||
| { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String> { | ||||
|         todo!() | ||||
| impl<T: Topology + CommandExecution> Interpret<T> for CommandInterpret<T> { | ||||
|     fn execute(&self) { | ||||
|         println!("Command interpret is executing: {:?}", self.score.args); | ||||
|         // In a real implementation, you would call the topology's execute_command method
 | ||||
|         // topology.execute_command(&self.score.args);
 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T> Score<T> for CommandScore | ||||
| where | ||||
|     T: Topology + CommandCapability, | ||||
| { | ||||
|     fn compile(&self) -> Result<Box<dyn Interpret<T>>, String> { | ||||
|         Ok(Box::new(CommandInterpret {})) | ||||
|     } | ||||
| 
 | ||||
|     fn name(&self) -> &str { | ||||
|         &self.name | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct K8sResourceScore { | ||||
| // FileScore implementation - a different type of score that requires FileSystem capability
 | ||||
| #[derive(Clone, Debug)] | ||||
| struct FileScore { | ||||
|     name: String, | ||||
|     manifest: String, | ||||
|     path: String, | ||||
|     content: Option<String>, | ||||
| } | ||||
| 
 | ||||
| impl K8sResourceScore { | ||||
|     pub fn new(name: String, manifest: String) -> Self { | ||||
|         Self { name, manifest } | ||||
| impl FileScore { | ||||
|     pub fn new_read(name: String, path: String) -> Self { | ||||
|         FileScore { name, path, content: None } | ||||
|     } | ||||
|     
 | ||||
|     pub fn new_write(name: String, path: String, content: String) -> Self { | ||||
|         FileScore { name, path, content: Some(content) } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct K8sResourceInterpret { | ||||
|     score: K8sResourceScore, | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology + KubernetesCapability> Interpret<T> for K8sResourceInterpret { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String> { | ||||
|         todo!() | ||||
| impl Score for FileScore { | ||||
|     fn get_interpret<T: Topology>(&self) -> Box<dyn Interpret<T>> { | ||||
|         // This constrains T to implement FileSystem
 | ||||
|         Box::new(FileInterpret::<T>::new(self.clone())) | ||||
|     } | ||||
|     
 | ||||
|     fn name(&self) -> String { | ||||
|         self.name.clone() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T> Score<T> for K8sResourceScore | ||||
| where | ||||
|     T: Topology + KubernetesCapability, | ||||
| { | ||||
|     fn compile(&self) -> Result<Box<(dyn Interpret<T> + 'static)>, String> { | ||||
|         Ok(Box::new(K8sResourceInterpret { | ||||
|             score: self.clone(), | ||||
|         })) | ||||
|     } | ||||
| 
 | ||||
|     fn name(&self) -> &str { | ||||
|         &self.name | ||||
|     } | ||||
| // FileInterpret implementation
 | ||||
| struct FileInterpret<T: Topology + FileSystem> { | ||||
|     score: FileScore, | ||||
|     _marker: std::marker::PhantomData<T>, | ||||
| } | ||||
| 
 | ||||
| pub struct Maestro<T: Topology> { | ||||
|     topology: T, | ||||
|     scores: Vec<Box<dyn Score<T>>>, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| impl<T: Topology> Maestro<T> { | ||||
|     pub fn new(topology: T) -> Self { | ||||
|         Self { | ||||
|             topology, | ||||
|             scores: Vec::new(), | ||||
| impl<T: Topology + FileSystem> FileInterpret<T> { | ||||
|     pub fn new(score: FileScore) -> Self { | ||||
|         FileInterpret { | ||||
|             score, | ||||
|             _marker: std::marker::PhantomData, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|     pub fn register_score<S>(&mut self, score: S) | ||||
|     where | ||||
|         S: Score<T> + 'static, | ||||
|     { | ||||
|         println!( | ||||
|             "Registering score '{}' for topology '{}'", | ||||
|             score.name(), | ||||
|             self.topology.name() | ||||
|         ); | ||||
|         self.scores.push(Box::new(score)); | ||||
|     } | ||||
| 
 | ||||
|     pub fn orchestrate(&self) -> Result<(), String> { | ||||
|         println!("Orchestrating topology '{}'", self.topology.name()); | ||||
|         for score in &self.scores { | ||||
|             let interpret = score.compile()?; | ||||
|             interpret.execute(&self.topology)?; | ||||
| impl<T: Topology + FileSystem> Interpret<T> for FileInterpret<T> { | ||||
|     fn execute(&self) { | ||||
|         match &self.score.content { | ||||
|             Some(content) => { | ||||
|                 println!("File interpret is writing to {}: {}", self.score.path, content); | ||||
|                 // In a real implementation: topology.write_file(&self.score.path, content);
 | ||||
|             }, | ||||
|             None => { | ||||
|                 println!("File interpret is reading from {}", self.score.path); | ||||
|                 // In a real implementation: let content = topology.read_file(&self.score.path);
 | ||||
|             } | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let linux_host = LinuxHostTopology::new("dev-machine".to_string(), "localhost".to_string()); | ||||
| 
 | ||||
|     let mut linux_maestro = Maestro::new(linux_host); | ||||
| 
 | ||||
|     linux_maestro.register_score(CommandScore::new( | ||||
|         "check-disk".to_string(), | ||||
|         "df".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(
 | ||||
|     //    "...".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_topology = K3DTopology::new( | ||||
|         "dev-cluster".to_string(), | ||||
|         k3d_host, | ||||
|         "devcluster".to_string(), | ||||
|     // Create our topologies
 | ||||
|     let linux = LinuxHostTopology { hostname: "server1.example.com".to_string() }; | ||||
|     let bare_metal = BareMetalTopology { device_id: "device001".to_string() }; | ||||
|     
 | ||||
|     // Create our maestros
 | ||||
|     let linux_maestro = Maestro::new(linux); | ||||
|     let bare_metal_maestro = Maestro::new(bare_metal); | ||||
|     
 | ||||
|     // Create scores
 | ||||
|     let command_score = CommandScore::new( | ||||
|         "List Files".to_string(), | ||||
|         vec!["ls".to_string(), "-la".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()], | ||||
|     )); | ||||
| 
 | ||||
|     k3d_maestro.register_score(K8sResourceScore::new( | ||||
|         "deploy-nginx".to_string(), | ||||
|         r#" | ||||
|         apiVersion: apps/v1 | ||||
|         kind: Deployment | ||||
|         metadata: | ||||
|           name: nginx | ||||
|         spec: | ||||
|           replicas: 1 | ||||
|           selector: | ||||
|             matchLabels: | ||||
|               app: nginx | ||||
|           template: | ||||
|             metadata: | ||||
|               labels: | ||||
|                 app: nginx | ||||
|             spec: | ||||
|               containers: | ||||
|               - name: nginx | ||||
|                 image: nginx:latest | ||||
|                 ports: | ||||
|                 - containerPort: 80 | ||||
|         "#
 | ||||
|         .to_string(), | ||||
|     )); | ||||
| 
 | ||||
|     // Orchestrate both topologies
 | ||||
|     linux_maestro.orchestrate().unwrap(); | ||||
|     k3d_maestro.orchestrate().unwrap(); | ||||
|     
 | ||||
|     let file_read_score = FileScore::new_read( | ||||
|         "Read Config".to_string(), | ||||
|         "/etc/config.json".to_string() | ||||
|     ); | ||||
|     
 | ||||
|     // This will work because LinuxHostTopology implements CommandExecution
 | ||||
|     linux_maestro.execute_score(command_score.clone()); | ||||
|     
 | ||||
|     // This will work because LinuxHostTopology implements FileSystem
 | ||||
|     linux_maestro.execute_score(file_read_score.clone()); | ||||
|     
 | ||||
|     // This will work because BareMetalTopology implements FileSystem
 | ||||
|     bare_metal_maestro.execute_score(file_read_score); | ||||
|     
 | ||||
|     // This would NOT compile because BareMetalTopology doesn't implement CommandExecution:
 | ||||
|     // bare_metal_maestro.execute_score(command_score);
 | ||||
|     // The error would occur at compile time, ensuring type safety
 | ||||
|     
 | ||||
|     println!("All scores executed successfully!"); | ||||
| } | ||||
|  | ||||
							
								
								
									
										314
									
								
								examples/topology/src/main_claude37_2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								examples/topology/src/main_claude37_2.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,314 @@ | ||||
| mod main_gemini25pro; | ||||
| use std::process::Command; | ||||
| 
 | ||||
| pub trait Capability {} | ||||
| 
 | ||||
| pub trait CommandCapability: Capability { | ||||
|     fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| pub trait KubernetesCapability: Capability { | ||||
|     fn apply_manifest(&self, manifest: &str) -> Result<(), String>; | ||||
|     fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| pub trait Topology { | ||||
|     fn name(&self) -> &str; | ||||
| } | ||||
| 
 | ||||
| pub trait Score<T: Topology> { | ||||
|     fn compile(&self) -> Result<Box<dyn Interpret<T>>, String>; | ||||
|     fn name(&self) -> &str; | ||||
| } | ||||
| 
 | ||||
| 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: &[&str]) -> Result<String, String> { | ||||
|         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()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 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: &[&str]) -> Result<String, String> { | ||||
|         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", &["-c", &format!("cat > {}", temp_file)])?; | ||||
| 
 | ||||
|         // Apply with kubectl
 | ||||
|         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<String, String> { | ||||
|         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", | ||||
|         ]) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct CommandScore { | ||||
|     name: String, | ||||
|     command: String, | ||||
|     args: Vec<String>, | ||||
| } | ||||
| 
 | ||||
| impl CommandScore { | ||||
|     pub fn new(name: String, command: String, args: Vec<String>) -> Self { | ||||
|         Self { | ||||
|             name, | ||||
|             command, | ||||
|             args, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub trait Interpret<T: Topology> { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| struct CommandInterpret; | ||||
| 
 | ||||
| impl<T> Interpret<T> for CommandInterpret | ||||
| where | ||||
|     T: Topology + CommandCapability, | ||||
| { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String> { | ||||
|         todo!() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T> Score<T> for CommandScore | ||||
| where | ||||
|     T: Topology + CommandCapability, | ||||
| { | ||||
|     fn compile(&self) -> Result<Box<dyn Interpret<T>>, String> { | ||||
|         Ok(Box::new(CommandInterpret {})) | ||||
|     } | ||||
| 
 | ||||
|     fn name(&self) -> &str { | ||||
|         &self.name | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #[derive(Clone)] | ||||
| pub struct K8sResourceScore { | ||||
|     name: String, | ||||
|     manifest: String, | ||||
| } | ||||
| 
 | ||||
| impl K8sResourceScore { | ||||
|     pub fn new(name: String, manifest: String) -> Self { | ||||
|         Self { name, manifest } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct K8sResourceInterpret { | ||||
|     score: K8sResourceScore, | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology + KubernetesCapability> Interpret<T> for K8sResourceInterpret { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String> { | ||||
|         todo!() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T> Score<T> for K8sResourceScore | ||||
| where | ||||
|     T: Topology + KubernetesCapability, | ||||
| { | ||||
|     fn compile(&self) -> Result<Box<(dyn Interpret<T> + 'static)>, String> { | ||||
|         Ok(Box::new(K8sResourceInterpret { | ||||
|             score: self.clone(), | ||||
|         })) | ||||
|     } | ||||
| 
 | ||||
|     fn name(&self) -> &str { | ||||
|         &self.name | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct Maestro<T: Topology> { | ||||
|     topology: T, | ||||
|     scores: Vec<Box<dyn Score<T>>>, | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| impl<T: Topology> Maestro<T> { | ||||
|     pub fn new(topology: T) -> Self { | ||||
|         Self { | ||||
|             topology, | ||||
|             scores: Vec::new(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn register_score<S>(&mut self, score: S) | ||||
|     where | ||||
|         S: Score<T> + 'static, | ||||
|     { | ||||
|         println!( | ||||
|             "Registering score '{}' for topology '{}'", | ||||
|             score.name(), | ||||
|             self.topology.name() | ||||
|         ); | ||||
|         self.scores.push(Box::new(score)); | ||||
|     } | ||||
| 
 | ||||
|     pub fn orchestrate(&self) -> Result<(), String> { | ||||
|         println!("Orchestrating topology '{}'", self.topology.name()); | ||||
|         for score in &self.scores { | ||||
|             let interpret = score.compile()?; | ||||
|             interpret.execute(&self.topology)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     let linux_host = LinuxHostTopology::new("dev-machine".to_string(), "localhost".to_string()); | ||||
| 
 | ||||
|     let mut linux_maestro = Maestro::new(linux_host); | ||||
| 
 | ||||
|     linux_maestro.register_score(CommandScore::new( | ||||
|         "check-disk".to_string(), | ||||
|         "df".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(
 | ||||
|     //    "...".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_topology = K3DTopology::new( | ||||
|         "dev-cluster".to_string(), | ||||
|         k3d_host, | ||||
|         "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()], | ||||
|     )); | ||||
| 
 | ||||
|     k3d_maestro.register_score(K8sResourceScore::new( | ||||
|         "deploy-nginx".to_string(), | ||||
|         r#" | ||||
|         apiVersion: apps/v1 | ||||
|         kind: Deployment | ||||
|         metadata: | ||||
|           name: nginx | ||||
|         spec: | ||||
|           replicas: 1 | ||||
|           selector: | ||||
|             matchLabels: | ||||
|               app: nginx | ||||
|           template: | ||||
|             metadata: | ||||
|               labels: | ||||
|                 app: nginx | ||||
|             spec: | ||||
|               containers: | ||||
|               - name: nginx | ||||
|                 image: nginx:latest | ||||
|                 ports: | ||||
|                 - containerPort: 80 | ||||
|         "#
 | ||||
|         .to_string(), | ||||
|     )); | ||||
| 
 | ||||
|     // Orchestrate both topologies
 | ||||
|     linux_maestro.orchestrate().unwrap(); | ||||
|     k3d_maestro.orchestrate().unwrap(); | ||||
| } | ||||
							
								
								
									
										369
									
								
								examples/topology/src/main_gemini25pro.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										369
									
								
								examples/topology/src/main_gemini25pro.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,369 @@ | ||||
| // Import necessary items (though for this example, few are needed beyond std)
 | ||||
| use std::fmt; | ||||
| 
 | ||||
| // --- Error Handling ---
 | ||||
| // A simple error type for demonstration purposes. In a real app, use `thiserror` or `anyhow`.
 | ||||
| #[derive(Debug)] | ||||
| enum OrchestrationError { | ||||
|     CommandFailed(String), | ||||
|     KubeClientError(String), | ||||
|     TopologySetupFailed(String), | ||||
| } | ||||
| 
 | ||||
| impl fmt::Display for OrchestrationError { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         match self { | ||||
|             OrchestrationError::CommandFailed(e) => write!(f, "Command execution failed: {}", e), | ||||
|             OrchestrationError::KubeClientError(e) => write!(f, "Kubernetes client error: {}", e), | ||||
|             OrchestrationError::TopologySetupFailed(e) => write!(f, "Topology setup failed: {}", e), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl std::error::Error for OrchestrationError {} | ||||
| 
 | ||||
| // Define a common Result type
 | ||||
| type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; | ||||
| 
 | ||||
| // --- 1. Capability Specification (as Traits) ---
 | ||||
| 
 | ||||
| /// Capability trait representing the ability to run Linux commands.
 | ||||
| /// This follows the "Parse, Don't Validate" idea implicitly - if you have an object
 | ||||
| /// implementing this, you know you *can* run commands, no need to check later.
 | ||||
| trait LinuxOperations { | ||||
|     fn run_command(&self, command: &str) -> Result<String>; | ||||
| } | ||||
| 
 | ||||
| /// A mock Kubernetes client trait for demonstration.
 | ||||
| trait KubeClient { | ||||
|     fn apply_manifest(&self, manifest: &str) -> Result<()>; | ||||
|     fn get_pods(&self, namespace: &str) -> Result<Vec<String>>; | ||||
| } | ||||
| 
 | ||||
| /// Mock implementation of a KubeClient.
 | ||||
| struct MockKubeClient { | ||||
|     cluster_name: String, | ||||
| } | ||||
| 
 | ||||
| impl KubeClient for MockKubeClient { | ||||
|     fn apply_manifest(&self, manifest: &str) -> Result<()> { | ||||
|         println!( | ||||
|             "[{}] Applying Kubernetes manifest:\n---\n{}\n---", | ||||
|             self.cluster_name, manifest | ||||
|         ); | ||||
|         // Simulate success or failure
 | ||||
|         if manifest.contains("invalid") { | ||||
|             Err(Box::new(OrchestrationError::KubeClientError( | ||||
|                 "Invalid manifest content".into(), | ||||
|             ))) | ||||
|         } else { | ||||
|             Ok(()) | ||||
|         } | ||||
|     } | ||||
|     fn get_pods(&self, namespace: &str) -> Result<Vec<String>> { | ||||
|         println!( | ||||
|             "[{}] Getting pods in namespace '{}'", | ||||
|             self.cluster_name, namespace | ||||
|         ); | ||||
|         Ok(vec![ | ||||
|             format!("pod-a-12345-{}-{}", namespace, self.cluster_name), | ||||
|             format!("pod-b-67890-{}-{}", namespace, self.cluster_name), | ||||
|         ]) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Capability trait representing access to a Kubernetes cluster.
 | ||||
| /// This follows Rust Embedded WG's "Zero-Cost Abstractions" - the trait itself
 | ||||
| /// adds no runtime overhead, only compile-time structure.
 | ||||
| trait KubernetesCluster { | ||||
|     // Provides access to a Kubernetes client instance.
 | ||||
|     // Using `impl Trait` in return position for flexibility.
 | ||||
|     fn get_kube_client(&self) -> Result<impl KubeClient>; | ||||
| } | ||||
| 
 | ||||
| // --- 2. Topology Implementations ---
 | ||||
| // Topologies implement the capabilities they provide.
 | ||||
| 
 | ||||
| /// Represents a basic Linux host.
 | ||||
| #[derive(Debug, Clone)] | ||||
| struct LinuxHostTopology { | ||||
|     hostname: String, | ||||
|     // In a real scenario: SSH connection details, etc.
 | ||||
| } | ||||
| 
 | ||||
| impl LinuxHostTopology { | ||||
|     fn new(hostname: &str) -> Self { | ||||
|         println!("Initializing LinuxHostTopology for {}", hostname); | ||||
|         Self { | ||||
|             hostname: hostname.to_string(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // LinuxHostTopology provides LinuxOperations capability.
 | ||||
| impl LinuxOperations for LinuxHostTopology { | ||||
|     fn run_command(&self, command: &str) -> Result<String> { | ||||
|         println!("[{}] Running command: '{}'", self.hostname, command); | ||||
|         // Simulate command execution (e.g., via SSH)
 | ||||
|         if command.starts_with("fail") { | ||||
|             Err(Box::new(OrchestrationError::CommandFailed(format!( | ||||
|                 "Command '{}' failed", | ||||
|                 command | ||||
|             )))) | ||||
|         } else { | ||||
|             Ok(format!("Output of '{}' on {}", command, self.hostname)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Represents a K3D (Kubernetes in Docker) cluster running on a host.
 | ||||
| #[derive(Debug, Clone)] | ||||
| struct K3DTopology { | ||||
|     cluster_name: String, | ||||
|     host_os: String, // Example: might implicitly run commands on the underlying host
 | ||||
|                      // In a real scenario: Kubeconfig path, Docker client, etc.
 | ||||
| } | ||||
| 
 | ||||
| impl K3DTopology { | ||||
|     fn new(cluster_name: &str) -> Self { | ||||
|         println!("Initializing K3DTopology for cluster {}", cluster_name); | ||||
|         Self { | ||||
|             cluster_name: cluster_name.to_string(), | ||||
|             host_os: "Linux".to_string(), // Assume k3d runs on Linux for this example
 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // K3DTopology provides KubernetesCluster capability.
 | ||||
| impl KubernetesCluster for K3DTopology { | ||||
|     fn get_kube_client(&self) -> Result<impl KubeClient> { | ||||
|         println!("[{}] Creating mock Kubernetes client", self.cluster_name); | ||||
|         // In a real scenario, this would initialize a client using kubeconfig etc.
 | ||||
|         Ok(MockKubeClient { | ||||
|             cluster_name: self.cluster_name.clone(), | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // K3DTopology *also* provides LinuxOperations (e.g., for running commands inside nodes or on the host managing k3d).
 | ||||
| impl LinuxOperations for K3DTopology { | ||||
|     fn run_command(&self, command: &str) -> Result<String> { | ||||
|         println!( | ||||
|             "[{} on {} host] Running command: '{}'", | ||||
|             self.cluster_name, self.host_os, command | ||||
|         ); | ||||
|         // Simulate command execution (maybe `docker exec` or similar)
 | ||||
|         if command.starts_with("fail") { | ||||
|             Err(Box::new(OrchestrationError::CommandFailed(format!( | ||||
|                 "Command '{}' failed within k3d context", | ||||
|                 command | ||||
|             )))) | ||||
|         } else { | ||||
|             Ok(format!( | ||||
|                 "Output of '{}' within k3d cluster {}", | ||||
|                 command, self.cluster_name | ||||
|             )) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // --- 3. Score Implementations ---
 | ||||
| // Scores require capabilities via trait bounds on their execution logic.
 | ||||
| 
 | ||||
| /// Base trait for identifying scores. Could be empty or hold metadata.
 | ||||
| trait Score { | ||||
|     fn name(&self) -> &'static str; | ||||
|     // We don't put execute here, as its signature depends on required capabilities.
 | ||||
| } | ||||
| 
 | ||||
| /// A score that runs a shell command on a Linux host.
 | ||||
| #[derive(Debug)] | ||||
| struct CommandScore { | ||||
|     command: String, | ||||
| } | ||||
| 
 | ||||
| impl Score for CommandScore { | ||||
|     fn name(&self) -> &'static str { | ||||
|         "CommandScore" | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl CommandScore { | ||||
|     fn new(command: &str) -> Self { | ||||
|         Self { | ||||
|             command: command.to_string(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Execute method is generic over T, but requires T implements LinuxOperations.
 | ||||
|     /// This follows the "Scores as Polymorphic Functions" idea.
 | ||||
|     fn execute<T: LinuxOperations + ?Sized>(&self, topology: &T) -> Result<()> { | ||||
|         println!("Executing Score: {}", Score::name(self)); | ||||
|         let output = topology.run_command(&self.command)?; | ||||
|         println!("Command Score Output: {}", output); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// A score that applies a Kubernetes resource manifest.
 | ||||
| #[derive(Debug)] | ||||
| struct K8sResourceScore { | ||||
|     manifest_path: String, // Path or content
 | ||||
| } | ||||
| 
 | ||||
| impl Score for K8sResourceScore { | ||||
|     fn name(&self) -> &'static str { | ||||
|         "K8sResourceScore" | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl K8sResourceScore { | ||||
|     fn new(manifest_path: &str) -> Self { | ||||
|         Self { | ||||
|             manifest_path: manifest_path.to_string(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Execute method requires T implements KubernetesCluster.
 | ||||
|     fn execute<T: KubernetesCluster + ?Sized>(&self, topology: &T) -> Result<()> { | ||||
|         println!("Executing Score: {}", Score::name(self)); | ||||
|         let client = topology.get_kube_client()?; | ||||
|         let manifest_content = format!( | ||||
|             "apiVersion: v1\nkind: Pod\nmetadata:\n  name: my-pod-from-{}", | ||||
|             self.manifest_path | ||||
|         ); // Simulate reading file
 | ||||
|         client.apply_manifest(&manifest_content)?; | ||||
|         println!( | ||||
|             "K8s Resource Score applied manifest: {}", | ||||
|             self.manifest_path | ||||
|         ); | ||||
|         Ok(()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // --- 4. Maestro (The Orchestrator) ---
 | ||||
| 
 | ||||
| // This version of Maestro uses a helper trait (`ScoreRunner`) to enable
 | ||||
| // storing heterogeneous scores while preserving compile-time checks.
 | ||||
| 
 | ||||
| /// A helper trait to erase the specific capability requirements *after*
 | ||||
| /// the compiler has verified them, allowing storage in a Vec.
 | ||||
| /// The verification happens in the blanket impls below.
 | ||||
| trait ScoreRunner<T> { | ||||
|     // T is the concrete Topology type
 | ||||
|     fn run(&self, topology: &T) -> Result<()>; | ||||
|     fn name(&self) -> &'static str; | ||||
| } | ||||
| 
 | ||||
| // Blanket implementation: A CommandScore can be run on any Topology T
 | ||||
| // *if and only if* T implements LinuxOperations.
 | ||||
| // The compiler checks this bound when `add_score` is called.
 | ||||
| impl<T: LinuxOperations> ScoreRunner<T> for CommandScore { | ||||
|     fn run(&self, topology: &T) -> Result<()> { | ||||
|         self.execute(topology) // Call the capability-specific execute method
 | ||||
|     } | ||||
|     fn name(&self) -> &'static str { | ||||
|         Score::name(self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Blanket implementation: A K8sResourceScore can be run on any Topology T
 | ||||
| // *if and only if* T implements KubernetesCluster.
 | ||||
| impl<T: KubernetesCluster> ScoreRunner<T> for K8sResourceScore { | ||||
|     fn run(&self, topology: &T) -> Result<()> { | ||||
|         self.execute(topology) // Call the capability-specific execute method
 | ||||
|     } | ||||
|     fn name(&self) -> &'static str { | ||||
|         Score::name(self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// The Maestro orchestrator, strongly typed to a specific Topology `T`.
 | ||||
| struct Maestro<T> { | ||||
|     topology: T, | ||||
|     // Stores type-erased runners, but addition is type-safe.
 | ||||
|     scores: Vec<Box<dyn ScoreRunner<T>>>, | ||||
| } | ||||
| 
 | ||||
| impl<T> Maestro<T> { | ||||
|     /// Creates a new Maestro instance bound to a specific topology.
 | ||||
|     fn new(topology: T) -> Self { | ||||
|         println!("Maestro initialized."); | ||||
|         Maestro { | ||||
|             topology, | ||||
|             scores: Vec::new(), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Adds a score to the Maestro.
 | ||||
|     /// **Compile-time check happens here!**
 | ||||
|     /// The `S: ScoreRunner<T>` bound ensures that the score `S` provides an
 | ||||
|     /// implementation of `ScoreRunner` *for the specific topology type `T`*.
 | ||||
|     /// The blanket impls above ensure this is only possible if `T` has the
 | ||||
|     /// required capabilities for `S`.
 | ||||
|     /// This directly follows the "Theoretical Example: The Compiler as an Ally".
 | ||||
|     fn add_score<S>(&mut self, score: S) | ||||
|     where | ||||
|         S: Score + ScoreRunner<T> + 'static, // S must be runnable on *this* T
 | ||||
|     { | ||||
|         println!("Registering score: {}", Score::name(&score)); | ||||
|         self.scores.push(Box::new(score)); | ||||
|     } | ||||
| 
 | ||||
|     /// Runs all registered scores sequentially on the topology.
 | ||||
|     fn run_all(&self) -> Vec<Result<()>> { | ||||
|         println!("\n--- Running all scores ---"); | ||||
|         self.scores | ||||
|             .iter() | ||||
|             .map(|score_runner| { | ||||
|                 println!("---"); | ||||
|                 let result = score_runner.run(&self.topology); | ||||
|                 match &result { | ||||
|                     Ok(_) => println!("Score '{}' completed successfully.", score_runner.name()), | ||||
|                     Err(e) => eprintln!("Score '{}' failed: {}", score_runner.name(), e), | ||||
|                 } | ||||
|                 result | ||||
|             }) | ||||
|             .collect() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // --- 5. Example Usage ---
 | ||||
| 
 | ||||
| fn main() { | ||||
|     println!("=== Scenario 1: Linux Host Topology ==="); | ||||
|     let linux_host = LinuxHostTopology::new("server1.example.com"); | ||||
|     let mut maestro_linux = Maestro::new(linux_host); | ||||
| 
 | ||||
|     // Add scores compatible with LinuxHostTopology (which has LinuxOperations)
 | ||||
|     maestro_linux.add_score(CommandScore::new("uname -a")); | ||||
|     maestro_linux.add_score(CommandScore::new("ls -l /tmp")); | ||||
| 
 | ||||
|     // *** Compile-time Error Example ***
 | ||||
|     // Try adding a score that requires KubernetesCluster capability.
 | ||||
|     // This line WILL NOT COMPILE because LinuxHostTopology does not implement KubernetesCluster,
 | ||||
|     // therefore K8sResourceScore does not implement ScoreRunner<LinuxHostTopology>.
 | ||||
|     // maestro_linux.add_score(K8sResourceScore::new("my-app.yaml"));
 | ||||
|     // Uncomment the line above to see the compiler error! The error message will
 | ||||
|     // likely point to the `ScoreRunner<LinuxHostTopology>` bound not being satisfied
 | ||||
|     // for `K8sResourceScore`.
 | ||||
| 
 | ||||
|     let results_linux = maestro_linux.run_all(); | ||||
|     println!("\nLinux Host Results: {:?}", results_linux); | ||||
| 
 | ||||
|     println!("\n=== Scenario 2: K3D Topology ==="); | ||||
|     let k3d_cluster = K3DTopology::new("dev-cluster"); | ||||
|     let mut maestro_k3d = Maestro::new(k3d_cluster); | ||||
| 
 | ||||
|     // Add scores compatible with K3DTopology (which has LinuxOperations AND KubernetesCluster)
 | ||||
|     maestro_k3d.add_score(CommandScore::new("pwd")); // Uses LinuxOperations
 | ||||
|     maestro_k3d.add_score(K8sResourceScore::new("nginx-deployment.yaml")); // Uses KubernetesCluster
 | ||||
|     maestro_k3d.add_score(K8sResourceScore::new("invalid-service.yaml")); // Test error case
 | ||||
|     maestro_k3d.add_score(CommandScore::new("fail please")); // Test error case
 | ||||
| 
 | ||||
|     let results_k3d = maestro_k3d.run_all(); | ||||
|     println!("\nK3D Cluster Results: {:?}", results_k3d); | ||||
| 
 | ||||
|     println!("\n=== Compile-Time Safety Demonstrated ==="); | ||||
|     println!("(Check the commented-out line in the code for the compile error example)"); | ||||
| } | ||||
							
								
								
									
										492
									
								
								examples/topology/src/main_geminifail.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										492
									
								
								examples/topology/src/main_geminifail.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,492 @@ | ||||
| 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<String>) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| pub trait KubernetesCapability: Capability { | ||||
|     fn apply_manifest(&self, manifest: &str) -> Result<(), String>; | ||||
|     fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| pub trait Topology { | ||||
|     fn name(&self) -> &str; | ||||
| } | ||||
| 
 | ||||
| pub trait Interpret<T: Topology> { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String>; | ||||
| } | ||||
| 
 | ||||
| // --- 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<String>, | ||||
| } | ||||
| 
 | ||||
| impl CommandScore { | ||||
|     pub fn new(name: String, command: String, args: Vec<String>) -> 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<dyn ScoreDefinition>; | ||||
| } | ||||
| 
 | ||||
| // Implement Clone for Box<dyn ScoreDefinition>
 | ||||
| impl Clone for Box<dyn ScoreDefinition> { | ||||
|     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<dyn ScoreDefinition> { | ||||
|         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<dyn ScoreDefinition> { | ||||
|         Box::new(self.clone()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| // --- Score Compatibility Trait (Generic over T) ---
 | ||||
| // This remains largely the same, ensuring compile-time checks
 | ||||
| pub trait Score<T: Topology>: ScoreDefinition { | ||||
|     // No need for name() here, it's in ScoreDefinition
 | ||||
|     fn compile(&self) -> Result<Box<dyn Interpret<T>>, String>; | ||||
| } | ||||
| 
 | ||||
| // --- Implementations of Score<T> (Crucial Link) ---
 | ||||
| 
 | ||||
| // CommandScore implements Score<T> for any T with CommandCapability
 | ||||
| impl<T> Score<T> for CommandScore | ||||
| where | ||||
|     T: Topology + CommandCapability + 'static, // Added 'static bound often needed for Box<dyn>
 | ||||
|     // Self: ScoreDefinition // This bound is implicit now
 | ||||
| { | ||||
|     fn compile(&self) -> Result<Box<dyn Interpret<T>>, String> { | ||||
|         // Pass necessary data from self to CommandInterpret
 | ||||
|         Ok(Box::new(CommandInterpret { | ||||
|             command: self.command.clone(), | ||||
|             args: self.args.clone(), | ||||
|         })) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // K8sResourceScore implements Score<T> for any T with KubernetesCapability
 | ||||
| impl<T> Score<T> for K8sResourceScore | ||||
| where | ||||
|     T: Topology + KubernetesCapability + 'static, | ||||
|     // Self: ScoreDefinition
 | ||||
| { | ||||
|     fn compile(&self) -> Result<Box<dyn Interpret<T>>, 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<String>, // Or owned Strings if lifetime is tricky
 | ||||
| } | ||||
| 
 | ||||
| impl<'a, T> Interpret<T> for CommandInterpret | ||||
| where | ||||
|     T: Topology + CommandCapability, | ||||
| { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String> { | ||||
|          // Now uses data stored in self
 | ||||
|         topology.execute_command(&self.command, &self.args) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| struct K8sResourceInterpret { | ||||
|     manifest: String, | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology + KubernetesCapability> Interpret<T> for K8sResourceInterpret { | ||||
|     fn execute(&self, topology: &T) -> Result<String, String> { | ||||
|         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<T> bound
 | ||||
| pub struct Maestro<T: Topology> { | ||||
|     topology: T, | ||||
|     // Stores Score<T> trait objects, ensuring compatibility
 | ||||
|     scores: Vec<Box<dyn Score<T>>>, | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology + 'static> Maestro<T> { // 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<T>
 | ||||
|     pub fn register_score<S>(&mut self, score: S) -> Result<(), String> | ||||
|     where | ||||
|         S: Score<T> + 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<dyn ScoreDefinition> and try to downcast/wrap
 | ||||
|     { | ||||
|         println!( | ||||
|             "Registering score '{}' for topology '{}'", | ||||
|             score.name(), | ||||
|             self.topology.name() | ||||
|         ); | ||||
|         // The compiler has already guaranteed that S implements Score<T>
 | ||||
|         // We need to box it as dyn Score<T>
 | ||||
|         self.scores.push(Box::new(score)); | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|      // Alternative registration if you have Box<dyn ScoreDefinition>
 | ||||
|      pub fn register_score_definition(&mut self, score_def: Box<dyn ScoreDefinition>) -> 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::<CommandScore>() { | ||||
|              // 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<dyn Score<T>>
 | ||||
|              let boxed_score: Box<dyn Score<T>> = 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::<K8sResourceScore>() { | ||||
|               // Check if T satisfies K8sResourceScore's requirements (KubernetesCapability)
 | ||||
|              let boxed_score: Box<dyn Score<T>> = 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<S: Score<T>>` 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<T> trait object
 | ||||
|     definition: Box<dyn ScoreDefinition>, | ||||
| } | ||||
| 
 | ||||
| struct HarmonyTui { | ||||
|     // List of available score *definitions*
 | ||||
|     available_scores: Vec<ScoreItem>, | ||||
|     // Example: Maybe maps topology names to Maestros
 | ||||
|     // maestros: HashMap<String, Box<dyn Any>>, // Storing Maestros generically is another challenge!
 | ||||
| } | ||||
| 
 | ||||
| impl HarmonyTui { | ||||
|     fn new() -> Self { | ||||
|         HarmonyTui { available_scores: vec![] } | ||||
|     } | ||||
| 
 | ||||
|     fn add_available_score(&mut self, score_def: Box<dyn ScoreDefinition>) { | ||||
|          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<T>( | ||||
|          &self, | ||||
|          score_index: usize, | ||||
|          maestro: &mut Maestro<T> | ||||
|      ) -> 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<dyn ScoreDefinition>, need to add to Maestro<T>
 | ||||
|          // Easiest is to downcast and call the generic register_score
 | ||||
| 
 | ||||
|          if let Some(cs) = score_item.definition.as_any().downcast_ref::<CommandScore>() { | ||||
|              // Compiler checks if CommandScore: Score<T> via register_score's bound
 | ||||
|              maestro.register_score(cs.clone())?; | ||||
|              Ok(()) | ||||
|          } else if let Some(ks) = score_item.definition.as_any().downcast_ref::<K8sResourceScore>() { | ||||
|               // Compiler checks if K8sResourceScore: Score<T> 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<String>) -> Result<String, String> { | ||||
|         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<String, String> { | ||||
|         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<String>) -> Result<String, String> { | ||||
|         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<LinuxHostTopology>
 | ||||
|     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<K3DTopology>
 | ||||
|     k3d_maestro.register_score(nodes_score.clone()).unwrap(); | ||||
|     // Compiler checks K8sResourceScore: Score<K3DTopology>
 | ||||
|     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<LinuxHostTopology> 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(); | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user