Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 87 additions & 14 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 3 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[workspace]
members = ["crates/*", "examples/configmapsync"]
members = ["crates/*", "examples/configmapsync", "examples/multicontroller"]
resolver = "2"

[workspace.package]
version = "0.9.2"
version = "0.9.5"
edition = "2024"
license = "MIT"
repository = "https://github.com/bartvanbenthem/koprs"
Expand All @@ -14,8 +14,4 @@ kube = { version = "1.1.0", features = ["runtime", "derive"] }
kube-runtime = "1.1.0"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
schemars = "0.8"
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"
inventory = "0.3"
schemars = "0.8"
116 changes: 96 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,119 @@ The Operator SDK for Rust is a framework that uses [`kube`](https://github.com/k
* Tools for scaffolding and code generation to bootstrap a new project fast
* Extensions to cover common Operator use cases

This repository contains the core framework, its proc macros, and the manifest generation
tooling for CRDs and RBAC.
This repository contains the core framework.

## Crates

| Crate | Description | Docs |
|-------|-------------|------|
| [`koprs`](./crates/koprs) | Core generic runtime framework | [![docs.rs](https://img.shields.io/docsrs/koprs)](https://docs.rs/koprs) [![crates.io](https://img.shields.io/crates/v/koprs)](https://crates.io/crates/koprs) |
| [`koprs-derive`](./crates/koprs-derive) | Proc macros — implementation detail | [![docs.rs](https://img.shields.io/docsrs/koprs-derive)](https://docs.rs/koprs-derive) [![crates.io](https://img.shields.io/crates/v/koprs-derive)](https://crates.io/crates/koprs-derive) |
| [`koprs-gen`](./crates/koprs-gen) | CRD and RBAC manifest generation CLI | [![docs.rs](https://img.shields.io/docsrs/koprs-gen)](https://docs.rs/koprs-gen) [![crates.io](https://img.shields.io/crates/v/koprs-gen)](https://crates.io/crates/koprs-gen) |
| [`koprs`](./crates/koprs) ([README](./crates/koprs/README.md)) | Core generic runtime framework | [![docs.rs](https://img.shields.io/docsrs/koprs)](https://docs.rs/koprs) [![crates.io](https://img.shields.io/crates/v/koprs)](https://crates.io/crates/koprs) |

## Workspace layout

```
koprs/
├── Cargo.toml # workspace manifest
├── Cargo.lock
└── crates/
├── koprs/ # core library
├── koprs-derive/ # proc macros
└── koprs-gen/ # codegen CLI
├── crates/
│ └── koprs/ # core library
└── examples/
├── configmapsync/ # single CRD, single controller
└── multicontroller/ # multiple CRDs, multiple controllers in one operator
```

## Getting started

If you are here to build a Kubernetes operator, you want [`koprs`](./crates/koprs). Start there.

For a working end-to-end example, see the [configmapsync operator](./examples/configmapsync/README.md).

For working end-to-end examples, see:

* [configmapsync](./examples/configmapsync/README.md) — a single CRD reconciled by one controller; the best starting point.
* [multicontroller](./examples/multicontroller/README.md) — multiple CRDs (`SecretSync`, `ServiceAccountSync`) each reconciled by its own controller, run side by side in one operator binary.

### Minimal example

A `koprs` operator boils down to three pieces: a CRD type, a [`Reconciler`](./crates/koprs/src/controller.rs),
and a [`ControllerBuilder`](./crates/koprs/src/controller.rs) that wires it all together.

```rust,no_run
use std::sync::Arc;
use std::time::Duration;

use kube::{Api, Client, CustomResource, ResourceExt};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use koprs::controller::{Action, Context, ControllerBuilder, Reconciler};
use koprs::error::KubeGenericError;
use koprs::status::patch_status_namespaced;

/// The `Greeting` CRD — `kube::CustomResource` derives the type, its CRD spec,
/// and the generated `Greeting` struct (spec + status + metadata) in one go.
#[derive(CustomResource, Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
#[kube(
group = "example.io",
version = "v1alpha1",
kind = "Greeting",
namespaced,
status = "GreetingStatus"
)]
pub struct GreetingSpec {
pub message: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, JsonSchema)]
pub struct GreetingStatus {
pub ready: bool,
}

struct GreetingReconciler;

impl Reconciler<Greeting> for GreetingReconciler {
type Error = KubeGenericError;

async fn reconcile(&self, cr: Arc<Greeting>, ctx: Arc<Context>) -> Result<Action, Self::Error> {
let name = cr.name_any();
let namespace = cr
.namespace()
.ok_or(KubeGenericError::MissingMetadata("namespace".into()))?;

// Mark the resource ready — replace with your own reconciliation logic.
patch_status_namespaced::<Greeting, GreetingStatus>(
ctx.client.clone(),
&namespace,
&name,
GreetingStatus { ready: true },
"greeting-operator",
)
.await?;

Ok(Action::requeue(Duration::from_secs(300)))
}
// error_policy defaults to requeue(30s) — override it for custom backoff
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Client::try_default().await?;
let api: Api<Greeting> = Api::all(client.clone());
let ctx = Context::new(client);

ControllerBuilder::new(api)
.health_port(8080)
.graceful_shutdown()
.run(GreetingReconciler, ctx)
.await?;

Ok(())
}
```

If you want to generate CRD or RBAC manifests from your annotated Rust types, you want [`koprs-gen`](./crates/koprs-gen).
> [!WARNING]
> `koprs-gen` is under development and not yet available.
For finalizers, owned-resource reconciliation, garbage collection, events, and leader
election, see the [configmapsync operator](./examples/configmapsync/README.md) — it
walks through the same building blocks in a complete, runnable operator. To see how to
run several CRDs and controllers from a single operator binary, see
[multicontroller](./examples/multicontroller/README.md).

## Contributing

Expand Down Expand Up @@ -96,19 +176,15 @@ unit tests, integration tests, coverage, release build, docs, and audit.

### Publishing

`publish.sh` handles the full pre-flight and publishes all three crates to crates.io in
dependency order — `koprs-derive`, `koprs`, `koprs-gen`.
`publish.sh` handles the full pre-flight and publishes the crate to crates.io.

```bash
./scripts/publish.sh # full pre-flight + publish all crates
./scripts/publish.sh # full pre-flight + publish
./scripts/publish.sh --dry-run # stop before cargo publish
./scripts/publish.sh --skip-ci # skip CI checks, publish only
./scripts/publish.sh --crate koprs # publish a single crate
```

A 20 second delay is applied between each crate to allow crates.io to index before the
next crate resolves it as a registry dependency.

See the [CI script docs](./scripts/cargo-ci.sh) for the full list of flags.

## License
Expand Down
1 change: 1 addition & 0 deletions crates/crates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
crates
8 changes: 0 additions & 8 deletions crates/koprs-derive/.gitignore

This file was deleted.

13 changes: 0 additions & 13 deletions crates/koprs-derive/Cargo.toml

This file was deleted.

36 changes: 0 additions & 36 deletions crates/koprs-derive/README.md

This file was deleted.

Loading
Loading