Skip to content

Commit c607261

Browse files
committed
Import migrate_junos_data script
Script to convert Juniper Junos "set" commands into our custom YAML configuration format. Signed-off-by: Georg Pfuetzenreuter <[email protected]>
1 parent b798eb9 commit c607261

File tree

1 file changed

+394
-0
lines changed

1 file changed

+394
-0
lines changed

Diff for: juniper_junos-formula/bin/migrate_junos_data.py

+394
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,394 @@
1+
#!/usr/bin/python3
2+
"""
3+
Tool converting Junos "set" output to our YAML configuration format for use with Salt
4+
Copyright (C) 2023 SUSE LLC <[email protected]>
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
"""
19+
import re
20+
21+
import json
22+
import yaml
23+
24+
infile = 'input.txt'
25+
outfile = 'output.yaml'
26+
27+
confmap = {'applications': {}, 'interfaces': {}, 'subinterfaces': {}, 'zones': {}, 'addresses': {}, 'address_sets': {}, 'policies': [], 'snats': {}, 'proxyarps': {}, 'snat_rules': {}, 'static_nat_rules': {}, 'sroutes': [], 'sroutes6': []}
28+
outmap = {'services': [], 'interfaces': {}, 'zones': [], 'addresses': [], 'address_groups': [], 'policies': [], 'source_nat_pools': [], 'proxy_arp': [], 'source_nat_rules': [], 'static_nat_rules': [], 'sroutes': [], 'sroutes6': [], 'vlans': {}}
29+
discard = ['mtu']
30+
lacp_key = 'lacp'
31+
mclag_key = 'mc'
32+
33+
def do_description(description):
34+
return 'description', ' '.join(description).replace('"','')
35+
36+
with open(infile, 'r') as fh:
37+
for line in fh:
38+
ls = line.replace('\n', '').split(' ')
39+
lsl = len(ls)
40+
if lsl < 5:
41+
continue
42+
match ls[1]:
43+
case 'applications':
44+
name = ls[3]
45+
if not name in confmap['applications']:
46+
confmap['applications'].update({name: {}})
47+
confmap['applications'][name].update({ls[4]: ls[5]})
48+
case 'interfaces':
49+
name = ls[2]
50+
if not name in confmap['interfaces']:
51+
confmap['interfaces'].update({name: {}})
52+
mymap = confmap['interfaces'][name]
53+
if ls[3] == 'unit':
54+
unit = int(ls[4])
55+
#if not 'units' in mymap:
56+
# mymap.update({'units': {}})
57+
#myunits = mymap['units']
58+
#if not unit in myunits:
59+
# myunits.update({unit: {}})
60+
# myunit = myunits[unit]
61+
if unit == 0:
62+
match ls[5]:
63+
case 'family':
64+
if ls[6] == 'ethernet-switching' and ls[7] in ['vlan', 'interface-mode']:
65+
#if not 'vlan' in myunit:
66+
# myunit.update({'vlan': {}})
67+
#myvlan = myunit['vlan']
68+
if not 'vlan' in mymap:
69+
mymap.update({'vlan': {}})
70+
myvlan = mymap['vlan']
71+
if ls[8] == 'members':
72+
if not 'ids' in myvlan:
73+
myvlan['ids'] = []
74+
myvlan['ids'].append(ls[9])
75+
elif ls[7] == 'interface-mode':
76+
myvlan['type'] = ls[8]
77+
# currently neither needed nor supported by the configuration
78+
#elif unit != 0:
79+
# name = f'{name}.{ls[4]}'
80+
# if not name in confmap['subinterfaces']:
81+
# confmap['subinterfaces'].update({name: {}})
82+
# mymap = confmap['subinterfaces'][name]
83+
mykey, myval = None, None
84+
if ls[3] == 'description':
85+
mykey, myval = do_description(ls[4:])
86+
elif ls[3] == 'mtu':
87+
mymap['mtu'] = int(ls[4])
88+
elif lsl >= 7 and ls[5] == 'description':
89+
mykey, myval = do_description(ls[6:])
90+
elif lsl == 6 or (lsl == 7 and ls[4] == 'lacp') or (lsl == 7 and ls[4] == 'mc-ae'):
91+
mykey, myval = ls[4], ls[5]
92+
match mykey:
93+
case 'redundancy-group':
94+
# set interfaces reth0 redundant-ether-options redundancy-group 1
95+
mykey = lacp_key
96+
myval = {'group': int(myval)}
97+
case 'lacp':
98+
mykey = lacp_key
99+
if lsl == 6:
100+
# set interfaces reth0 redundant-ether-options lacp active
101+
myval = {'mode': myval}
102+
elif lsl == 7:
103+
# set interfaces reth0 redundant-ether-options lacp periodic fast
104+
myval = {ls[5]: ls[6]}
105+
case 'mc-ae':
106+
mykey = mclag_key
107+
myval = {ls[5]: ls[6]}
108+
case '802.3ad':
109+
mykey = lacp_key
110+
myval = ls[5]
111+
elif lsl == 9:
112+
if ls[3] == 'unit':
113+
myval = ls[8]
114+
match ls[6]:
115+
case 'inet':
116+
# set interfaces reth0 unit 0 family inet address 195.135.223.5/28
117+
#mykey = 'addr'
118+
mykey = 'addresses'
119+
case 'inet6':
120+
# set interfaces reth0 unit 0 family inet6 address 2a07:de40:b260:0001::5/64
121+
#mykey = 'addr6'
122+
mykey = 'addresses'
123+
elif lsl == 5:
124+
mykey, myval = ls[3], ls[4]
125+
#else:
126+
#print(f'Dropped line: {ls}')
127+
if mykey and myval:
128+
#print(mykey)
129+
if mykey in ['lacp', 'mc'] and lsl > 6:
130+
if not 'ae' in mymap:
131+
mymap['ae'] = {}
132+
myae = mymap['ae']
133+
lowmap = myae
134+
else:
135+
lowmap = mymap
136+
if mykey in lowmap:
137+
if isinstance(myval, dict):
138+
# add to existing lacp_options
139+
lowmap[mykey].update(myval)
140+
elif isinstance(mykey[mykey], list):
141+
lowmap[mykey].append(myval)
142+
elif mykey not in discard:
143+
if mykey.startswith('addr'):
144+
myval = [myval]
145+
lowmap.update({mykey: myval})
146+
case 'security':
147+
if ls[3] == 'security-zone':
148+
name = ls[4]
149+
if not name in confmap['zones']:
150+
confmap['zones'].update({name: {'interfaces': [], 'local-services': []}})
151+
match ls[5]:
152+
case 'interfaces':
153+
ifname = ls[6]
154+
iflist = confmap['zones'][name]['interfaces']
155+
if not ifname in iflist:
156+
iflist.append(ifname)
157+
case 'host-inbound-traffic':
158+
svname = ls[7]
159+
svlist = confmap['zones'][name]['local-services']
160+
if not svname in svlist:
161+
svlist.append(svname)
162+
elif ls[2] == 'address-book':
163+
name = ls[5]
164+
match ls[4]:
165+
case 'address':
166+
addressmap = confmap['addresses']
167+
address = ' '.join(ls[6:])
168+
addressmap.update({name: address})
169+
case 'address-set':
170+
addressmap = confmap['address_sets']
171+
if not name in addressmap:
172+
addressmap.update({name: []})
173+
addressmap[name].append(ls[7])
174+
elif ls[2] == 'policies':
175+
#if ls[7] == 'policy':
176+
# policy = ls[8]
177+
#if policy not in confmap['policies']:
178+
# confmap['policies'].update({policy: {'match': {'sources': [], 'destinations': [], 'applications': []}}})
179+
#policymap = confmap['policies'][policy]
180+
policymap = confmap['policies']
181+
182+
mymap = {'match': {'sources': [], 'destinations': [], 'applications': []}}
183+
184+
if ls[7] == 'policy':
185+
policy = ls[8]
186+
mymap.update({'name': policy})
187+
if ls[3] == 'from-zone':
188+
from_zone = ls[4]
189+
mymap.update({'from_zone': from_zone})
190+
if ls[5] == 'to-zone':
191+
to_zone = ls[6]
192+
mymap.update({'to_zone': to_zone})
193+
if ls[9] == 'match':
194+
match ls[10]:
195+
case 'source-address':
196+
source = ls[11]
197+
mymap['match']['sources'].append(source)
198+
case 'destination-address':
199+
destination = ls[11]
200+
mymap['match']['destinations'].append(destination)
201+
case 'application':
202+
application = ls[11]
203+
mymap['match']['applications'].append(application)
204+
elif ls[9] == 'then':
205+
mymap.update({'action': ls[10]})
206+
207+
policymap.append(mymap)
208+
209+
elif ls[2] == 'nat':
210+
if ls[3] in ['source', 'static']:
211+
name = ls[5]
212+
if ls[4] == 'pool':
213+
if ls[6] == 'address':
214+
address = ls[7]
215+
confmap['snats'].update({name: address})
216+
elif ls[4] == 'rule-set':
217+
if ls[3] == 'source':
218+
confname = 'snat_rules'
219+
elif ls[3] == 'static':
220+
confname = 'static_nat_rules'
221+
rulesetmap = confmap[confname]
222+
if not name in rulesetmap:
223+
rulesetmap.update({name: {'rules': {}}})
224+
if ls[6] in ['from', 'to'] and ls[7] == 'zone':
225+
rulesetmap[name].update({ls[6]: ls[8]})
226+
if ls[6] == 'rule':
227+
rulename = ls[7]
228+
if not rulename in rulesetmap[name]['rules']:
229+
rulesetmap[name]['rules'].update({rulename: {'sources': [], 'destinations': []}})
230+
rulemap = rulesetmap[name]['rules'][rulename]
231+
if ls[8] == 'match' and ls[9] == 'source-address':
232+
rulemap['sources'].append(ls[10])
233+
if ls[8] == 'match' and ls[9] == 'destination-address':
234+
# we don't seem to use this and assume everything?
235+
rulemap['destinations'].append(ls[10])
236+
elif ls[8] == 'then':
237+
if ls[9] == 'source-nat':
238+
if ls[10] == 'pool':
239+
rulemap.update({'pool': ls[11]})
240+
elif ls[9] == 'static-nat':
241+
if ls[10] == 'prefix':
242+
rulemap.update({'prefix': ls[11]})
243+
if not rulemap['destinations']:
244+
# feels wrong, it's in the expected output file but not in the input file
245+
rulemap['destinations'].append('0.0.0.0/0')
246+
247+
elif ls[3] == 'proxy-arp':
248+
if ls[4] == 'interface' and ls[6] == 'address':
249+
interface = ls[5]
250+
address = ls[7]
251+
confmap['proxyarps'].update({interface: address})
252+
253+
case 'routing-options':
254+
if ls[2] == 'static' and ls[3] == 'route' and ls[5] == 'next-hop':
255+
route = ls[4]
256+
hop = ls[6]
257+
confmap['sroutes'].append({'dst': route, 'gw': hop})
258+
elif ls[2] == 'rib' and ls[3] == 'inet6.0' and ls[4] == 'static' and ls[5] == 'route' and ls[7] == 'next-hop':
259+
route = ls[6]
260+
hop = ls[8]
261+
confmap['sroutes6'].append({'dst': route, 'gw': hop})
262+
263+
case 'vlans':
264+
vlan = ls[2]
265+
match ls[3]:
266+
case 'vlan-id':
267+
mykey = 'id'
268+
myval = int(ls[4])
269+
case 'description':
270+
mykey, myval = do_description(ls[4:])
271+
mymap = outmap['vlans']
272+
if vlan in mymap:
273+
mymap[vlan].update({mykey: myval})
274+
else:
275+
mymap.update({vlan: {mykey: myval}})
276+
277+
scrubbed_policies = []
278+
for policy in confmap['policies']:
279+
scrubbed = policy.copy()
280+
scrubbed.pop('match')
281+
if 'action' in scrubbed:
282+
scrubbed.pop('action')
283+
scrubbed_policies.append(scrubbed)
284+
285+
unique_policies = [dict(t) for t in {tuple(d.items()) for d in scrubbed_policies}]
286+
287+
for policy in unique_policies:
288+
policymap = {'fromzn': policy['from_zone'], 'tozn': policy['to_zone'], 'name': policy['name'], 'srcs': [], 'dsts': [], 'applications': []}
289+
for origpolicy in confmap['policies']:
290+
if origpolicy['name'] == policy['name'] and origpolicy['from_zone'] == policy['from_zone'] and origpolicy['to_zone'] == policy['to_zone']:
291+
new_sources = set(origpolicy['match']['sources']) - set(policymap['srcs'])
292+
if new_sources:
293+
policymap['srcs'].extend(list(new_sources))
294+
new_destinations = set(origpolicy['match']['destinations']) - set(policymap['dsts'])
295+
if new_destinations:
296+
policymap['dsts'].extend(list(new_destinations))
297+
new_applications = set(origpolicy['match']['applications']) - set(policymap['applications'])
298+
if new_applications:
299+
policymap['applications'].extend(list(new_applications))
300+
if 'action' in origpolicy:
301+
policymap.update({'action': origpolicy['action']})
302+
if not policymap['srcs']:
303+
del policymap['srcs']
304+
if not policymap['dsts']:
305+
del policymap['dsts']
306+
if not policymap['applications']:
307+
del policymap['applications']
308+
outmap['policies'].append(policymap)
309+
310+
for config in confmap.keys():
311+
if isinstance(confmap[config], list):
312+
if not config == 'policies':
313+
# currently used for sroutes and sroutes6
314+
outmap[config].extend(confmap[config])
315+
continue
316+
for name, lowconfig in confmap[config].items():
317+
match config:
318+
case 'applications':
319+
outport = lowconfig.get('destination-port', 'N/A')
320+
try:
321+
outport = int(outport)
322+
except ValueError:
323+
outport = outport
324+
outconfig = {'name': name, 'proto': lowconfig['protocol'], 'port': outport}
325+
outmap['services'].append(outconfig)
326+
case 'interfaces':
327+
outmap['interfaces'].update({name: lowconfig})
328+
#case 'subinterfaces':
329+
# mymap = {'ifname': name}
330+
# mymap.update(lowconfig)
331+
# mymap.update({'vlanid': int(re.search(r'\d+$', name).group(0))})
332+
# outmap['subinterfaces'].append(mymap)
333+
case 'zones':
334+
mymap = {'name': name}
335+
mymap.update(lowconfig)
336+
outmap['zones'].append(mymap)
337+
case 'addresses':
338+
mymap = {'name': name, 'prefix': lowconfig}
339+
outmap['addresses'].append(mymap)
340+
case 'address_sets':
341+
mymap = {'name': name, 'addresses': lowconfig}
342+
outmap['address_groups'].append(mymap)
343+
# policies are now treated in a special clinic
344+
#case 'policies':
345+
# match = lowconfig['match']
346+
# mymap = {'fromzn': lowconfig['from_zone'], 'tozn': lowconfig['to_zone'], 'name': name,
347+
# 'srcs': match['sources'], 'dsts': match['destinations'], 'applications': match['applications'],
348+
# 'action': lowconfig['action']}
349+
# outmap['policies'].append(mymap)
350+
case 'snats':
351+
mymap = {'name': name, 'prefix': lowconfig}
352+
outmap['source_nat_pools'].append(mymap)
353+
case 'proxyarps':
354+
mymap = {'interface': name, 'prefix': lowconfig}
355+
outmap['proxy_arp'].append(mymap)
356+
case 'snat_rules':
357+
mymap = {'set': name, 'fromzn': lowconfig['from'], 'tozn': lowconfig['to'], 'rules': []}
358+
for rule, ruleconfig in lowconfig['rules'].items():
359+
mymap['rules'].append({'name': rule, 'srcs': ruleconfig['sources'], 'dsts': ruleconfig['destinations'], 'pool': ruleconfig['pool']})
360+
outmap['source_nat_rules'].append(mymap)
361+
case 'static_nat_rules':
362+
mymap = {'set': name, 'fromzn': lowconfig['from'], 'rules': []}
363+
for rule, ruleconfig in lowconfig['rules'].items():
364+
thismap = {'name': rule, 'dsts': ruleconfig['destinations'], 'natprefix': ruleconfig['prefix']}
365+
if ruleconfig['sources']:
366+
# we don't seem to use this
367+
thismap.update({'srcs': ruleconfig['sources']})
368+
mymap['rules'].append(thismap)
369+
outmap['static_nat_rules'].append(mymap)
370+
371+
with open('debug', 'w') as fh:
372+
json.dump(confmap, fh)
373+
374+
dumpmap = {}
375+
dumpmap['devices'] = {'FIXME': {}}
376+
dd = dumpmap['devices']['FIXME']
377+
378+
for key, value in outmap.items():
379+
if key:
380+
pair = {key: value}
381+
# to-do: maybe differentiate between switching and firewall keys?
382+
if key in ['vlans']:
383+
dumpmap.update(pair)
384+
elif key in ['interfaces']:
385+
dd.update(pair)
386+
387+
if not dd:
388+
dumpmap.clear()
389+
390+
with open(outfile, 'w') as fh:
391+
# yaml.Dumper.ignore_aliases = lambda *args : True
392+
yaml.dump(dumpmap, fh, sort_keys=False)
393+
394+
# yes, I discovered Junos JSON output too late.

0 commit comments

Comments
 (0)