Files
harmony/docs/guides/adding-capabilities.md
Jean-Gabriel Gill-Couture 64582caa64
Some checks failed
Run Check Script / check (pull_request) Failing after 10s
docs: Major rehaul of documentation
2026-03-19 22:38:55 -04:00

136 lines
4.1 KiB
Markdown

# Adding Capabilities
`Capabilities` are trait methods that a `Topology` exposes to Scores. They are the "how" — the specific APIs and features that let a Score translate intent into infrastructure actions.
## How Capabilities Work
When a Score declares it needs certain Capabilities:
```rust
impl<T: Topology + K8sclient + HelmCommand> Score<T> for MyScore {
// ...
}
```
The compiler verifies that the target `Topology` implements both `K8sclient` and `HelmCommand`. If it doesn't, compilation fails. This is the compile-time safety check that prevents invalid configurations from reaching production.
## Built-in Capabilities
Harmony provides a set of standard Capabilities:
| Capability | What it provides |
|------------|------------------|
| `K8sclient` | A Kubernetes API client |
| `HelmCommand` | A configured `helm` CLI invocation |
| `TlsRouter` | TLS certificate management |
| `NetworkManager` | Host network configuration |
| `SwitchClient` | Network switch configuration |
| `CertificateManagement` | Certificate issuance via cert-manager |
## Implementing a Capability
Capabilities are implemented as trait methods on your Topology:
```rust
use std::sync::Arc;
use harmony_k8s::K8sClient;
use harmony::topology::K8sclient;
pub struct MyTopology {
kubeconfig: Option<String>,
}
#[async_trait]
impl K8sclient for MyTopology {
async fn k8s_client(&self) -> Result<Arc<K8sClient>, String> {
let client = match &self.kubeconfig {
Some(path) => K8sClient::from_kubeconfig(path).await?,
None => K8sClient::try_default().await?,
};
Ok(Arc::new(client))
}
}
```
## Adding a Custom Capability
For specialized infrastructure needs, add your own Capability as a trait:
```rust
use async_trait::async_trait;
use crate::executors::ExecutorError;
/// A capability for configuring network switches
#[async_trait]
pub trait SwitchClient: Send + Sync {
async fn configure_port(
&self,
switch: &str,
port: &str,
vlan: u16,
) -> Result<(), ExecutorError>;
async fn configure_port_channel(
&self,
switch: &str,
name: &str,
ports: &[&str],
) -> Result<(), ExecutorError>;
}
```
Then implement it on your Topology:
```rust
use harmony_infra::brocade::BrocadeClient;
pub struct MyTopology {
switch_client: Arc<dyn SwitchClient>,
}
impl SwitchClient for MyTopology {
async fn configure_port(&self, switch: &str, port: &str, vlan: u16) -> Result<(), ExecutorError> {
self.switch_client.configure_port(switch, port, vlan).await
}
async fn configure_port_channel(&self, switch: &str, name: &str, ports: &[&str]) -> Result<(), ExecutorError> {
self.switch_client.configure_port_channel(switch, name, ports).await
}
}
```
Now Scores that need `SwitchClient` can run on `MyTopology`.
## Capability Composition
Topologies often compose multiple Capabilities to support complex Scores:
```rust
pub struct HAClusterTopology {
pub kubeconfig: Option<String>,
pub router: Arc<dyn Router>,
pub load_balancer: Arc<dyn LoadBalancer>,
pub switch_client: Arc<dyn SwitchClient>,
pub dhcp_server: Arc<dyn DhcpServer>,
pub dns_server: Arc<dyn DnsServer>,
// ...
}
impl K8sclient for HAClusterTopology { ... }
impl HelmCommand for HAClusterTopology { ... }
impl SwitchClient for HAClusterTopology { ... }
impl DhcpServer for HAClusterTopology { ... }
impl DnsServer for HAClusterTopology { ... }
impl Router for HAClusterTopology { ... }
impl LoadBalancer for HAClusterTopology { ... }
```
A Score that needs all of these can run on `HAClusterTopology` because the Topology provides all of them.
## Best Practices
- **Keep Capabilities focused** — one Capability per concern (Kubernetes client, Helm, switch config)
- **Return meaningful errors** — use specific error types so Scores can handle failures appropriately
- **Make Capabilities optional where sensible** — not every Topology needs every Capability; use `Option<T>` or a separate trait for optional features
- **Document preconditions** — if a Capability requires the infrastructure to be in a specific state, document it in the trait doc comments