-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathtclparser2.py
112 lines (90 loc) · 3.02 KB
/
tclparser2.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""Sanitizer output parsing"""
def parse_f5_config(config):
"""Very simple sanitizer output parser.
This only parses "{ ... }" groups and ";" command separators.
Everything else is treated as commands and command arguments.
Each command is a list itself, and "{ ... }" groups are nested
lists. Returns a list of commands.
"""
# Normalize line endings.
config = config.replace('\r\n', '\n')
# Work around the sanitizer removing too much from the line when
# removing an IP address.
config = config.replace('servers <REMOVED>\n', 'servers {\n')
# Standardize command separators.
config = config.replace('\n', ';')
# Ensure we can cleanly split on the symbols we care about.
for symbol in ';{}':
config = config.replace(symbol, ' %s ' % symbol)
tokens = config.split()
def parse_commands(pos):
commands = []
command = []
while pos < len(tokens):
token = tokens[pos]
if token == ';':
if command:
# Avoid creating empty commands when seeing
# repeated command separators.
commands.append(command)
command = []
elif token == '{':
nested, pos = parse_commands(pos + 1)
command.append(nested)
elif token == '}':
if command:
commands.append(command)
return commands, pos
else:
command.append(token)
pos += 1
if command:
commands.append(command)
return commands, pos
commands, pos = parse_commands(0)
if pos != len(tokens):
msg = 'Could only parse %d of %d tokens' % (pos, len(tokens))
raise RuntimeError(msg)
return commands
def clean(name):
"""Remove port numbers and route domain from name.
>>> clean('foo')
'foo'
>>> clean('foo:http')
'foo'
>>> clean('foo%123')
'foo'
>>> clean('foo%123:ssh')
'foo'
"""
name = name.partition(':')[0]
name = name.partition('%')[0]
return name
def extract_pools(config):
"""Extract pools from a sanitized F5 config.
All pools are returned in a mapping of pool name to the list of
pool members.
"""
commands = parse_f5_config(config)
pools = {}
for cmd in commands:
if cmd[0] == 'pool':
pool_name = cmd[1]
parts = cmd[2]
for p in parts:
if p[0] == 'members':
if len(p) == 2:
# A pool with a list of members
pools[pool_name] = [clean(m[0]) for m in p[1]]
else:
# A single unpacked member
pools[pool_name] = [clean(p[1])]
return pools
if __name__ == '__main__':
import fileinput
import pprint
data = ''.join(fileinput.input())
try:
pprint.pprint(extract_pools(data))
except RuntimeError as exc:
print exc