1#[cfg(feature = "serde")]
20mod bool;
21
22mod netplan;
23pub use netplan::*;
24
25use std::collections::HashMap;
26
27#[cfg(feature = "serde")]
28use serde::{Deserialize, Serialize};
29
30#[cfg(feature = "derive_builder")]
31use derive_builder::Builder;
32
33#[derive(Default, Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35#[cfg_attr(feature = "derive_builder", derive(Builder))]
36#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
37#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
38pub struct NetplanConfig {
39 pub network: NetworkConfig,
40}
41
42#[derive(Default, Debug, Clone, PartialEq, Eq)]
43#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
44#[cfg_attr(feature = "derive_builder", derive(Builder))]
45#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))]
46#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
47pub struct NetworkConfig {
48 pub version: u8,
49 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
50 pub renderer: Option<Renderer>,
51 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
52 pub ethernets: Option<HashMap<String, EthernetConfig>>,
53 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
54 pub modems: Option<HashMap<String, ModemConfig>>,
55 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
56 pub wifis: Option<HashMap<String, WifiConfig>>,
57 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
58 pub bonds: Option<HashMap<String, BondConfig>>,
59 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
60 pub bridges: Option<HashMap<String, BridgeConfig>>,
61 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
62 pub vlans: Option<HashMap<String, VlanConfig>>,
63 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
64 pub tunnels: Option<HashMap<String, TunnelConfig>>,
65 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
66 pub vrfs: Option<HashMap<String, VrfsConfig>>,
67 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
68 pub dummy_devices: Option<HashMap<String, DummyDeviceConfig>>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
81#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
83pub enum Renderer {
84 #[cfg_attr(feature = "serde", serde(rename = "networkd"))]
85 Networkd,
86 #[cfg_attr(feature = "serde", serde(rename = "NetworkManager"))]
87 NetworkManager,
88 #[cfg_attr(feature = "serde", serde(rename = "sriov"))]
89 Sriov,
90}
91
92#[derive(Debug, Clone, PartialEq, Eq)]
100pub enum UseDomains {
101 Boolean(bool),
102 Route,
103}
104
105#[cfg(feature = "serde")]
106impl Serialize for UseDomains {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: serde::Serializer,
110 {
111 match self {
112 UseDomains::Boolean(b) => serializer.serialize_bool(*b),
113 UseDomains::Route => serializer.serialize_str("route"),
114 }
115 }
116}
117
118#[cfg(feature = "serde")]
119impl<'de> Deserialize<'de> for UseDomains {
120 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
121 where
122 D: serde::Deserializer<'de>,
123 {
124 struct UseDomainsVisitor;
125
126 impl serde::de::Visitor<'_> for UseDomainsVisitor {
127 type Value = UseDomains;
128
129 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
130 formatter.write_str("a boolean, or the string \"route\"")
131 }
132
133 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
134 where
135 E: serde::de::Error,
136 {
137 Ok(UseDomains::Boolean(v))
138 }
139
140 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
141 where
142 E: serde::de::Error,
143 {
144 match v.to_lowercase().as_str() {
145 "true" | "yes" | "on" | "y" => Ok(UseDomains::Boolean(true)),
146 "false" | "no" | "off" | "n" => Ok(UseDomains::Boolean(false)),
147 "route" => Ok(UseDomains::Route),
148 _ => Err(serde::de::Error::unknown_variant(
149 v,
150 &["true", "false", "yes", "no", "on", "off", "y", "n", "route"],
151 )),
152 }
153 }
154 }
155
156 deserializer.deserialize_any(UseDomainsVisitor)
157 }
158}
159
160#[cfg(feature = "schemars")]
161impl schemars::JsonSchema for UseDomains {
162 fn schema_name() -> std::borrow::Cow<'static, str> {
163 "UseDomains".into()
164 }
165
166 fn json_schema(_generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
167 schemars::json_schema!({
168 "anyOf": [
169 { "type": "boolean" },
170 { "type": "string", "enum": ["route"] }
171 ]
172 })
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use crate::NetplanConfig;
179
180 #[test]
181 fn yaml_booleans() {
182 let input = r#"
183 network:
184 version: 2
185 ethernets:
186 nics:
187 match:
188 name: ens*
189 dhcp4: on
190 dhcp6: N
191 "#;
192
193 let netplan_config: NetplanConfig = serde_yaml::from_str(&input).unwrap();
194 let ethernets = netplan_config.network.ethernets.unwrap();
195 let ethernet = ethernets.values().next().unwrap();
196
197 assert!(ethernet.common_all.is_some());
198
199 let common = ethernet.common_all.as_ref().unwrap();
200
201 assert_eq!(common.dhcp4, Some(true));
202 }
203}