harmony/adr/003-abstractions/topology/src/main_claude37_2.rs
Jean-Gabriel Gill-Couture 00e71b97f6
All checks were successful
Run Check Script / check (push) Successful in 1m49s
Run Check Script / check (pull_request) Successful in 1m48s
chore: Move ADR helper files into folders with their corresponding ADR number
2025-06-09 13:54:23 -04:00

315 lines
7.6 KiB
Rust

mod main_gemini25pro;
use std::process::Command;
pub trait Capability {}
pub trait CommandCapability: Capability {
fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String>;
}
pub trait KubernetesCapability: Capability {
fn apply_manifest(&self, manifest: &str) -> Result<(), String>;
fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String>;
}
pub trait Topology {
fn name(&self) -> &str;
}
pub trait Score<T: Topology> {
fn compile(&self) -> Result<Box<dyn Interpret<T>>, String>;
fn name(&self) -> &str;
}
pub struct LinuxHostTopology {
name: String,
host: String,
}
impl Capability for LinuxHostTopology {}
impl LinuxHostTopology {
pub fn new(name: String, host: String) -> Self {
Self { name, host }
}
}
impl Topology for LinuxHostTopology {
fn name(&self) -> &str {
&self.name
}
}
impl CommandCapability for LinuxHostTopology {
fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String> {
println!("Executing on {}: {} {:?}", self.host, command, args);
// In a real implementation, this would SSH to the host and execute the command
let output = Command::new(command)
.args(args)
.output()
.map_err(|e| e.to_string())?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
}
pub struct K3DTopology {
name: String,
linux_host: LinuxHostTopology,
cluster_name: String,
}
impl Capability for K3DTopology {}
impl K3DTopology {
pub fn new(name: String, linux_host: LinuxHostTopology, cluster_name: String) -> Self {
Self {
name,
linux_host,
cluster_name,
}
}
}
impl Topology for K3DTopology {
fn name(&self) -> &str {
&self.name
}
}
impl CommandCapability for K3DTopology {
fn execute_command(&self, command: &str, args: &[&str]) -> Result<String, String> {
self.linux_host.execute_command(command, args)
}
}
impl KubernetesCapability for K3DTopology {
fn apply_manifest(&self, manifest: &str) -> Result<(), String> {
println!("Applying manifest to K3D cluster '{}'", self.cluster_name);
// Write manifest to a temporary file
let temp_file = format!("/tmp/manifest-harmony-temp.yaml");
// Use the linux_host directly to avoid capability trait bounds
self.linux_host
.execute_command("bash", &["-c", &format!("cat > {}", temp_file)])?;
// Apply with kubectl
self.linux_host.execute_command("kubectl", &[
"--context",
&format!("k3d-{}", self.cluster_name),
"apply",
"-f",
&temp_file,
])?;
Ok(())
}
fn get_resource(&self, resource_type: &str, name: &str) -> Result<String, String> {
println!(
"Getting resource {}/{} from K3D cluster '{}'",
resource_type, name, self.cluster_name
);
self.linux_host.execute_command("kubectl", &[
"--context",
&format!("k3d-{}", self.cluster_name),
"get",
resource_type,
name,
"-o",
"yaml",
])
}
}
pub struct CommandScore {
name: String,
command: String,
args: Vec<String>,
}
impl CommandScore {
pub fn new(name: String, command: String, args: Vec<String>) -> Self {
Self {
name,
command,
args,
}
}
}
pub trait Interpret<T: Topology> {
fn execute(&self, topology: &T) -> Result<String, String>;
}
struct CommandInterpret;
impl<T> Interpret<T> for CommandInterpret
where
T: Topology + CommandCapability,
{
fn execute(&self, topology: &T) -> Result<String, String> {
todo!()
}
}
impl<T> Score<T> for CommandScore
where
T: Topology + CommandCapability,
{
fn compile(&self) -> Result<Box<dyn Interpret<T>>, String> {
Ok(Box::new(CommandInterpret {}))
}
fn name(&self) -> &str {
&self.name
}
}
#[derive(Clone)]
pub struct K8sResourceScore {
name: String,
manifest: String,
}
impl K8sResourceScore {
pub fn new(name: String, manifest: String) -> Self {
Self { name, manifest }
}
}
struct K8sResourceInterpret {
score: K8sResourceScore,
}
impl<T: Topology + KubernetesCapability> Interpret<T> for K8sResourceInterpret {
fn execute(&self, topology: &T) -> Result<String, String> {
todo!()
}
}
impl<T> Score<T> for K8sResourceScore
where
T: Topology + KubernetesCapability,
{
fn compile(&self) -> Result<Box<(dyn Interpret<T> + 'static)>, String> {
Ok(Box::new(K8sResourceInterpret {
score: self.clone(),
}))
}
fn name(&self) -> &str {
&self.name
}
}
pub struct Maestro<T: Topology> {
topology: T,
scores: Vec<Box<dyn Score<T>>>,
}
impl<T: Topology> Maestro<T> {
pub fn new(topology: T) -> Self {
Self {
topology,
scores: Vec::new(),
}
}
pub fn register_score<S>(&mut self, score: S)
where
S: Score<T> + 'static,
{
println!(
"Registering score '{}' for topology '{}'",
score.name(),
self.topology.name()
);
self.scores.push(Box::new(score));
}
pub fn orchestrate(&self) -> Result<(), String> {
println!("Orchestrating topology '{}'", self.topology.name());
for score in &self.scores {
let interpret = score.compile()?;
interpret.execute(&self.topology)?;
}
Ok(())
}
}
fn main() {
let linux_host = LinuxHostTopology::new("dev-machine".to_string(), "localhost".to_string());
let mut linux_maestro = Maestro::new(linux_host);
linux_maestro.register_score(CommandScore::new(
"check-disk".to_string(),
"df".to_string(),
vec!["-h".to_string()],
));
linux_maestro.orchestrate().unwrap();
// This would fail to compile if we tried to register a K8sResourceScore
// because LinuxHostTopology doesn't implement KubernetesCapability
//linux_maestro.register_score(K8sResourceScore::new(
// "...".to_string(),
// "...".to_string(),
//));
// Create a K3D topology which has both Command and Kubernetes capabilities
let k3d_host = LinuxHostTopology::new("k3d-host".to_string(), "localhost".to_string());
let k3d_topology = K3DTopology::new(
"dev-cluster".to_string(),
k3d_host,
"devcluster".to_string(),
);
// Create a maestro for the K3D topology
let mut k3d_maestro = Maestro::new(k3d_topology);
// We can register both command scores and kubernetes scores
k3d_maestro.register_score(CommandScore::new(
"check-nodes".to_string(),
"kubectl".to_string(),
vec!["get".to_string(), "nodes".to_string()],
));
k3d_maestro.register_score(K8sResourceScore::new(
"deploy-nginx".to_string(),
r#"
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
"#
.to_string(),
));
// Orchestrate both topologies
linux_maestro.orchestrate().unwrap();
k3d_maestro.orchestrate().unwrap();
}