diff --git a/README.md b/README.md new file mode 100644 index 0000000..5333e05 --- /dev/null +++ b/README.md @@ -0,0 +1,98 @@ +# nocontrol + +A Rust library for building Kubernetes-style reconciliation controllers. Define your desired state with manifests and let nocontrol continuously reconcile them. + +## Features + +- **Declarative manifests** - Define your desired state as typed specifications +- **Automatic reconciliation** - Changes trigger reconciliation loops +- **Configurable resync** - Periodic full reconciliation (default: 5 minutes) +- **Lease-based coordination** - Safe for distributed deployments +- **Requeue support** - Schedule future reconciliations with delays + +## Quick Start + +```rust +use nocontrol::{ + ControlPlane, Operator, OperatorState, Specification, + manifests::{Action, Manifest, ManifestMetadata, ManifestState}, +}; +use serde::{Deserialize, Serialize}; + +// 1. Define your specification +#[derive(Clone, Serialize, Deserialize)] +struct MySpec { + replicas: u32, +} + +impl Specification for MySpec { + fn kind(&self) -> &'static str { + "MyResource" + } +} + +// 2. Implement the Operator trait +#[derive(Clone)] +struct MyOperator; + +impl Operator for MyOperator { + type Specifications = MySpec; + + async fn reconcile( + &self, + manifest: &mut ManifestState, + ) -> anyhow::Result { + println!("Reconciling: {}", manifest.manifest.name); + + // Your reconciliation logic here + // ... + + // Requeue after 30 seconds + Ok(Action::Requeue(std::time::Duration::from_secs(30))) + } +} + +// 3. Run the control plane +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let operator = OperatorState::new(MyOperator); + let control_plane = ControlPlane::new(operator); + + // Add a manifest + control_plane.add_manifest(Manifest { + name: "my-resource".into(), + metadata: ManifestMetadata {}, + spec: MySpec { replicas: 3 }, + }).await?; + + // Run the reconciliation loop + control_plane.execute().await +} +``` + +## Configuration + +Configure the operator with `OperatorConfig`: + +```rust +use nocontrol::{OperatorConfig, OperatorState}; +use std::time::Duration; + +let config = OperatorConfig { + resync_interval: Duration::from_secs(10 * 60), // 10 minutes + ..Default::default() +}; + +let operator = OperatorState::new_with_config(MyOperator, config); +``` + +## Actions + +Return from `reconcile()` to control scheduling: + +- `Action::None` - No follow-up reconciliation +- `Action::Requeue(duration)` - Reconcile again after the specified delay + +## License + +MIT