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

Subscribers

People subscribed via source and target branches