Merge ~jocave/checkbox-support:add-netplan-parser into checkbox-support:master

Proposed by Jonathan Cave
Status: Merged
Approved by: Jonathan Cave
Approved revision: ad173b07ad9e07be4d0e6e5c992f4edd522a33f7
Merged at revision: 7dd870f11e085126d5966725a6bfd9d0e0abb235
Proposed branch: ~jocave/checkbox-support:add-netplan-parser
Merge into: checkbox-support:master
Diff against target: 165 lines (+159/-0)
1 file modified
checkbox_support/parsers/netplan.py (+159/-0)
Reviewer Review Type Date Requested Status
Maciej Kisielewski Approve
Review via email: mp+368621@code.launchpad.net

Description of the change

Add a netplan config file parser taken from the upstream project. This is a simple read-only parser at the moment that collates information as it reads the files in order of precedence.

It may be necessary in the future to expand on this so we can support reading a configuration hierarchy, modify and then write out.

To post a comment you must log in.
Revision history for this message
Maciej Kisielewski (kissiel) wrote :

+1

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/checkbox_support/parsers/netplan.py b/checkbox_support/parsers/netplan.py
2new file mode 100644
3index 0000000..ff49cbf
4--- /dev/null
5+++ b/checkbox_support/parsers/netplan.py
6@@ -0,0 +1,159 @@
7+# This file is based on the parser from the netplan project:
8+# https://github.com/CanonicalLtd/netplan/blob/master/netplan/configmanager.py
9+#
10+# Copyright 2019 Canonical Ltd.
11+#
12+# Authors:
13+# Mathieu Trudel-Lapierre <mathieu.trudel-lapierre@canonical.com>
14+# Jonathan Cave <jonathan.cave@canonical.com>
15+#
16+# Checkbox is free software: you can redistribute it and/or modify
17+# it under the terms of the GNU General Public License version 3,
18+# as published by the Free Software Foundation.
19+#
20+# Checkbox is distributed in the hope that it will be useful,
21+# but WITHOUT ANY WARRANTY; without even the implied warranty of
22+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23+# GNU General Public License for more details.
24+#
25+# You should have received a copy of the GNU General Public License
26+# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
27+
28+import glob
29+import logging
30+import os
31+import yaml
32+
33+
34+class Netplan():
35+
36+ def __init__(self, prefix="/"):
37+ self.prefix = prefix
38+ self.config = {}
39+
40+ @property
41+ def network(self):
42+ return self.config['network']
43+
44+ @property
45+ def interfaces(self):
46+ interfaces = {}
47+ interfaces.update(self.ethernets)
48+ interfaces.update(self.wifis)
49+ interfaces.update(self.bridges)
50+ interfaces.update(self.bonds)
51+ interfaces.update(self.vlans)
52+ return interfaces
53+
54+ @property
55+ def physical_interfaces(self):
56+ interfaces = {}
57+ interfaces.update(self.ethernets)
58+ interfaces.update(self.wifis)
59+ return interfaces
60+
61+ @property
62+ def ethernets(self):
63+ return self.network['ethernets']
64+
65+ @property
66+ def wifis(self):
67+ return self.network['wifis']
68+
69+ @property
70+ def bridges(self):
71+ return self.network['bridges']
72+
73+ @property
74+ def bonds(self):
75+ return self.network['bonds']
76+
77+ @property
78+ def vlans(self):
79+ return self.network['vlans']
80+
81+ @property
82+ def renderer(self):
83+ return self.network['renderer']
84+
85+ def parse(self):
86+ """
87+ Parse all our config files to return an object that describes the
88+ system's entire configuration, so that it can later be interrogated.
89+ Returns a dict that contains the entire, collated and merged YAML.
90+ """
91+
92+ # /run/netplan shadows /etc/netplan/, which shadows /lib/netplan
93+ names_to_paths = {}
94+ for yaml_dir in ['lib', 'etc', 'run']:
95+ for yaml_file in glob.glob(os.path.join(
96+ self.prefix, yaml_dir, 'netplan', '*.yaml')):
97+ names_to_paths[os.path.basename(yaml_file)] = yaml_file
98+
99+ files = [names_to_paths[name]
100+ for name in sorted(names_to_paths.keys())]
101+
102+ self.config['network'] = {
103+ 'ethernets': {},
104+ 'wifis': {},
105+ 'bridges': {},
106+ 'bonds': {},
107+ 'vlans': {},
108+ 'renderer': None
109+ }
110+ for yaml_file in files:
111+ self._merge_yaml_config(yaml_file)
112+
113+ def _merge_interface_config(self, orig, new):
114+ new_interfaces = set()
115+ changed_ifaces = list(new.keys())
116+
117+ for ifname in changed_ifaces:
118+ iface = new.pop(ifname)
119+ if ifname in orig:
120+ logging.debug("{} exists in {}".format(ifname, orig))
121+ orig[ifname].update(iface)
122+ else:
123+ logging.debug("{} not found in {}".format(ifname, orig))
124+ orig[ifname] = iface
125+ new_interfaces.add(ifname)
126+
127+ return new_interfaces
128+
129+ def _merge_yaml_config(self, yaml_file):
130+ new_interfaces = set()
131+
132+ try:
133+ with open(yaml_file) as f:
134+ yaml_data = yaml.load(f, Loader=yaml.CSafeLoader)
135+ network = None
136+ if yaml_data is not None:
137+ network = yaml_data.get('network')
138+ if network:
139+ if 'ethernets' in network:
140+ new = self._merge_interface_config(
141+ self.ethernets, network.get('ethernets'))
142+ new_interfaces |= new
143+ if 'wifis' in network:
144+ new = self._merge_interface_config(
145+ self.wifis, network.get('wifis'))
146+ new_interfaces |= new
147+ if 'bridges' in network:
148+ new = self._merge_interface_config(
149+ self.bridges, network.get('bridges'))
150+ new_interfaces |= new
151+ if 'bonds' in network:
152+ new = self._merge_interface_config(
153+ self.bonds, network.get('bonds'))
154+ new_interfaces |= new
155+ if 'vlans' in network:
156+ new = self._merge_interface_config(
157+ self.vlans, network.get('vlans'))
158+ new_interfaces |= new
159+ if 'renderer' in network:
160+ self.config['network']['renderer'] = network.get(
161+ 'renderer')
162+ return new_interfaces
163+ except (IOError, yaml.YAMLError):
164+ logging.error('Error while loading {}'.format(yaml_file))
165+ self.config = {}

Subscribers

People subscribed via source and target branches