1#![warn(missing_docs)]
2#![doc = include_str!("../README.md")]
3
4pub mod backend;
5pub mod backup;
6pub mod backup_config;
7pub mod backup_schedule;
8pub mod cluster_repository;
9pub mod common;
10pub mod maintenance;
11pub mod repository;
12pub mod restore;
13
14pub mod error;
18pub mod identity;
19pub mod jitter;
20pub mod retention;
21pub mod validate;
22
23pub use backend::Backend;
24pub use backup::{
25 Backup, BackupPhase, BackupSpec, BackupStats, BackupStatus, BackupTiming, Origin,
26};
27pub use backup_config::{
28 BackupConfig, BackupConfigSpec, BackupConfigStatus, CopyMethod, GroupBy, Hook,
29 SourcePathStrategy,
30};
31pub use backup_schedule::{
32 BackupSchedule, BackupScheduleSpec, BackupScheduleStatus, ConcurrencyPolicy, ScheduleSpec,
33};
34pub use cluster_repository::{
35 AllowedNamespaces, ClusterRepository, ClusterRepositorySpec, ClusterRepositoryStatus,
36 IdentityTemplate,
37};
38pub use common::{ConfigRef, CronSpec, DeletionPolicy, ObjectRef, PhaseLabel};
39pub use maintenance::{
40 Maintenance, MaintenanceSchedule, MaintenanceSpec, MaintenanceStatus, Ownership,
41 RepositoryMaintenanceSpec, TakeoverPolicy, default_maintenance_schedule,
42};
43pub use repository::{Repository, RepositoryPhase, RepositorySpec, RepositoryStatus};
44pub use restore::{
45 OnMissingSnapshot, Restore, RestorePhase, RestoreSource, RestoreSpec, RestoreStatus,
46 RestoreTarget,
47};
48
49pub use error::{ValidationError, ValidationResult};
51pub use identity::{IdentityInputs, identity_string, resolve_identity};
52pub use jitter::{offset as jitter_offset, substitute_h};
53pub use retention::{BackupLike, KeptSet, select_kept};
54
55pub const GROUP: &str = "kopiur.home-operations.com";
57pub const VERSION: &str = "v1alpha1";
59
60#[cfg(test)]
68pub(crate) mod testutil {
69 pub(crate) fn from_yaml<T: serde::de::DeserializeOwned>(yaml: &str) -> T {
70 let value: serde_json::Value = serde_yaml::from_str(yaml).expect("yaml -> json value");
71 serde_json::from_value(value).expect("json value -> typed")
72 }
73}
74
75#[cfg(test)]
76mod roundtrip_tests {
77 use super::*;
80 use crate::testutil::from_yaml;
81 use kube::core::CustomResourceExt;
82
83 #[test]
84 fn repository_crd_metadata_is_correct() {
85 let crd = Repository::crd();
86 assert_eq!(crd.spec.group, "kopiur.home-operations.com");
87 assert_eq!(crd.spec.names.kind, "Repository");
88 assert_eq!(crd.spec.scope, "Namespaced");
89 assert_eq!(crd.spec.versions[0].name, "v1alpha1");
90 }
91
92 #[test]
93 fn repository_s3_roundtrip_matches_adr_shape() {
94 let yaml = r#"
96backend:
97 s3:
98 bucket: my-backups
99 prefix: prod/
100 endpoint: s3.us-east-1.amazonaws.com
101 region: us-east-1
102 auth:
103 secretRef:
104 name: nas-primary-creds
105encryption:
106 passwordSecretRef:
107 name: nas-primary-creds
108 key: KOPIA_PASSWORD
109create:
110 enabled: true
111"#;
112 let spec: RepositorySpec = from_yaml(yaml);
113 match &spec.backend {
115 Backend::S3(s3) => {
116 assert_eq!(s3.bucket, "my-backups");
117 assert_eq!(s3.prefix.as_deref(), Some("prod/"));
118 }
119 other => panic!("expected S3 backend, got {}", other.kind_str()),
120 }
121 let json = serde_json::to_value(&spec).expect("serialize");
123 let reparsed: RepositorySpec = serde_json::from_value(json).expect("reparse");
124 assert_eq!(spec, reparsed);
125 }
126
127 #[test]
128 fn backend_is_externally_tagged() {
129 let spec: RepositorySpec = from_yaml(
130 "backend:\n filesystem:\n path: /repo\nencryption:\n passwordSecretRef:\n name: s\n",
131 );
132 assert_eq!(spec.backend.kind_str(), "Filesystem");
133 let v = serde_json::to_value(&spec.backend).unwrap();
134 assert_eq!(v["filesystem"]["path"], "/repo");
135 }
136
137 #[test]
138 fn unknown_backend_variant_is_rejected() {
139 let value: serde_json::Value = serde_yaml::from_str("dropbox:\n bucket: x\n").unwrap();
140 let err = serde_json::from_value::<Backend>(value);
141 assert!(
142 err.is_err(),
143 "unknown backend variant must fail to deserialize"
144 );
145 }
146}