Skip to main content

netplan_types/
lib.rs

1//! netplan-types
2//!
3//! ## Motivation
4//! This crate attempts to map the entire netplan configuration into Rust structs and enums.
5//! The 'layout' is as close to what you'd write in your netplan YAML files.
6//!
7//! The intented use of this crate is to allow for easy editing of the network configuration via the netplan
8//! configuration files from a Rust program.
9//!
10//! Based on the documentation from netplan, which can be found [here](https://netplan.io/reference/)
11//! Please note that I do not check the docs often for updates, if anything is missing or incorrect in the future,
12//! please open an issue or a pull-request so the issue can be addressed.
13//!
14//! ## Features
15//! - `serde`: \[Default\] Add serde support
16//! - `derive_builder` Enable the derive_builder crate for an automatically generated builder pattern API
17//! - `schemars`: Enable the schemars crate for generating a JSON schema from the structs
18
19#[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/// Use the given networking backend for this definition. Currently supported are
72/// networkd and NetworkManager. This property can be specified globally
73/// in network:, for a device type (in e. g. ethernets:) or
74/// for a particular device definition. Default is networkd.
75///
76/// (Since 0.99) The renderer property has one additional acceptable value for vlan
77/// objects (i. e. defined in vlans:): sriov. If a vlan is defined with the
78/// sriov renderer for an SR-IOV Virtual Function interface, this causes netplan to
79/// set up a hardware VLAN filter for it. There can be only one defined per VF.
80#[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/// Takes a boolean, or the special value “route”. When true, the domain
93/// name received from the DHCP server will be used as DNS search domain
94/// over this link, similar to the effect of the Domains= setting. If set
95/// to “route”, the domain name received from the DHCP server will be
96/// used for routing DNS queries only, but not for searching, similar to
97/// the effect of the Domains= setting when the argument is prefixed with
98/// “~”.
99#[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}