1#[cfg(feature = "client")]
2mod client;
3
4#[cfg(feature = "client")]
5pub use client::{Client, Error};
6
7#[cfg(feature = "provider")]
8mod provider;
9use kubizone_common::{DomainName, Type};
10#[cfg(feature = "provider")]
11pub use provider::{serve, Provider};
12
13use serde::{Deserialize, Serialize};
14use std::collections::{HashMap, HashSet};
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(rename_all = "camelCase")]
18struct DomainFilter {
19 #[serde(default)]
20 pub filters: Vec<String>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
25#[serde(rename_all = "camelCase")]
26pub struct EndpointIdent {
27 pub dns_name: DomainName,
28 pub record_type: Type,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(rename_all = "camelCase")]
34pub struct Endpoint {
35 #[serde(flatten)]
37 pub identity: EndpointIdent,
38
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub set_identifier: Option<String>,
41
42 pub targets: Vec<String>,
45
46 #[serde(default, rename = "recordTTL", skip_serializing_if = "Option::is_none")]
48 pub record_ttl: Option<i64>,
49
50 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
53 pub labels: HashMap<String, String>,
54
55 #[serde(default, skip_serializing_if = "Vec::is_empty")]
57 pub provider_specific: Vec<ProviderSpecificProperty>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
62#[serde(rename_all = "camelCase")]
63pub struct ProviderSpecificProperty {
64 pub name: String,
66
67 pub value: String,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72#[serde(rename_all = "camelCase")]
73struct Changes {
74 pub create: Vec<Endpoint>,
75 pub update_old: Vec<Endpoint>,
76 pub update_new: Vec<Endpoint>,
77 pub delete: Vec<Endpoint>,
78}
79
80#[derive(Debug, Clone, PartialEq, Eq)]
82pub enum Change {
83 Update {
85 old: Endpoint,
87 new: Endpoint,
88 },
89
90 Delete(Endpoint),
92
93 Create(Endpoint),
95}
96
97impl From<Changes> for Vec<Change> {
98 fn from(changes: Changes) -> Self {
99 let mut out = Vec::new();
100
101 for endpoint in changes.delete {
102 out.push(Change::Delete(endpoint));
103 }
104
105 for old in changes.update_old {
106 if let Some(new) = changes
107 .update_new
108 .iter()
109 .find(|new| new.identity == old.identity)
110 .cloned()
111 {
112 out.push(Change::Update { old, new })
113 }
114 }
115
116 for endpoint in changes.create {
117 out.push(Change::Create(endpoint))
118 }
119
120 out
121 }
122}
123
124impl From<Vec<Change>> for Changes {
125 fn from(value: Vec<Change>) -> Self {
126 let mut out = Changes {
127 create: vec![],
128 update_old: vec![],
129 update_new: vec![],
130 delete: vec![],
131 };
132
133 for change in value {
134 match change {
135 Change::Update { old, new } => {
136 out.update_old.push(old);
137 out.update_new.push(new);
138 }
139 Change::Delete(endpoint) => out.delete.push(endpoint),
140 Change::Create(endpoint) => out.create.push(endpoint),
141 }
142 }
143
144 out
145 }
146}
147
148pub trait EndpointDiff {
150 fn difference(self, other: Self) -> Vec<Change>;
158}
159
160impl EndpointDiff for Vec<Endpoint> {
161 fn difference(self, other: Self) -> Vec<Change> {
162 let old: HashMap<EndpointIdent, Endpoint> = HashMap::from_iter(
163 self.into_iter()
164 .map(|endpoint| (endpoint.identity.clone(), endpoint)),
165 );
166 let new: HashMap<EndpointIdent, Endpoint> = HashMap::from_iter(
167 other
168 .into_iter()
169 .map(|endpoint| (endpoint.identity.clone(), endpoint)),
170 );
171
172 let old_keys: HashSet<_> = old.keys().collect();
173 let new_keys: HashSet<_> = new.keys().collect();
174
175 let creates = new_keys
176 .difference(&old_keys)
177 .filter_map(|identity| new.get(identity))
178 .cloned()
179 .map(Change::Create);
180
181 let deletes = old_keys
182 .difference(&new_keys)
183 .filter_map(|identity| old.get(identity))
184 .cloned()
185 .map(Change::Delete);
186
187 let updates = old_keys.intersection(&new_keys).filter_map(|identity| {
188 let old = old.get(identity)?.clone();
189 let new = new.get(identity)?.clone();
190
191 if old == new {
192 return None;
193 }
194
195 Some(Change::Update { old, new })
196 });
197
198 deletes.into_iter().chain(updates).chain(creates).collect()
199 }
200}
201
202#[cfg(test)]
203#[test]
204fn difference_calculation() {
205 let a = vec![
206 Endpoint {
207 identity: EndpointIdent {
208 dns_name: DomainName::try_from("update.org.").unwrap(),
209 record_type: Type::A,
210 },
211 set_identifier: None,
212 targets: vec!["192.168.0.1".to_string()],
213 record_ttl: Some(300),
214 labels: HashMap::default(),
215 provider_specific: Vec::new(),
216 },
217 Endpoint {
218 identity: EndpointIdent {
219 dns_name: DomainName::try_from("delete.org.").unwrap(),
220 record_type: Type::A,
221 },
222 set_identifier: None,
223 targets: vec!["192.168.0.1".to_string()],
224 record_ttl: Some(300),
225 labels: HashMap::default(),
226 provider_specific: Vec::new(),
227 },
228 ];
229
230 let b = vec![
231 Endpoint {
232 identity: EndpointIdent {
233 dns_name: DomainName::try_from("update.org.").unwrap(),
234 record_type: Type::A,
235 },
236 set_identifier: None,
237 targets: vec!["192.168.0.2".to_string()],
238 record_ttl: Some(300),
239 labels: HashMap::default(),
240 provider_specific: Vec::new(),
241 },
242 Endpoint {
243 identity: EndpointIdent {
244 dns_name: DomainName::try_from("create.org.").unwrap(),
245 record_type: Type::A,
246 },
247 set_identifier: None,
248 targets: vec!["192.168.0.1".to_string()],
249 record_ttl: Some(300),
250 labels: HashMap::default(),
251 provider_specific: Vec::new(),
252 },
253 ];
254
255 let changes = a.difference(b);
256
257 assert_eq!(
258 changes,
259 vec![
260 Change::Delete(Endpoint {
261 identity: EndpointIdent {
262 dns_name: DomainName::try_from("delete.org.").unwrap(),
263 record_type: Type::A,
264 },
265 set_identifier: None,
266 targets: vec!["192.168.0.1".to_string()],
267 record_ttl: Some(300),
268 labels: HashMap::default(),
269 provider_specific: Vec::new(),
270 }),
271 Change::Update {
272 old: Endpoint {
273 identity: EndpointIdent {
274 dns_name: DomainName::try_from("update.org.").unwrap(),
275 record_type: Type::A,
276 },
277 set_identifier: None,
278 targets: vec!["192.168.0.1".to_string()],
279 record_ttl: Some(300),
280 labels: HashMap::default(),
281 provider_specific: Vec::new(),
282 },
283 new: Endpoint {
284 identity: EndpointIdent {
285 dns_name: DomainName::try_from("update.org.").unwrap(),
286 record_type: Type::A,
287 },
288 set_identifier: None,
289 targets: vec!["192.168.0.2".to_string()],
290 record_ttl: Some(300),
291 labels: HashMap::default(),
292 provider_specific: Vec::new(),
293 }
294 },
295 Change::Create(Endpoint {
296 identity: EndpointIdent {
297 dns_name: DomainName::try_from("create.org.").unwrap(),
298 record_type: Type::A,
299 },
300 set_identifier: None,
301 targets: vec!["192.168.0.1".to_string()],
302 record_ttl: Some(300),
303 labels: HashMap::default(),
304 provider_specific: Vec::new(),
305 })
306 ]
307 )
308}