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

Add to your Cargo.toml:

[package]
...

[dependencies]
nocontrol = "0.0.1"
...

Note: Requires Rust nightly (edition 2024).

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<MySpec>,
    ) -> anyhow::Result<Action> {
        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:

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

Description
No description provided
Readme MIT 233 KiB
Languages
Rust 100%