Some checks failed
Run Check Script / check (pull_request) Failing after 10s
136 lines
4.1 KiB
Markdown
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
|