Expand description
§kopiur-api
Strongly-typed CRD definitions and shared, controller-free logic for Kopiur, the Kopia-native Kubernetes backup operator (ADR-0003).
§Role in the workspace
This crate holds the 7 CRD types in API group kopiur.home-operations.com,
version v1alpha1 — Repository (ns), ClusterRepository (cluster),
BackupConfig, Backup, BackupSchedule, Restore, and
Maintenance — together with the shared pure logic every consumer needs:
validation (validate), identity resolution (resolve_identity), schedule
jitter (jitter), and GFS retention (select_kept).
It deliberately has no controller-runtime dependencies — no kube::Client,
no tokio. Downstream tools (a custom backup-triggering controller, a CI linter
for BackupConfig manifests, a dashboard) can depend on the API types and shared
logic alone, without pulling in the async runtime or the cluster client
(ADR §5.1). The webhook and the controller both import the same validate/
identity/retention functions, so validation and resolution behave identically
across call sites (“one validator, two callers”).
§The load-bearing idea: type-safety end-to-end (ADR §5.5)
Every discriminated union in the CRD surface is a Rust enum:
Backend, AllowedNamespaces, DeletionPolicy,
RestoreSource, Hook, and friends.
A deserialized value is always exactly one variant — an invalid “two backends
at once” or “no backend” state is unrepresentable — and reconcilers match
exhaustively. A new variant added later cannot compile until every handler
accounts for it. For backup software, where a silently-unhandled case can lose
user data, this eliminates the highest-severity class of “controller silently
dropped data” bugs. This is the whole reason Kopiur is Rust and not Go; preserve
this property in every change (prefer an enum + exhaustive match over
if let / _ => catch-alls).
§Key types
| Type | Purpose |
|---|---|
Repository / ClusterRepository | The kopia repository as a first-class resource (namespaced / cluster-scoped). |
Backend | The storage backend union (s3, azure, gcs, b2, filesystem, sftp, webDav, rclone). |
BackupConfig | The backup recipe (sources, retention, hooks). |
Backup | A single backup invocation, owning its snapshot via finalizer. |
BackupSchedule | The schedule that emits Backups on a cron. |
Restore | A restore request (RestoreSource / RestoreTarget). |
Maintenance | kopia maintenance as a first-class, default-managed concern. |
DeletionPolicy | Delete / Retain / Orphan — ties snapshot lifecycle to the CR. |
Shared pure logic (no controller deps):
| Function | Purpose |
|---|---|
resolve_identity | Render the kopia username@hostname:path identity, pinned to status at admission. |
select_kept | GFS retention: decide which backups to keep. |
jitter_offset / substitute_h | Deterministic H/jitter from (scheduleUID, slot). |
§Conventions
Before editing this crate, read docs/dev/api-conventions.md. The load-bearing
rules:
- Discriminated unions are externally-tagged enums (
backend: { s3: {...} }), not#[serde(tag = "...")]— internally-tagged enums break Kubernetes structural-schema generation. - No
Eqon structs that embedk8s-openapitypes (LabelSelector,ResourceRequirements,SecurityContext, …) — they arePartialEqonly. - Sub-objects, not leaf fields, for every credential/policy/identity/schedule surface, so future fields slot in without API breakage.
§Usage
Construct or deserialize a Backend the way the API server does (JSON value →
typed) and match it exhaustively:
use kopiur_api::Backend;
// External tagging: the variant key selects exactly one backend.
let backend: Backend = serde_json::from_value(serde_json::json!({
"s3": { "bucket": "my-backups", "region": "us-east-1" }
}))
.unwrap();
// A deserialized Backend is always exactly one variant -> exhaustive match.
let summary = match &backend {
Backend::S3(s3) => format!("s3://{}", s3.bucket),
Backend::Azure(_) => "azure".into(),
Backend::Gcs(_) => "gcs".into(),
Backend::B2(_) => "b2".into(),
Backend::Filesystem(_) => "filesystem".into(),
Backend::Sftp(_) => "sftp".into(),
Backend::WebDav(_) => "webdav".into(),
Backend::Rclone(_) => "rclone".into(),
};
assert_eq!(summary, "s3://my-backups");
// The stable discriminant is independent of the camelCase wire key.
assert_eq!(backend.kind_str(), "S3");Note: deserialize via
serde_json(the API-server path), neverserde_yamldirectly into a typed value — serde_yaml 0.9 mis-encodes externally-tagged enums. For YAML tests, go YAML →serde_json::Value→ typed.
§See also
- ADR-0003 — the canonical source of truth for the CRD surface, UX, and design.
docs/dev/api-conventions.md— how to encode the ADR’s fields in Rust.
Re-exports§
pub use backend::Backend;pub use backup::Backup;pub use backup::BackupPhase;pub use backup::BackupSpec;pub use backup::BackupStats;pub use backup::BackupStatus;pub use backup::BackupTiming;pub use backup::Origin;pub use backup_config::BackupConfig;pub use backup_config::BackupConfigSpec;pub use backup_config::BackupConfigStatus;pub use backup_config::CopyMethod;pub use backup_config::GroupBy;pub use backup_config::Hook;pub use backup_config::SourcePathStrategy;pub use backup_schedule::BackupSchedule;pub use backup_schedule::BackupScheduleSpec;pub use backup_schedule::BackupScheduleStatus;pub use backup_schedule::ConcurrencyPolicy;pub use backup_schedule::ScheduleSpec;pub use cluster_repository::AllowedNamespaces;pub use cluster_repository::ClusterRepository;pub use cluster_repository::ClusterRepositorySpec;pub use cluster_repository::ClusterRepositoryStatus;pub use cluster_repository::IdentityTemplate;pub use common::ConfigRef;pub use common::CronSpec;pub use common::DeletionPolicy;pub use common::ObjectRef;pub use common::PhaseLabel;pub use maintenance::Maintenance;pub use maintenance::MaintenanceSchedule;pub use maintenance::MaintenanceSpec;pub use maintenance::MaintenanceStatus;pub use maintenance::Ownership;pub use maintenance::RepositoryMaintenanceSpec;pub use maintenance::TakeoverPolicy;pub use maintenance::default_maintenance_schedule;pub use repository::Repository;pub use repository::RepositoryPhase;pub use repository::RepositorySpec;pub use repository::RepositoryStatus;pub use restore::OnMissingSnapshot;pub use restore::Restore;pub use restore::RestorePhase;pub use restore::RestoreSource;pub use restore::RestoreSpec;pub use restore::RestoreStatus;pub use restore::RestoreTarget;pub use error::ValidationError;pub use error::ValidationResult;pub use identity::IdentityInputs;pub use identity::identity_string;pub use identity::resolve_identity;pub use jitter::offset as jitter_offset;pub use jitter::substitute_h;pub use retention::BackupLike;pub use retention::KeptSet;pub use retention::select_kept;
Modules§
- backend
- Storage backends for a kopia repository.
- backup
- The
BackupCRD — a single kopia snapshot as a Kubernetes object. ADR-0001 §3.4, ADR-0003 §4.5. - backup_
config - The
BackupConfigCRD — the recipe. Idempotent; runs nothing on its own. ADR-0001 §3.3, ADR-0003 §4.8. - backup_
schedule - The
BackupScheduleCRD — when a backup runs. CreatesBackupCRs on a cron schedule in theBackupConfig’s namespace. ADR-0001 §3.5, ADR-0003 §4.4. - cluster_
repository - The
ClusterRepositoryCRD — a cluster-scoped, shared kopia repository operated by a platform team. ADR-0001 §3.2, ADR-0003 §3.2. - common
- Shared sub-objects reused across multiple CRDs.
- error
- Typed validation errors shared by the admission webhook and the controller.
- identity
- Kopia identity resolution (ADR §4.2).
- jitter
- Deterministic schedule jitter and Jenkins-style
Hsubstitution (ADR §4.1). - maintenance
- The
MaintenanceCRD — scheduleskopia maintenance runquick + full and manages the ownership lease. At most one per repository. ADR-0001 §3.7. - repository
- The
RepositoryCRD — a namespaced kopia repository. ADR-0003 §3.1. - restore
- The
RestoreCRD — a restore from a snapshot/identity to a PVC, or a passive populator source. ADR-0001 §3.6, ADR-0003 §4.6. - retention
- Grandfather-father-son (GFS) retention selection (ADR §4.4).
- validate
- Cross-field validation the type system can’t express (ADR §2.2 principle 8).