1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use std::collections::BTreeMap;
use serde::Deserialize;
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ObjectMeta {
#[serde(default)]
pub name: String,
#[serde(default)]
pub annotations: BTreeMap<String, String>,
#[serde(default)]
pub resource_version: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
pub struct NodeSpec {
#[serde(rename = "podCIDR", default)]
pub pod_cidr: Option<String>,
#[serde(rename = "podCIDRs", default)]
pub pod_cidrs: Vec<String>,
}
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)]
pub struct Node {
#[serde(default)]
pub metadata: ObjectMeta,
#[serde(default)]
pub spec: NodeSpec,
}
impl Node {
pub fn pod_cidr_strings(&self) -> Vec<String> {
if !self.spec.pod_cidrs.is_empty() {
return self.spec.pod_cidrs.clone();
}
self.spec.pod_cidr.iter().cloned().collect()
}
pub fn annotation(&self, key: &str) -> Option<&str> {
self.metadata.annotations.get(key).map(String::as_str)
}
}
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ListMeta {
#[serde(default)]
pub resource_version: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize)]
pub struct NodeList {
#[serde(default)]
pub metadata: ListMeta,
#[serde(default)]
pub items: Vec<Node>,
}
#[derive(Debug, Deserialize)]
pub struct WatchEvent {
#[serde(rename = "type")]
pub kind: String,
pub object: serde_json::Value,
}
#[derive(Clone, Debug, Default)]
pub struct NodeSet {
nodes: BTreeMap<String, Node>,
}
impl NodeSet {
pub fn from_list(list: NodeList) -> NodeSet {
let mut set = NodeSet::default();
for node in list.items {
set.upsert(node);
}
set
}
pub fn upsert(&mut self, node: Node) {
self.nodes.insert(node.metadata.name.clone(), node);
}
pub fn remove(&mut self, name: &str) {
self.nodes.remove(name);
}
pub fn get(&self, name: &str) -> Option<&Node> {
self.nodes.get(name)
}
pub fn to_vec(&self) -> Vec<Node> {
self.nodes.values().cloned().collect()
}
pub fn len(&self) -> usize {
self.nodes.len()
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_kubectl_node_list_parses_with_pod_cidrs_and_annotations() {
let json = r#"{
"apiVersion": "v1", "kind": "NodeList",
"metadata": {"resourceVersion": "42"},
"items": [
{"metadata": {"name": "a", "resourceVersion": "7",
"annotations": {"socialweb.computer/mesh-endpoint": "ab"}},
"spec": {"podCIDR": "10.42.1.0/24", "podCIDRs": ["10.42.1.0/24", "fd00:42:1::/64"]}},
{"metadata": {"name": "b"}, "spec": {"podCIDR": "10.42.2.0/24"}},
{"metadata": {"name": "c"}, "spec": {}}
]}"#;
let list: NodeList = serde_json::from_str(json).unwrap();
assert_eq!(list.metadata.resource_version.as_deref(), Some("42"));
let set = NodeSet::from_list(list);
assert_eq!(set.len(), 3);
let a = set.get("a").unwrap();
assert_eq!(a.pod_cidr_strings(), vec!["10.42.1.0/24", "fd00:42:1::/64"]);
assert_eq!(a.annotation("socialweb.computer/mesh-endpoint"), Some("ab"));
assert_eq!(
set.get("b").unwrap().pod_cidr_strings(),
vec!["10.42.2.0/24"]
);
assert!(set.get("c").unwrap().pod_cidr_strings().is_empty());
}
#[test]
fn a_watch_event_line_parses() {
let line = r#"{"type":"MODIFIED","object":{"kind":"Node","metadata":{"name":"a"}}}"#;
let event: WatchEvent = serde_json::from_str(line).unwrap();
assert_eq!(event.kind, "MODIFIED");
let node: Node = serde_json::from_value(event.object).unwrap();
assert_eq!(node.metadata.name, "a");
}
}