use crate::estate::{Iface, Rule};
use serde_json::{json, Value};
pub fn plans() -> Value {
let rows: Vec<Value> = crate::render::PLANS
.iter()
.map(|p| {
json!({
"name": p,
"core_number": crate::render::cores_of(p),
"memory_amount": crate::render::memory_of(p),
"public_traffic_out": 2048,
"storage_size": 25,
"storage_tier": "maxiops",
})
})
.collect();
json!({ "plans": { "plan": rows } })
}
pub fn interfaces_from_body(server: &Value) -> Vec<Iface> {
let arr = server["networking"]["interfaces"]["interface"].as_array().cloned().unwrap_or_default();
if arr.is_empty() {
return Iface::default_pair();
}
arr.iter()
.enumerate()
.map(|(i, v)| Iface {
index: v["index"].as_u64().unwrap_or(i as u64 + 1) as u32,
kind: v["type"].as_str().unwrap_or("public").to_string(),
family: v["ip_addresses"]["ip_address"][0]["family"].as_str().unwrap_or("IPv4").to_string(),
})
.collect()
}
pub fn rules_from_body(body: &Value) -> Vec<Rule> {
let arr: Vec<Value> = match body["firewall_rules"]["firewall_rule"].as_array() {
Some(a) => a.clone(),
None => match body["firewall_rule"].as_object() {
Some(_) => vec![body["firewall_rule"].clone()],
None => vec![],
},
};
arr.iter()
.enumerate()
.map(|(i, v)| Rule {
position: v["position"].as_str().map(str::to_string).unwrap_or_else(|| (i + 1).to_string()),
direction: s(v, "direction", "in"),
action: s(v, "action", "drop"),
family: s(v, "family", "IPv4"),
protocol: s(v, "protocol", ""),
source_address_start: s(v, "source_address_start", ""),
source_address_end: s(v, "source_address_end", ""),
source_port_start: s(v, "source_port_start", ""),
source_port_end: s(v, "source_port_end", ""),
destination_address_start: s(v, "destination_address_start", ""),
destination_address_end: s(v, "destination_address_end", ""),
destination_port_start: s(v, "destination_port_start", ""),
destination_port_end: s(v, "destination_port_end", ""),
icmp_type: s(v, "icmp_type", ""),
comment: s(v, "comment", ""),
})
.collect()
}
fn s(v: &Value, key: &str, default: &str) -> String {
match &v[key] {
Value::String(x) => x.clone(),
Value::Number(n) => n.to_string(),
_ => default.to_string(),
}
}
pub fn render_rule(r: &Rule) -> Value {
json!({
"position": r.position,
"direction": r.direction,
"action": r.action,
"family": r.family,
"protocol": r.protocol,
"source_address_start": r.source_address_start,
"source_address_end": r.source_address_end,
"source_port_start": r.source_port_start,
"source_port_end": r.source_port_end,
"destination_address_start": r.destination_address_start,
"destination_address_end": r.destination_address_end,
"destination_port_start": r.destination_port_start,
"destination_port_end": r.destination_port_end,
"icmp_type": r.icmp_type,
"comment": r.comment,
})
}
pub fn render_rules(rules: &[Rule]) -> Value {
json!({ "firewall_rules": { "firewall_rule": rules.iter().map(render_rule).collect::<Vec<_>>() } })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_advertised_plan_is_a_plan_the_create_accepts() {
let p = plans();
let rows = p["plans"]["plan"].as_array().expect("plans").clone();
assert!(!rows.is_empty());
for r in rows {
let name = r["name"].as_str().expect("a plan has a name");
assert!(crate::render::plan_known(name), "{name} is advertised and would be refused");
}
}
#[test]
fn a_rule_reads_the_same_from_either_spelling() {
let one = json!({"firewall_rule": {"action":"accept","direction":"in","protocol":"tcp",
"destination_port_start": 443, "destination_port_end":"443","comment":"HTTPS"}});
let bulk = json!({"firewall_rules": {"firewall_rule": [one["firewall_rule"].clone()]}});
let a = rules_from_body(&one);
let b = rules_from_body(&bulk);
assert_eq!(a.len(), 1);
assert_eq!(a[0].comment, "HTTPS");
assert_eq!(a[0].destination_port_start, "443");
assert_eq!(a[0].destination_port_start, b[0].destination_port_start);
assert_eq!(a[0].position, "1");
}
#[test]
fn an_absent_networking_block_is_the_old_pair() {
assert_eq!(interfaces_from_body(&json!({})), Iface::default_pair());
let tf = json!({"networking": {"interfaces": {"interface": [
{"index": 1, "type": "public"}, {"index": 2, "type": "utility"}]}}});
let got = interfaces_from_body(&tf);
assert_eq!(got.len(), 2);
assert_eq!(got[1].kind, "utility");
}
}