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