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