forked from NationTech/harmony
		
	spike: Working on abstractions, Topology, Score, Capability, Maestro for strong type safety and nice UX/DX
This commit is contained in:
		
							parent
							
								
									2433c02de9
								
							
						
					
					
						commit
						3962238f0d
					
				
							
								
								
									
										10
									
								
								examples/topology/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								examples/topology/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | [package] | ||||||
|  | name = "example-topology" | ||||||
|  | edition = "2024" | ||||||
|  | version.workspace = true | ||||||
|  | readme.workspace = true | ||||||
|  | license.workspace = true | ||||||
|  | publish = false | ||||||
|  | 
 | ||||||
|  | [dependencies] | ||||||
|  | rand.workspace = true | ||||||
							
								
								
									
										332
									
								
								examples/topology/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										332
									
								
								examples/topology/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,332 @@ | |||||||
|  | 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<String, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// 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<String, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ===== 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<T: Topology> { | ||||||
|  |     fn apply(&self, topology: &T) -> 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 { | ||||||
|  |     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()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// 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 { 
 | ||||||
|  |             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> { | ||||||
|  |         // Delegate to the underlying Linux host
 | ||||||
|  |         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] | ||||||
|  |         )?; | ||||||
|  |         
 | ||||||
|  |         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", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ===== Concrete Scores =====
 | ||||||
|  | 
 | ||||||
|  | /// A score that executes commands on a topology
 | ||||||
|  | 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 } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Score<T> for CommandScore 
 | ||||||
|  | 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 name(&self) -> &str { | ||||||
|  |         &self.name | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A score that applies Kubernetes resources to a topology
 | ||||||
|  | pub struct K8sResourceScore { | ||||||
|  |     name: String, | ||||||
|  |     manifest: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl K8sResourceScore { | ||||||
|  |     pub fn new(name: String, manifest: String) -> Self { | ||||||
|  |         Self { name, manifest } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Score<T> 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 name(&self) -> &str { | ||||||
|  |         &self.name | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ===== Maestro Orchestrator =====
 | ||||||
|  | 
 | ||||||
|  | /// Type-safe orchestrator that enforces capability requirements at compile time
 | ||||||
|  | pub struct Maestro<T: Topology> { | ||||||
|  |     topology: T, | ||||||
|  |     scores: Vec<Box<dyn ScoreWrapper<T>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A trait object wrapper that hides the specific Score type but preserves its
 | ||||||
|  | /// capability requirements
 | ||||||
|  | trait ScoreWrapper<T: Topology> { | ||||||
|  |     fn apply(&self, topology: &T) -> Result<(), String>; | ||||||
|  |     fn name(&self) -> &str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Implementation of ScoreWrapper for any Score that works with topology T
 | ||||||
|  | impl<T, S> ScoreWrapper<T> for S 
 | ||||||
|  | where 
 | ||||||
|  |     T: Topology, | ||||||
|  |     S: Score<T> + 'static | ||||||
|  | { | ||||||
|  |     fn apply(&self, topology: &T) -> Result<(), String> { | ||||||
|  |         <S as Score<T>>::apply(self, topology) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn name(&self) -> &str { | ||||||
|  |         <S as Score<T>>::name(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Topology> Maestro<T> { | ||||||
|  |     pub fn new(topology: T) -> Self { | ||||||
|  |         Self { | ||||||
|  |             topology, | ||||||
|  |             scores: Vec::new(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Register a score that is compatible with this topology's capabilities
 | ||||||
|  |     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)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// 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)?; | ||||||
|  |         } | ||||||
|  |         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 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()] | ||||||
|  |     )); | ||||||
|  |     
 | ||||||
|  |     // This would fail to compile if we tried to register a K8sResourceScore
 | ||||||
|  |     // because LinuxHostTopology doesn't implement KubernetesCapability
 | ||||||
|  |     // linux_maestro.register_score(K8sResourceScore::new(...));
 | ||||||
|  |     
 | ||||||
|  |     // 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(); | ||||||
|  | } | ||||||
							
								
								
									
										323
									
								
								examples/topology/src/main_claudev1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										323
									
								
								examples/topology/src/main_claudev1.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,323 @@ | |||||||
|  | use std::marker::PhantomData; | ||||||
|  | use std::process::Command; | ||||||
|  | 
 | ||||||
|  | // ===== 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<String, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// 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<String, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ===== 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<T: Topology> { | ||||||
|  |     fn apply(&self, topology: &T) -> Result<(), String>; | ||||||
|  |     fn name(&self) -> &str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ===== Concrete Topologies =====
 | ||||||
|  | 
 | ||||||
|  | /// A topology representing a Linux host
 | ||||||
|  | pub struct LinuxHostTopology { | ||||||
|  |     name: String, | ||||||
|  |     host: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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()) | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A topology representing a K3D Kubernetes cluster
 | ||||||
|  | pub struct K3DTopology { | ||||||
|  |     name: String, | ||||||
|  |     linux_host: LinuxHostTopology, | ||||||
|  |     cluster_name: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 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> { | ||||||
|  |         // Delegate to the underlying Linux host
 | ||||||
|  |         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::random::<u32>()); | ||||||
|  |         self.execute_command("bash", &["-c", &format!("cat > {}", temp_file)])?; | ||||||
|  |         
 | ||||||
|  |         // Apply with kubectl
 | ||||||
|  |         self.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.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, | ||||||
|  |     args: Vec<String>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl CommandScore { | ||||||
|  |     pub fn new(name: String, command: String, args: Vec<String>) -> Self { | ||||||
|  |         Self { name, command, args } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Score<T> for CommandScore 
 | ||||||
|  | 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 name(&self) -> &str { | ||||||
|  |         &self.name | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A score that applies Kubernetes resources to a topology
 | ||||||
|  | pub struct K8sResourceScore { | ||||||
|  |     name: String, | ||||||
|  |     manifest: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl K8sResourceScore { | ||||||
|  |     pub fn new(name: String, manifest: String) -> Self { | ||||||
|  |         Self { name, manifest } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Score<T> 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 name(&self) -> &str { | ||||||
|  |         &self.name | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ===== Maestro Orchestrator =====
 | ||||||
|  | 
 | ||||||
|  | /// Type-safe orchestrator that enforces capability requirements at compile time
 | ||||||
|  | pub struct Maestro<T: Topology> { | ||||||
|  |     topology: T, | ||||||
|  |     scores: Vec<Box<dyn ScoreWrapper<T>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// A trait object wrapper that hides the specific Score type but preserves its
 | ||||||
|  | /// capability requirements
 | ||||||
|  | trait ScoreWrapper<T: Topology> { | ||||||
|  |     fn apply(&self, topology: &T) -> Result<(), String>; | ||||||
|  |     fn name(&self) -> &str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /// Implementation of ScoreWrapper for any Score that works with topology T
 | ||||||
|  | impl<T, S> ScoreWrapper<T> for S 
 | ||||||
|  | where 
 | ||||||
|  |     T: Topology, | ||||||
|  |     S: Score<T> + 'static | ||||||
|  | { | ||||||
|  |     fn apply(&self, topology: &T) -> Result<(), String> { | ||||||
|  |         <S as Score<T>>::apply(self, topology) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn name(&self) -> &str { | ||||||
|  |         <S as Score<T>>::name(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Topology> Maestro<T> { | ||||||
|  |     pub fn new(topology: T) -> Self { | ||||||
|  |         Self { | ||||||
|  |             topology, | ||||||
|  |             scores: Vec::new(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Register a score that is compatible with this topology's capabilities
 | ||||||
|  |     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)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// 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)?; | ||||||
|  |         } | ||||||
|  |         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 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()] | ||||||
|  |     )); | ||||||
|  |     
 | ||||||
|  |     // This would fail to compile if we tried to register a K8sResourceScore
 | ||||||
|  |     // because LinuxHostTopology doesn't implement KubernetesCapability
 | ||||||
|  |     // linux_maestro.register_score(K8sResourceScore::new(...));
 | ||||||
|  |     
 | ||||||
|  |     // 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(); | ||||||
|  | } | ||||||
							
								
								
									
										129
									
								
								examples/topology/src/main_right.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								examples/topology/src/main_right.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,129 @@ | |||||||
|  | use std::marker::PhantomData; | ||||||
|  | 
 | ||||||
|  | // Capability Trait Hierarchy
 | ||||||
|  | pub trait Capability {} | ||||||
|  | 
 | ||||||
|  | // Specific Capability Traits
 | ||||||
|  | pub trait ShellAccess: Capability {} | ||||||
|  | pub trait ContainerRuntime: Capability {} | ||||||
|  | pub trait KubernetesAccess: Capability {} | ||||||
|  | pub trait FileSystemAccess: Capability {} | ||||||
|  | 
 | ||||||
|  | // Topology Trait - Defines the core interface for infrastructure topologies
 | ||||||
|  | pub trait Topology { | ||||||
|  |     type Capabilities: Capability; | ||||||
|  |     
 | ||||||
|  |     fn name(&self) -> &str; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Score Trait - Defines the core interface for infrastructure transformation
 | ||||||
|  | pub trait Score { | ||||||
|  |     type RequiredCapabilities: Capability; | ||||||
|  |     type OutputTopology: Topology; | ||||||
|  | 
 | ||||||
|  |     fn apply<T: Topology>(&self, topology: T) -> Result<Self::OutputTopology, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Linux Host Topology
 | ||||||
|  | pub struct LinuxHostTopology; | ||||||
|  | 
 | ||||||
|  | impl Topology for LinuxHostTopology { | ||||||
|  |     type Capabilities = dyn ShellAccess + FileSystemAccess; | ||||||
|  | 
 | ||||||
|  |     fn name(&self) -> &str { | ||||||
|  |         "Linux Host" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ShellAccess for LinuxHostTopology {} | ||||||
|  | impl FileSystemAccess for LinuxHostTopology {} | ||||||
|  | 
 | ||||||
|  | // K3D Topology
 | ||||||
|  | pub struct K3DTopology; | ||||||
|  | 
 | ||||||
|  | impl Topology for K3DTopology { | ||||||
|  |     type Capabilities = dyn ContainerRuntime + KubernetesAccess + ShellAccess; | ||||||
|  | 
 | ||||||
|  |     fn name(&self) -> &str { | ||||||
|  |         "K3D Kubernetes Cluster" | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl ContainerRuntime for K3DTopology {} | ||||||
|  | impl KubernetesAccess for K3DTopology {} | ||||||
|  | impl ShellAccess for K3DTopology {} | ||||||
|  | 
 | ||||||
|  | // Command Score - A score that requires shell access
 | ||||||
|  | pub struct CommandScore { | ||||||
|  |     command: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Score for CommandScore { | ||||||
|  |     type RequiredCapabilities = dyn ShellAccess; | ||||||
|  |     type OutputTopology = LinuxHostTopology; | ||||||
|  | 
 | ||||||
|  |     fn apply<T: Topology>(&self, _topology: T) -> Result<Self::OutputTopology, String> 
 | ||||||
|  |     where 
 | ||||||
|  |         T: ShellAccess 
 | ||||||
|  |     { | ||||||
|  |         // Simulate command execution
 | ||||||
|  |         println!("Executing command: {}", self.command); | ||||||
|  |         Ok(LinuxHostTopology) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Kubernetes Resource Score
 | ||||||
|  | pub struct K8sResourceScore { | ||||||
|  |     resource_definition: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Score for K8sResourceScore { | ||||||
|  |     type RequiredCapabilities = dyn KubernetesAccess; | ||||||
|  |     type OutputTopology = K3DTopology; | ||||||
|  | 
 | ||||||
|  |     fn apply<T: Topology>(&self, _topology: T) -> Result<Self::OutputTopology, String> 
 | ||||||
|  |     where 
 | ||||||
|  |         T: dyn KubernetesAccess 
 | ||||||
|  |     { | ||||||
|  |         // Simulate Kubernetes resource application
 | ||||||
|  |         println!("Applying K8s resource: {}", self.resource_definition); | ||||||
|  |         Ok(K3DTopology) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Maestro - The orchestration coordinator
 | ||||||
|  | pub struct Maestro; | ||||||
|  | 
 | ||||||
|  | impl Maestro { | ||||||
|  |     // Type-safe score application
 | ||||||
|  |     pub fn apply_score<T, S>(topology: T, score: S) -> Result<S::OutputTopology, String> | ||||||
|  |     where 
 | ||||||
|  |         T: Topology, | ||||||
|  |         S: Score, | ||||||
|  |         T: S::RequiredCapabilities | ||||||
|  |     { | ||||||
|  |         score.apply(topology) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | fn main() { | ||||||
|  |     // Example usage demonstrating type-driven design
 | ||||||
|  |     let linux_host = LinuxHostTopology; | ||||||
|  |     let k3d_cluster = K3DTopology; | ||||||
|  | 
 | ||||||
|  |     // Command score on Linux host
 | ||||||
|  |     let command_score = CommandScore { | ||||||
|  |         command: "echo 'Hello, World!'".to_string(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let result = Maestro::apply_score(linux_host, command_score) | ||||||
|  |         .expect("Command score application failed"); | ||||||
|  | 
 | ||||||
|  |     // K8s resource score on K3D cluster
 | ||||||
|  |     let k8s_score = K8sResourceScore { | ||||||
|  |         resource_definition: "apiVersion: v1\nkind: Pod\n...".to_string(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     let k8s_result = Maestro::apply_score(k3d_cluster, k8s_score) | ||||||
|  |         .expect("K8s resource score application failed"); | ||||||
|  | } | ||||||
							
								
								
									
										155
									
								
								examples/topology/src/main_v1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								examples/topology/src/main_v1.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,155 @@ | |||||||
|  | mod main_right; | ||||||
|  | mod main_claude; | ||||||
|  | // Capability Traits
 | ||||||
|  | 
 | ||||||
|  | trait Capability {} | ||||||
|  | 
 | ||||||
|  | trait LinuxOperations: Capability { | ||||||
|  |     fn execute_command(&self, command: &str) -> Result<String, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | trait KubernetesOperations: Capability { | ||||||
|  |     fn create_resource(&self, resource: &str) -> Result<String, String>; | ||||||
|  |     fn delete_resource(&self, resource: &str) -> Result<String, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Topology Implementations
 | ||||||
|  | 
 | ||||||
|  | struct LinuxHostTopology; | ||||||
|  | 
 | ||||||
|  | impl LinuxOperations for LinuxHostTopology { | ||||||
|  |     fn execute_command(&self, command: &str) -> Result<String, String> { | ||||||
|  |         // Implementation for executing commands on a Linux host
 | ||||||
|  |         Ok(format!("Executed command: {}", command)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Capability for LinuxHostTopology {} | ||||||
|  | 
 | ||||||
|  | struct K3DTopology; | ||||||
|  | 
 | ||||||
|  | impl KubernetesOperations for K3DTopology { | ||||||
|  |     fn create_resource(&self, resource: &str) -> Result<String, String> { | ||||||
|  |         // Implementation for creating Kubernetes resources in K3D
 | ||||||
|  |         Ok(format!("Created resource: {}", resource)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn delete_resource(&self, resource: &str) -> Result<String, String> { | ||||||
|  |         // Implementation for deleting Kubernetes resources in K3D
 | ||||||
|  |         Ok(format!("Deleted resource: {}", resource)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Capability for K3DTopology {} | ||||||
|  | 
 | ||||||
|  | // Score Implementations
 | ||||||
|  | 
 | ||||||
|  | struct K8sResourceScore { | ||||||
|  |     resource: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Score<T> for K8sResourceScore | ||||||
|  | where | ||||||
|  |     T: KubernetesOperations, | ||||||
|  | { | ||||||
|  |     fn execute(&self, topology: &T) -> Result<String, String> { | ||||||
|  |         topology.create_resource(&self.resource) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | struct CommandScore { | ||||||
|  |     command: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T> Score<T> for CommandScore | ||||||
|  | where | ||||||
|  |     T: LinuxOperations + 'static, | ||||||
|  | { | ||||||
|  |     fn execute(&self, topology: &T) -> Result<String, String> { | ||||||
|  |         topology.execute_command(&self.command) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Score Trait
 | ||||||
|  | 
 | ||||||
|  | trait Score<T> | ||||||
|  | where | ||||||
|  |     T: Capability + 'static, | ||||||
|  | { | ||||||
|  |     fn execute(&self, topology: &T) -> Result<String, String>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Maestro Implementation
 | ||||||
|  | 
 | ||||||
|  | struct Maestro { | ||||||
|  |     scores: Vec<Box<dyn Score<Box<dyn Capability>>>>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl Maestro { | ||||||
|  |     fn new() -> Self { | ||||||
|  |         Maestro { scores: Vec::new() } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn register_score<T>(&mut self, score: Box<T>) | ||||||
|  |     where | ||||||
|  |         T: Score<Box<dyn Capability>> + 'static, | ||||||
|  |     { | ||||||
|  |         self.scores.push(Box::new(score)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn execute_scores<T>(&self, topology: &T) -> Result<Vec<String>, String> | ||||||
|  |     where | ||||||
|  |         T: Capability + 'static, | ||||||
|  |     { | ||||||
|  |         let mut results = Vec::new(); | ||||||
|  |         for score in &self.scores { | ||||||
|  |             if let Some(score) = score.as_any().downcast_ref::<Box<dyn Score<T>>>() { | ||||||
|  |                 results.push(score.execute(topology)?); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         Ok(results) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Helper trait for downcasting
 | ||||||
|  | 
 | ||||||
|  | trait AsAny { | ||||||
|  |     fn as_any(&self) -> &dyn std::any::Any; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: 'static> AsAny for T { | ||||||
|  |     fn as_any(&self) -> &dyn std::any::Any { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Main Function
 | ||||||
|  | 
 | ||||||
|  | fn main() { | ||||||
|  |     let mut maestro = Maestro::new(); | ||||||
|  | 
 | ||||||
|  |     let k8s_score = K8sResourceScore { | ||||||
|  |         resource: "deployment.yaml".to_string(), | ||||||
|  |     }; | ||||||
|  |     maestro.register_score(k8s_score); | ||||||
|  | 
 | ||||||
|  |     let command_score = CommandScore { | ||||||
|  |         command: "ls -l".to_string(), | ||||||
|  |     }; | ||||||
|  |     maestro.register_score(command_score); | ||||||
|  | 
 | ||||||
|  |     let linux_topology = LinuxHostTopology; | ||||||
|  |     let k3d_topology = K3DTopology; | ||||||
|  | 
 | ||||||
|  |     let linux_results = maestro.execute_scores(&linux_topology).unwrap(); | ||||||
|  |     println!("Linux Topology Results:"); | ||||||
|  |     for result in linux_results { | ||||||
|  |         println!("{}", result); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     let k3d_results = maestro.execute_scores(&k3d_topology).unwrap(); | ||||||
|  |     println!("K3D Topology Results:"); | ||||||
|  |     for result in k3d_results { | ||||||
|  |         println!("{}", result); | ||||||
|  |     } | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user