trigger 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. trigger/__init__.py +7 -0
  2. trigger/acl/__init__.py +32 -0
  3. trigger/acl/autoacl.py +70 -0
  4. trigger/acl/db.py +324 -0
  5. trigger/acl/dicts.py +357 -0
  6. trigger/acl/grammar.py +112 -0
  7. trigger/acl/ios.py +222 -0
  8. trigger/acl/junos.py +422 -0
  9. trigger/acl/models.py +118 -0
  10. trigger/acl/parser.py +168 -0
  11. trigger/acl/queue.py +296 -0
  12. trigger/acl/support.py +1431 -0
  13. trigger/acl/tools.py +746 -0
  14. trigger/bin/__init__.py +0 -0
  15. trigger/bin/acl.py +233 -0
  16. trigger/bin/acl_script.py +574 -0
  17. trigger/bin/aclconv.py +82 -0
  18. trigger/bin/check_access.py +93 -0
  19. trigger/bin/check_syntax.py +66 -0
  20. trigger/bin/fe.py +197 -0
  21. trigger/bin/find_access.py +191 -0
  22. trigger/bin/gnng.py +434 -0
  23. trigger/bin/gong.py +86 -0
  24. trigger/bin/load_acl.py +841 -0
  25. trigger/bin/load_config.py +18 -0
  26. trigger/bin/netdev.py +317 -0
  27. trigger/bin/optimizer.py +638 -0
  28. trigger/bin/run_cmds.py +18 -0
  29. trigger/changemgmt/__init__.py +352 -0
  30. trigger/changemgmt/bounce.py +57 -0
  31. trigger/cmds.py +1217 -0
  32. trigger/conf/__init__.py +94 -0
  33. trigger/conf/global_settings.py +674 -0
  34. trigger/contrib/__init__.py +7 -0
  35. trigger/exceptions.py +307 -0
  36. trigger/gorc.py +172 -0
  37. trigger/netdevices/__init__.py +1288 -0
  38. trigger/netdevices/loader.py +174 -0
  39. trigger/netscreen.py +1030 -0
  40. trigger/packages/__init__.py +6 -0
  41. trigger/packages/peewee.py +8084 -0
  42. trigger/rancid.py +463 -0
  43. trigger/tacacsrc.py +584 -0
  44. trigger/twister.py +2203 -0
  45. trigger/twister2.py +745 -0
  46. trigger/utils/__init__.py +88 -0
  47. trigger/utils/cli.py +349 -0
  48. trigger/utils/importlib.py +77 -0
  49. trigger/utils/network.py +157 -0
  50. trigger/utils/rcs.py +178 -0
  51. trigger/utils/templates.py +81 -0
  52. trigger/utils/url.py +78 -0
  53. trigger/utils/xmltodict.py +298 -0
  54. trigger-2.0.0.dist-info/METADATA +146 -0
  55. trigger-2.0.0.dist-info/RECORD +61 -0
  56. trigger-2.0.0.dist-info/WHEEL +5 -0
  57. trigger-2.0.0.dist-info/entry_points.txt +15 -0
  58. trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
  59. trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
  60. trigger-2.0.0.dist-info/top_level.txt +2 -0
  61. twisted/plugins/trigger_xmlrpc.py +124 -0
trigger/rancid.py ADDED
@@ -0,0 +1,463 @@
1
+ #!/usr/bin/env python
2
+
3
+ """
4
+ Parse RANCID db files so they can be converted into Trigger NetDevice objects.
5
+
6
+ .. versionadded:: 1.2
7
+
8
+ Far from complete. Very early in development. Here is a basic example.
9
+
10
+ >>> from trigger import rancid
11
+ >>> rancid_root = '/path/to/rancid/data'
12
+ >>> r = Rancid(rancid_root)
13
+ >>> dev = r.devices.get('test1-abc.net.aol.com')
14
+ >>> dev
15
+ RancidDevice(nodeName='test-abc.net.aol.com', manufacturer='juniper', deviceStatus='up', deviceType=None)
16
+
17
+ Another option if you want to get the parsed RANCID data directly without
18
+ having to create an object is as simple as this::
19
+
20
+ >>> parsed = rancid.parse_rancid_data('/path/to/dancid/data')
21
+
22
+ Or using multiple RANCID instances within a single root::
23
+
24
+ >>> multi_parsed = rancid.parse_rancid_data('/path/to/rancid/data', recurse_subdirs=True)
25
+
26
+ """
27
+
28
+ __author__ = "Jathan McCollum"
29
+ __maintainer__ = "Jathan McCollum"
30
+ __email__ = "jathan.mccollum@teamaol.com"
31
+ __copyright__ = "Copyright 2012-2012, AOL Inc.; 2013 Salesforce.com"
32
+ __version__ = "0.1.1"
33
+
34
+ import collections
35
+ import csv
36
+ import itertools
37
+ import os
38
+
39
+ __all__ = (
40
+ "parse_rancid_file",
41
+ "parse_devices",
42
+ "walk_rancid_subdirs",
43
+ "parse_rancid_data",
44
+ "gather_devices",
45
+ "Rancid",
46
+ "RancidDevice",
47
+ )
48
+
49
+ # Constants
50
+ CONFIG_DIRNAME = "configs"
51
+ RANCID_DB_FILE = "router.db"
52
+ RANCID_ALL_FILE = "routers.all"
53
+ RANCID_DOWN_FILE = "routers.down"
54
+ RANCID_UP_FILE = "routers.up"
55
+ NETDEVICE_FIELDS = ["nodeName", "manufacturer", "deviceStatus", "deviceType"]
56
+
57
+
58
+ # Functions
59
+ def _parse_delimited_file(root_dir, filename, delimiter=":"):
60
+ """
61
+ Parse a colon-delimited file and return the contents as a list of lists.
62
+
63
+ Intended to be used for parsing of all RANCID files.
64
+
65
+ :param root_dir:
66
+ Where to find the file
67
+
68
+ :param filename:
69
+ Name of the file to parse
70
+
71
+ :param delimiter:
72
+ (Optional) Field delimiter
73
+ """
74
+ filepath = os.path.join(root_dir, filename)
75
+ with open(filepath) as f:
76
+ reader = csv.reader(f, delimiter=delimiter)
77
+ return [r for r in reader if len(r) > 1] # Skip unparsed lines
78
+
79
+ return None
80
+
81
+
82
+ def parse_rancid_file(rancid_root, filename=RANCID_DB_FILE, fields=None, delimiter=":"):
83
+ """
84
+ Parse a RANCID file and return generator representing a list of lists
85
+ mapped to the ``fields``.
86
+
87
+ :param rancid_root:
88
+ Where to find the file
89
+
90
+ :param filename:
91
+ Name of the file to parse (e.g. ``router.db``)
92
+
93
+ :param fields:
94
+ (Optional) A list of field names used to map to the device data
95
+
96
+ :param delimiter:
97
+ (Optional) Field delimiter
98
+ """
99
+ device_data = _parse_delimited_file(rancid_root, filename, delimiter)
100
+ if not device_data:
101
+ return None # Always return None if there are no results
102
+
103
+ # Make sure fields is not null and is some kind of iterable
104
+ if not fields:
105
+ fields = NETDEVICE_FIELDS
106
+ else:
107
+ if not isinstance(fields, collections.Iterable):
108
+ raise RuntimeError("`fields` must be iterable")
109
+
110
+ # Map fields to generator of generators (!!)
111
+ # Python 3: izip_longest renamed to zip_longest
112
+ metadata = (itertools.zip_longest(fields, vals) for vals in device_data)
113
+
114
+ return metadata
115
+
116
+
117
+ def walk_rancid_subdirs(rancid_root, config_dirname=CONFIG_DIRNAME, fields=None):
118
+ """
119
+ Walk the ``rancid_root`` and parse the included RANCID files.
120
+
121
+ Returns a dictionary keyed by the name of the subdirs with values set to
122
+ the parsed data for each RANCID file found inside.
123
+
124
+ >>> from trigger import rancid
125
+ >>> subdirs = rancid.walk_rancid_subdirs('/data/rancid')
126
+ >>> subdirs.get('network-security')
127
+ {'router.db': <generator object <genexpr> at 0xa5b852c>,
128
+ 'routers.all': <generator object <genexpr> at 0xa5a348c>,
129
+ 'routers.down': <generator object <genexpr> at 0xa5be9dc>,
130
+ 'routers.up': <generator object <genexpr> at 0xa5bea54>}
131
+
132
+ :param rancid_root:
133
+ Where to find the file
134
+
135
+ :param config_dirname:
136
+ If the 'configs' dir is named something else
137
+
138
+ :param fields:
139
+ (Optional) A list of field names used to map to the device data
140
+ """
141
+ walker = os.walk(rancid_root)
142
+ baseroot, basedirs, basefiles = walker.next() # First item is base
143
+
144
+ results = {}
145
+ for root, dirnames, filenames in walker:
146
+ # Skip any path with CVS in it
147
+ if "CVS" in root:
148
+ # print 'skipping CVS:', root
149
+ continue
150
+
151
+ # Don't visit CVS directories
152
+ if "CVS" in dirnames:
153
+ dirnames.remove("CVS")
154
+
155
+ # Skip directories with nothing in them
156
+ if not filenames or not dirnames:
157
+ continue
158
+
159
+ # Only walk directories in which we also have configs
160
+ if config_dirname in dirnames:
161
+ owner = os.path.basename(root)
162
+ results[owner] = {}
163
+ for file_ in filenames:
164
+ results[owner][file_] = parse_rancid_file(root, file_, fields)
165
+
166
+ return results
167
+
168
+
169
+ def parse_rancid_data(
170
+ rancid_root,
171
+ filename=RANCID_DB_FILE,
172
+ fields=None,
173
+ config_dirname=CONFIG_DIRNAME,
174
+ recurse_subdirs=False,
175
+ ):
176
+ """
177
+ Parse single or multiple RANCID instances and return an iterator of the
178
+ device metadata.
179
+
180
+ A single instance expects to find 'router.db' in ``rancid_root``.
181
+
182
+ If you set ``recurise_subdirs``, multiple instances will be expected, and a
183
+ `router.db` will be expected to be found in each subdirectory.
184
+
185
+ :param rancid_root:
186
+ Where to find the file
187
+
188
+ :param filename:
189
+ Name of the file to parse (e.g. ``router.db``)
190
+
191
+ :param fields:
192
+ (Optional) A list of field names used to map to the device data
193
+
194
+ :param config_dirname:
195
+ If the 'configs' dir is named something else
196
+
197
+ :param recurse_subdirs:
198
+ Whether to recurse directories (e.g. multiple instances)
199
+ """
200
+ if recurse_subdirs:
201
+ subdirs = walk_rancid_subdirs(rancid_root, config_dirname, fields)
202
+ metadata = gather_devices(subdirs, filename)
203
+ else:
204
+ metadata = parse_rancid_file(rancid_root, filename, fields)
205
+
206
+ return metadata
207
+
208
+
209
+ def parse_devices(metadata, parser):
210
+ """
211
+ Iterate device ``metadata`` to use ``parser`` to create and return a
212
+ list of network device objects.
213
+
214
+ :param metadata:
215
+ A collection of key/value pairs (Generally returned from
216
+ `~trigger.rancid.parse_rancid_file`)
217
+
218
+ :param parser:
219
+ A callabale used to create your objects
220
+ """
221
+
222
+ # Two tees of `metadata` iterator, in case a TypeError is encountered, we
223
+ # aren't losing the first item.
224
+ md_original, md_backup = itertools.tee(metadata)
225
+ try:
226
+ # Try to parse using the generator (most efficient)
227
+ return [parser(d) for d in md_original]
228
+ except TypeError:
229
+ # Try to parse by unpacking a dict into kwargs
230
+ return [parser(**dict(d)) for d in md_backup]
231
+ except Exception as err:
232
+ # Or just give up
233
+ print(f"Parser failed with this error: {repr(err)!r}")
234
+ return None
235
+ else:
236
+ raise RuntimeError("This should never happen!")
237
+
238
+
239
+ def gather_devices(subdir_data, rancid_db_file=RANCID_DB_FILE):
240
+ """
241
+ Returns a chained iterator of parsed RANCID data, based from the results of
242
+ `~trigger.rancid.walk_rancid_subdirs`.
243
+
244
+ This iterator is suitable for consumption by
245
+ `~trigger.rancid.parse_devices` or Trigger's
246
+ `~trigger.netdevices.NetDevices`.
247
+
248
+ :param rancid_root:
249
+ Where to find your RANCID files (router.db, et al.)
250
+
251
+ :param rancid_db_file:
252
+ If it's named other than ``router.db``
253
+ """
254
+ iters = []
255
+ for rdir, files in subdir_data.items():
256
+ # Only carry on if we find 'router.db' or it's equivalent
257
+ metadata = files.get(rancid_db_file)
258
+ if metadata is None:
259
+ continue
260
+
261
+ iters.append(metadata)
262
+
263
+ return itertools.chain(*iters)
264
+
265
+
266
+ def _parse_config_file(
267
+ rancid_root, filename, parser=None, config_dirname=CONFIG_DIRNAME, max_lines=30
268
+ ):
269
+ """Parse device config file for metadata (make, model, etc.)"""
270
+ filepath = os.path.join(rancid_root, config_dirname, filename)
271
+ try:
272
+ with open(filepath) as f:
273
+ config = []
274
+ for idx, line in enumerate(f):
275
+ if idx >= max_lines:
276
+ break
277
+
278
+ if any([line.startswith("#"), line.startswith("!") and len(line) > 2]):
279
+ config.append(line.strip())
280
+
281
+ return config
282
+
283
+ except OSError:
284
+ return None
285
+
286
+
287
+ def _parse_config_files(devices, rancid_root, config_dirname=CONFIG_DIRNAME):
288
+ """Parse multiple device config files"""
289
+ return_data = {}
290
+ for dev in devices:
291
+ return_data[dev.nodeName] = _parse_config_file(
292
+ rancid_root, dev.nodeName, config_dirname
293
+ )
294
+ return return_data
295
+
296
+
297
+ def _parse_cisco(config):
298
+ """NYI - Parse Cisco config to get metadata"""
299
+
300
+
301
+ def _parse_juniper(config):
302
+ """NYI - Parse Juniper config to get metadata"""
303
+
304
+
305
+ def _parse_netscreen(config):
306
+ """NYI - Parse NetScreen config to get metadata"""
307
+
308
+
309
+ def massage_data(device_list):
310
+ """ "
311
+ Given a list of objects, try to fixup their metadata based on thse rules.
312
+
313
+ INCOMPLETE.
314
+ """
315
+ devices = device_list
316
+
317
+ netdevices = {}
318
+ for idx, dev in enumerate(devices):
319
+ if dev.manufacturer == "netscreen":
320
+ dev.manufacturer = "juniper"
321
+ dev.deviceType = "FIREWALL"
322
+
323
+ elif dev.manufacturer == "cisco":
324
+ dev.deviceType = "ROUTER"
325
+
326
+ elif dev.manufacturer == "juniper":
327
+ dev.deviceType = "ROUTER"
328
+ else:
329
+ print("WTF", dev.nodeName, "requires no massaging!")
330
+
331
+ """
332
+ # Asset
333
+ dev.serialNumber = dev.assetID = None
334
+ dev.lastUpdate = datetime.datetime.today()
335
+ """
336
+ netdevices[dev.nodeName] = dev
337
+
338
+ return netdevices
339
+
340
+
341
+ # Classes
342
+ class RancidDevice(collections.namedtuple("RancidDevice", NETDEVICE_FIELDS)):
343
+ """
344
+ A simple subclass of namedtuple to store contents of parsed RANCID files.
345
+
346
+ Designed to support all router.* files. The field names are intended to be
347
+ compatible with Trigger's NetDevice objects.
348
+
349
+ :param nodeName:
350
+ Hostname of device
351
+
352
+ :param manufacturer:
353
+ Vendor/manufacturer name of device
354
+
355
+ :param deviceStatus:
356
+ (Optional) Up/down status of device
357
+
358
+ :param deviceType:
359
+ (Optional) The device type... determined somehow
360
+ """
361
+
362
+ __slots__ = ()
363
+
364
+ def __new__(cls, nodeName, manufacturer, deviceStatus=None, deviceType=None):
365
+ return super(cls, RancidDevice).__new__(
366
+ cls, nodeName, manufacturer, deviceStatus, deviceType
367
+ )
368
+
369
+
370
+ class Rancid:
371
+ """
372
+ Holds RANCID data. INCOMPLETE.
373
+
374
+ Defaults to a single RANID instance specified as ``rancid_root``. It will
375
+ parse the file found at ``rancid_db_file`` and use this to populate the
376
+ ``devices`` dictionary with instances of ``device_class``.
377
+
378
+ If you set ``recurse_subdirs``, it is assumed that ``rancid_root`` holds
379
+ one or more individual RANCID instances and will attempt to walk them,
380
+ parse them, and then aggregate all of the resulting device instances into
381
+ the ``devices`` dictionary.
382
+
383
+ Still needs:
384
+
385
+ + Config parsing for metadata (make, model, type, serial, etc.)
386
+ + Recursive Config file population/parsing when ``recurse_subdirs`` is set
387
+
388
+ :param rancid_root:
389
+ Where to find your RANCID files (router.db, et al.)
390
+
391
+ :param rancid_db_file:
392
+ If it's named other than ``router.db``
393
+
394
+ :param config_dir:
395
+ If it's named other than ``configs``
396
+
397
+ :param device_fields:
398
+ A list of field names used to map to the device data. These must match
399
+ the attributes expected by ``device_class``.
400
+
401
+ :param device_class:
402
+ If you want something other than ``RancidDevice``
403
+
404
+ :param recurse_subdirs:
405
+ Whether you want to recurse directories.
406
+ """
407
+
408
+ def __init__(
409
+ self,
410
+ rancid_root,
411
+ rancid_db_file=RANCID_DB_FILE,
412
+ config_dirname=CONFIG_DIRNAME,
413
+ device_fields=None,
414
+ device_class=None,
415
+ recurse_subdirs=False,
416
+ ):
417
+ if device_class is None:
418
+ device_class = RancidDevice
419
+
420
+ self.rancid_root = rancid_root
421
+ self.rancid_db_file = rancid_db_file
422
+ self.config_dirname = config_dirname
423
+ self.device_fields = device_fields
424
+ self.device_class = device_class
425
+ self.recurse_subdirs = recurse_subdirs
426
+ self.configs = {}
427
+ self.data = {}
428
+ self.devices = {}
429
+ self._populate()
430
+
431
+ def _populate(self):
432
+ """Fired after init, does all the stuff to populate RANCID data."""
433
+ self._populate_devices()
434
+
435
+ def _populate_devices(self):
436
+ """
437
+ Read router.db or equivalent and populate ``devices`` dictionary
438
+ with objects.
439
+ """
440
+ metadata = parse_rancid_data(
441
+ self.rancid_root,
442
+ filename=self.rancid_db_file,
443
+ fields=self.device_fields,
444
+ config_dirname=self.config_dirname,
445
+ recurse_subdirs=self.recurse_subdirs,
446
+ )
447
+
448
+ objects = parse_devices(metadata, self.device_class)
449
+ self.devices = dict((d.nodeName, d) for d in objects)
450
+
451
+ def _populate_configs(self):
452
+ """NYI - Read configs"""
453
+ self.configs = _parse_config_files(self.devices.values(), self.rancid_root)
454
+
455
+ def _populate_data(self):
456
+ """NYI - Maybe keep the other metadata but how?"""
457
+ # self.data['routers.all'] = parse_rancid_file(root, RANCID_ALL_FILE)
458
+ # self.data['routers.down'] = parse_rancid_file(root, RANCID_DOWN_FILE)
459
+ # self.data['routers.up'] = parse_rancid_file(root, RANCID_UP_FILE)
460
+ pass
461
+
462
+ def __repr__(self):
463
+ return f"Rancid({self.rancid_root!r}, recurse_subdirs={self.recurse_subdirs})"