[workspace] # The thin CLI crate `kobectl` (binary `kobe`) is a separate member so it can # be published to crates.io with a slim dependency tree. The operator stays the # root package (it is the bulk of the code and what the container images build). members = ["crates/kobectl", "crates/kobe-runner", "crates/kobe-state-machine"] resolver = "3" # Single source of truth for the release version, shared by the operator and the # CLI so they always publish in lockstep. `just bump X.Y.Z` rewrites this; the # version-consistency gate enforces tag == this == Chart.yaml at release time. [workspace.package] version = "0.61.1" edition = "2024" rust-version = "1.94.1" license = "Apache-2.0" # Strip debug symbols at build time. Without this, `cargo deb` (the build_deb # step in _release-rust.yml) tries to SEPARATE debug symbols into a -dbgsym # package, which shells out to `strip` — not on the deb runner's PATH / not the # cross-target strip — and fails with "can't separate debug symbols". Stripping # in the profile makes cargo-deb skip that step. Mirrors kache. [profile.release] strip = "symbols" [package] name = "kobe-operator" version.workspace = true edition.workspace = true rust-version.workspace = true license.workspace = true description = "Cluster pool operator for instant CI/dev cluster provisioning (k3k, k3s, k0s, CAPI)" [[bin]] name = "kobe-operator" path = "src/main.rs" [[bin]] name = "kobe-sync" path = "src/kobe_sync_bin.rs" [[bin]] name = "crdgen" path = "src/crdgen.rs" [[bin]] name = "kubeconfig-publisher" path = "src/kubeconfig_publisher_bin.rs" [[bin]] name = "kobe-host-reaper" path = "src/host_reaper_bin.rs" [dependencies] # Kubernetes — kube 3.0 requires k8s-openapi 0.27 + schemars 1.x kube = { version = "4.0", features = ["runtime", "derive", "client", "ws"] } # Direct dependency purely to turn on one unstable kube-runtime feature. The # `kube` umbrella only exposes the whole `unstable-runtime` set, and features are # additive, so naming `kube-runtime` here enables exactly `reconcile_on` and # nothing else. It is the only way to feed a controller reconcile requests from a # stream it did not build: child-cluster watches live in another apiserver and # are discovered per lease. The stable alternative, `reconcile_all_on`, would # wake every SandboxLease on every child event. kube-runtime = { version = "4.0", default-features = false, features = [ "unstable-runtime-reconcile-on", ] } k8s-openapi = { version = "0.28", features = ["v1_32", "schemars"] } # Web framework axum = { version = "0.8", features = ["json", "matched-path", "ws"] } tower = "0.5" tower-http = { version = "0.6", features = ["cors", "trace"] } # Async runtime tokio = { version = "1", features = ["full"] } tokio-util = "0.7" futures = "0.3" # Serialization serde = { version = "1", features = ["derive"] } serde_json = "1" serde_yaml_ng = "0.10" toml = "0.8" json-patch = "4" kobe-state-machine = { path = "crates/kobe-state-machine" } sha2 = "0.10" # Sandbox attach query parsing: repeated `command=` keys must collect into # argv, which axum's Query (serde_urlencoded) cannot express. form_urlencoded = "1.2" # Auth # Direct use is only `Algorithm`. Same major as kunobi-auth so the tree # does not carry jsonwebtoken 9 and 10 side by side. default-features # off: jsonwebtoken 10 panics if both crypto backends are compiled in. # `rust_crypto` comes from kunobi-auth's defaults. jsonwebtoken = { version = "11", default-features = false, features = ["use_pem"] } # Unify with kunobi-auth's reqwest 0.13. The old `rustls-tls` feature # was renamed; `rustls-no-provider` keeps ring as our process provider. reqwest = { version = "0.13", features = ["json", "rustls-no-provider", "stream"], default-features = false } # AWS S3 for diagnostics # `default-features = false` to drop the SDK's `rustls` feature, which is not # the modern TLS stack it sounds like: it selects the legacy hyper-0.14 # connector, dragging in rustls 0.21 (and rustls-webpki 0.101) alongside the # rustls 0.23 everything else already uses, plus h2 0.3. That second stack was # the source of four of the five advisories `cargo audit` reported, none of # which had a fix available on those lines. Nothing here builds a custom # connector — S3 is used once, through `aws_config::load_defaults` — so the # default HTTPS client is the only one that was ever exercised. aws-sdk-s3 = { version = "1", default-features = false, features = [ "default-https-client", "rt-tokio", "sigv4a", ] } aws-config = { version = "1", features = ["behavior-version-latest"] } # Observability tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json", "registry"] } prometheus = "0.14" opentelemetry = "0.31" opentelemetry_sdk = { version = "0.31", features = ["rt-tokio"] } opentelemetry-otlp = { version = "0.31", features = ["grpc-tonic"] } tracing-opentelemetry = "0.32" # PostgreSQL for direct-k3s shared datastore sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "postgres"] } # kobe-sync: reverse proxy # `client` feature is needed by the upgrade tunnel — we use # `hyper::client::conn::http1::handshake().with_upgrades()` to talk to the # host apiserver for exec/attach/portforward, since reqwest doesn't expose # the underlying connection post-101 Switching Protocols. hyper = { version = "1", features = ["http1", "http2", "server", "client"] } hyper-util = { version = "0.1", features = ["tokio", "server-auto"] } http-body-util = "0.1" bytes = "1" # kobe-sync: TLS and certificates rcgen = { version = "0.13", features = ["x509-parser"] } time = "0.3" rustls = { version = "0.23", default-features = false, features = ["ring"] } tokio-rustls = "0.26" rustls-pemfile = "2" # Used by the proxy to extract identity (Subject CN/O) from the peer's # client certificate, so it can be forwarded to the local apiserver as # X-Remote-User / X-Remote-Group impersonation headers (front-proxy # pattern). Also used in tests to assert SANs on generated apiserver # serving certs. x509-parser = "0.16" # Base64 encoding (used for kubeconfig generation) base64 = "0.22" # Async trait (dyn dispatch for async traits) async-trait = "0.1" # Operator-side kunobi libs + CLI plumbing. (The interactive `kobe` CLI moved to # the `crates/kobectl` member; `ratatui`/`crossterm` went with it — the operator # uses none of the TUI crates.) kunobi-auth = "0.11.1" kunobi-ha = "0.6.0" # Hot-reload the PostgreSQL credential from a mounted Secret on rotation (#91). kunobi-reload = { git = "https://github.com/kunobi-ninja/kunobi-reload.git", branch = "main" } clap = { version = "4", features = ["derive"] } dirs = "6" # Utilities anyhow = "1" thiserror = "2" chrono = { version = "0.4", features = ["serde"] } uuid = { version = "1", features = ["v4"] } schemars = "1.2" rand = "0.9" hex = "0.4" url = "2" http = "1" libc = "0.2" regex = "1" # P2P data-plane transport for Sandbox attach/exec/port-forward (#197). # Compiled into the binary unconditionally; activated per-pool at runtime via # `SandboxPool.spec.transport`. Public relay by default, overridable. iroh = "1" # The wire contract with the in-container Sandbox runner (#82). A path # dependency on the runner crate rather than a second copy of the types: two # hand-maintained definitions of a wire format drift, and the drift shows up as # a MISREAD reply — a caller told their command succeeded because a field moved. kobe-runner = { path = "crates/kobe-runner" } [dev-dependencies] tokio-test = "0.4" # The conformance suite drives the public HTTP API, including a blocking # release from a `Drop` — a leaked sandbox makes the NEXT scenario queue and # fail for an unrelated reason. reqwest = { version = "0.13", features = [ "json", "rustls-no-provider", "blocking", ], default-features = false } wiremock = "0.6" tempfile = "3"