diff --git a/examples/topology/Cargo.toml b/examples/topology/Cargo.toml new file mode 100644 index 0000000..96740a9 --- /dev/null +++ b/examples/topology/Cargo.toml @@ -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 diff --git a/examples/topology/src/main.rs b/examples/topology/src/main.rs new file mode 100644 index 0000000..f3091c4 --- /dev/null +++ b/examples/topology/src/main.rs @@ -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; +} + +/// Capability for interacting with a Kubernetes cluster +pub trait KubernetesCapability: Capability { + fn apply_manifest(&self, manifest: &str) -> Result<(), String>; + fn get_resource(&self, resource_type: &str, name: &str) -> Result; +} + +// ===== Topology Traits ===== + +/// Base trait for all topologies +pub trait Topology { + // Base topology methods that don't depend on capabilities + fn name(&self) -> &str; +} + +// ===== Score Traits ===== + +/// Generic Score trait with an associated Capability type +pub trait Score { + fn apply(&self, topology: &T) -> Result<(), String>; + fn 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 { + 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 { + // 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::()); + 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 { + 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, +} + +impl CommandScore { + pub fn new(name: String, command: String, args: Vec) -> Self { + Self { name, command, args } + } +} + +impl Score 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 Score for K8sResourceScore +where + T: Topology + KubernetesCapability +{ + fn apply(&self, topology: &T) -> Result<(), String> { + println!("Applying K8sResourceScore '{}' to topology '{}'", self.name, topology.name()); + topology.apply_manifest(&self.manifest) + } + + fn name(&self) -> &str { + &self.name + } +} + +// ===== Maestro Orchestrator ===== + +/// Type-safe orchestrator that enforces capability requirements at compile time +pub struct Maestro { + topology: T, + scores: Vec>>, +} + +/// A trait object wrapper that hides the specific Score type but preserves its +/// capability requirements +trait ScoreWrapper { + fn apply(&self, topology: &T) -> Result<(), String>; + fn name(&self) -> &str; +} + +/// Implementation of ScoreWrapper for any Score that works with topology T +impl ScoreWrapper for S +where + T: Topology, + S: Score + 'static +{ + fn apply(&self, topology: &T) -> Result<(), String> { + >::apply(self, topology) + } + + fn name(&self) -> &str { + >::name(self) + } +} + +impl Maestro { + pub fn new(topology: T) -> Self { + Self { + topology, + scores: Vec::new(), + } + } + + /// Register a score that is compatible with this topology's capabilities + 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)); + } + + /// 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(); +} diff --git a/examples/topology/src/main_claudev1.rs b/examples/topology/src/main_claudev1.rs new file mode 100644 index 0000000..480fa2c --- /dev/null +++ b/examples/topology/src/main_claudev1.rs @@ -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; +} + +/// Capability for interacting with a Kubernetes cluster +pub trait KubernetesCapability: Capability { + fn apply_manifest(&self, manifest: &str) -> Result<(), String>; + fn get_resource(&self, resource_type: &str, name: &str) -> Result; +} + +// ===== Topology Traits ===== + +/// Base trait for all topologies +pub trait Topology { + // Base topology methods that don't depend on capabilities + fn name(&self) -> &str; +} + +// ===== Score Traits ===== + +/// Generic Score trait with an associated Capability type +pub trait Score { + fn apply(&self, topology: &T) -> Result<(), String>; + fn 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 { + 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 { + // 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::()); + 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 { + 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, +} + +impl CommandScore { + pub fn new(name: String, command: String, args: Vec) -> Self { + Self { name, command, args } + } +} + +impl Score 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 Score for K8sResourceScore +where + T: Topology + KubernetesCapability +{ + fn apply(&self, topology: &T) -> Result<(), String> { + println!("Applying K8sResourceScore '{}' to topology '{}'", self.name, topology.name()); + topology.apply_manifest(&self.manifest) + } + + fn name(&self) -> &str { + &self.name + } +} + +// ===== Maestro Orchestrator ===== + +/// Type-safe orchestrator that enforces capability requirements at compile time +pub struct Maestro { + topology: T, + scores: Vec>>, +} + +/// A trait object wrapper that hides the specific Score type but preserves its +/// capability requirements +trait ScoreWrapper { + fn apply(&self, topology: &T) -> Result<(), String>; + fn name(&self) -> &str; +} + +/// Implementation of ScoreWrapper for any Score that works with topology T +impl ScoreWrapper for S +where + T: Topology, + S: Score + 'static +{ + fn apply(&self, topology: &T) -> Result<(), String> { + >::apply(self, topology) + } + + fn name(&self) -> &str { + >::name(self) + } +} + +impl Maestro { + pub fn new(topology: T) -> Self { + Self { + topology, + scores: Vec::new(), + } + } + + /// Register a score that is compatible with this topology's capabilities + 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)); + } + + /// 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(); +} diff --git a/examples/topology/src/main_right.rs b/examples/topology/src/main_right.rs new file mode 100644 index 0000000..baa6c6c --- /dev/null +++ b/examples/topology/src/main_right.rs @@ -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(&self, topology: T) -> Result; +} + +// 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(&self, _topology: T) -> Result + 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(&self, _topology: T) -> Result + 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(topology: T, score: S) -> Result + 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"); +} diff --git a/examples/topology/src/main_v1.rs b/examples/topology/src/main_v1.rs new file mode 100644 index 0000000..3ae3b11 --- /dev/null +++ b/examples/topology/src/main_v1.rs @@ -0,0 +1,155 @@ +mod main_right; +mod main_claude; +// Capability Traits + +trait Capability {} + +trait LinuxOperations: Capability { + fn execute_command(&self, command: &str) -> Result; +} + +trait KubernetesOperations: Capability { + fn create_resource(&self, resource: &str) -> Result; + fn delete_resource(&self, resource: &str) -> Result; +} + +// Topology Implementations + +struct LinuxHostTopology; + +impl LinuxOperations for LinuxHostTopology { + fn execute_command(&self, command: &str) -> Result { + // 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 { + // Implementation for creating Kubernetes resources in K3D + Ok(format!("Created resource: {}", resource)) + } + + fn delete_resource(&self, resource: &str) -> Result { + // Implementation for deleting Kubernetes resources in K3D + Ok(format!("Deleted resource: {}", resource)) + } +} + +impl Capability for K3DTopology {} + +// Score Implementations + +struct K8sResourceScore { + resource: String, +} + +impl Score for K8sResourceScore +where + T: KubernetesOperations, +{ + fn execute(&self, topology: &T) -> Result { + topology.create_resource(&self.resource) + } +} + +struct CommandScore { + command: String, +} + +impl Score for CommandScore +where + T: LinuxOperations + 'static, +{ + fn execute(&self, topology: &T) -> Result { + topology.execute_command(&self.command) + } +} + +// Score Trait + +trait Score +where + T: Capability + 'static, +{ + fn execute(&self, topology: &T) -> Result; +} + +// Maestro Implementation + +struct Maestro { + scores: Vec>>>, +} + +impl Maestro { + fn new() -> Self { + Maestro { scores: Vec::new() } + } + + fn register_score(&mut self, score: Box) + where + T: Score> + 'static, + { + self.scores.push(Box::new(score)); + } + + fn execute_scores(&self, topology: &T) -> Result, String> + where + T: Capability + 'static, + { + let mut results = Vec::new(); + for score in &self.scores { + if let Some(score) = score.as_any().downcast_ref::>>() { + results.push(score.execute(topology)?); + } + } + Ok(results) + } +} + +// Helper trait for downcasting + +trait AsAny { + fn as_any(&self) -> &dyn std::any::Any; +} + +impl 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); + } +}