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
@@ -0,0 +1,352 @@
1
+ """
2
+ Abstract interface to bounce windows and moratoria.
3
+ """
4
+
5
+ __author__ = "Jathan McCollum, Mark Thomas, Michael Shields"
6
+ __maintainer__ = "Jathan McCollum"
7
+ __email__ = "jathan.mccollum@teamaol.com"
8
+ __copyright__ = "Copyright 2006-2012, AOL Inc."
9
+
10
+ # Imports
11
+ from datetime import datetime, timedelta
12
+
13
+ from pytz import UTC, timezone
14
+
15
+ from trigger import exceptions
16
+ from trigger.conf import settings
17
+
18
+ # Constants
19
+ BOUNCE_VALUES = ("green", "yellow", "red")
20
+ BOUNCE_DEFAULT_TZ = timezone(settings.BOUNCE_DEFAULT_TZ)
21
+ BOUNCE_DEFAULT_COLOR = settings.BOUNCE_DEFAULT_COLOR
22
+ BOUNCE_VALUE_MAP = {
23
+ "red": 3,
24
+ "yellow": 2,
25
+ "green": 1,
26
+ }
27
+
28
+
29
+ # Exports
30
+ __all__ = ("BounceStatus", "BounceWindow", "bounce")
31
+
32
+
33
+ # Classes
34
+ class BounceStatus:
35
+ """
36
+ An object that represents a bounce window risk-level status.
37
+
38
+ + green: Low risk
39
+ + yellow: Medium risk
40
+ + red: High risk
41
+
42
+ Objects stringify to 'red', 'green', or 'yellow', and can be compared
43
+ against those strings. Objects can also be compared against each other.
44
+ 'red' > 'yellow' > 'green'.
45
+
46
+ >>> green = BounceStatus('green')
47
+ >>> yellow = BounceStatus('yellow')
48
+ >>> print green
49
+ green
50
+ >>> yellow > green
51
+ True
52
+
53
+ :param status_name:
54
+ The colored risk-level status name.
55
+ """
56
+
57
+ def __init__(self, status_name):
58
+ self.status_name = status_name
59
+ self.value = BOUNCE_VALUES.index(status_name)
60
+
61
+ def __repr__(self):
62
+ return f"<{self.__class__.__name__}: {self.status_name}>"
63
+
64
+ def __str__(self):
65
+ return self.status_name
66
+
67
+ def _get_compare_value(self, other):
68
+ """Helper to get comparison value from self or other."""
69
+ try:
70
+ return other.value
71
+ except AttributeError:
72
+ # Other object is not a BounceStatus; maybe it's a string.
73
+ return BounceStatus(other).value
74
+
75
+ def __eq__(self, other):
76
+ try:
77
+ return self.value == self._get_compare_value(other)
78
+ except (ValueError, KeyError):
79
+ return False
80
+
81
+ def __ne__(self, other):
82
+ return not self.__eq__(other)
83
+
84
+ def __lt__(self, other):
85
+ return self.value < self._get_compare_value(other)
86
+
87
+ def __le__(self, other):
88
+ return self.value <= self._get_compare_value(other)
89
+
90
+ def __gt__(self, other):
91
+ return self.value > self._get_compare_value(other)
92
+
93
+ def __ge__(self, other):
94
+ return self.value >= self._get_compare_value(other)
95
+
96
+ def __hash__(self):
97
+ return hash((self.status_name, self.value))
98
+
99
+
100
+ class BounceWindow:
101
+ """
102
+ Build a bounce window of 24 `~trigger.changemgmt.BounceStatus` objects.
103
+
104
+ You may either specify your own list of 24
105
+ `~trigger.changemgmt.BounceStatus` objects using ``status_by_hour``, or you
106
+ may omit this argument and specify your 'green', 'yellow', and 'red'
107
+ risk levels by using hyphenated and comma-separated text strings.
108
+
109
+ You may use digits ("14") or hyphenated ranges ("0-5") and may join these
110
+ together using a comma (",") with or without spacing separating them. For
111
+ example "0-5, 14" will be parsed into ``[0, 1, 2, 3, 4, 5, 14]``.
112
+
113
+ The `default` color is used to fill in the gaps between the other colors,
114
+ so that the total is always 24 in the resultant list status objects.
115
+
116
+ >>> b = BounceWindow(green='0-3, 23', red='10', default='yellow')
117
+ >>> b.status()
118
+ <BounceStatus: yellow>
119
+ >>> b.next_ok('green')
120
+ datetime.datetime(2012, 12, 5, 4, 0, tzinfo=<UTC>)
121
+ >>> b.dump()
122
+ {0: <BounceStatus: green>,
123
+ 1: <BounceStatus: green>,
124
+ 2: <BounceStatus: green>,
125
+ 3: <BounceStatus: green>,
126
+ 4: <BounceStatus: yellow>,
127
+ 5: <BounceStatus: yellow>,
128
+ 6: <BounceStatus: yellow>,
129
+ 7: <BounceStatus: yellow>,
130
+ 8: <BounceStatus: yellow>,
131
+ 9: <BounceStatus: yellow>,
132
+ 10: <BounceStatus: red>,
133
+ 11: <BounceStatus: yellow>,
134
+ 12: <BounceStatus: yellow>,
135
+ 13: <BounceStatus: yellow>,
136
+ 14: <BounceStatus: yellow>,
137
+ 15: <BounceStatus: yellow>,
138
+ 16: <BounceStatus: yellow>,
139
+ 17: <BounceStatus: yellow>,
140
+ 18: <BounceStatus: yellow>,
141
+ 19: <BounceStatus: yellow>,
142
+ 20: <BounceStatus: yellow>,
143
+ 21: <BounceStatus: yellow>,
144
+ 22: <BounceStatus: yellow>,
145
+ 23: <BounceStatus: green>}
146
+
147
+ You may modify the global default fallback color by setting
148
+ :setting:`BOUNCE_DEFAULT_COLOR` in your ``settings.py``.
149
+
150
+ Although the query API is generic and could accomodate any sort of bounce
151
+ window policy, this constructor knows only about AOL's bounce windows,
152
+ which operate on "US/Eastern" time (worldwide), always change on hour
153
+ boundaries, and are the same every day. If that ever changes, only this
154
+ class will need to be updated.
155
+
156
+ End-users are not expected to create new ``BounceWindow`` objects;
157
+ instead, use `~trigger.changemgmt.bounce()` or
158
+ `~trigger.netdevices.NetDevice.bounce` to get an object,
159
+ then query its methods.
160
+
161
+ :param status_by_hour:
162
+ (Optional) A list of 24 `~trigger.changemgmt.BounceStatus` objects.
163
+
164
+ :param green:
165
+ Representative string of hours.
166
+
167
+ :param yellow:
168
+ Representative string of hours.
169
+
170
+ :param red:
171
+ Representative string of hours.
172
+
173
+ :param default:
174
+ The color used to fill in the gaps between other risk levels.
175
+ """
176
+
177
+ # Prepopulate these objects to save a little horsepower
178
+ BOUNCE_STATUS = {n: BounceStatus(n) for n in BOUNCE_VALUES}
179
+
180
+ def __init__(
181
+ self,
182
+ status_by_hour=None,
183
+ green=None,
184
+ yellow=None,
185
+ red=None,
186
+ default=BOUNCE_DEFAULT_COLOR,
187
+ ):
188
+ # Parse the hours specified into BounceWindows
189
+ self._green = green
190
+ self._yellow = yellow
191
+ self._red = red
192
+ self.default = default
193
+ hours = {
194
+ "green": self._parse_hours(green),
195
+ "yellow": self._parse_hours(yellow),
196
+ "red": self._parse_hours(red),
197
+ }
198
+ self.hours = hours
199
+ self.hour_map = self._map_bounces(self.hours, default=default)
200
+
201
+ # Allow for providing status_by_hour, but don't rely on it
202
+ if status_by_hour is None:
203
+ # Python 3: dict.values() returns a view, not a list
204
+ # We need a list indexed by hour (0-23) for subscripting
205
+ status_by_hour = [self.hour_map[i] for i in range(24)]
206
+
207
+ if not len(status_by_hour) == 24:
208
+ msg = "There must be exactly 24 hours defined for this BounceWindow."
209
+ raise exceptions.InvalidBounceWindow(msg)
210
+
211
+ # Make sure each status occurs at least once, or next_ok()
212
+ # might never return.
213
+ for status in BOUNCE_VALUE_MAP:
214
+ if status not in status_by_hour:
215
+ msg = f"{status} risk-level must be defined!"
216
+ raise exceptions.InvalidBounceWindow(msg)
217
+ self._status_by_hour = status_by_hour
218
+
219
+ def __repr__(self):
220
+ return f"{self.__class__.__name__}(green={self._green!r}, yellow={self._yellow!r}, red={self._red!r}, default={self.default!r})"
221
+
222
+ def status(self, when=None):
223
+ """
224
+ Return a `~trigger.changemgmt.BounceStatus` object for the specified
225
+ time or now.
226
+
227
+ :param when:
228
+ A ``datetime`` object.
229
+ """
230
+ when_et = (when or datetime.now(tz=UTC)).astimezone(BOUNCE_DEFAULT_TZ)
231
+
232
+ # Return default during weekend moratorium, otherwise look it up.
233
+ if (
234
+ when_et.weekday() >= 5
235
+ or when_et.weekday() == 0
236
+ and when_et.hour < 4
237
+ or when_et.weekday() == 4
238
+ and when_et.hour >= 12
239
+ ):
240
+ return BounceStatus(BOUNCE_DEFAULT_COLOR)
241
+ else:
242
+ return self._status_by_hour[when_et.hour]
243
+
244
+ def next_ok(self, status, when=None):
245
+ """
246
+ Return the next time at or after the specified time (default now) that
247
+ it the bounce status will be at equal to or less than the given status.
248
+
249
+ For example, ``next_ok('yellow')`` will return the time that the bounce
250
+ window becomes 'yellow' or 'green'. Returns UTC time.
251
+
252
+ :param status:
253
+ The colored risk-level status name.
254
+
255
+ :param when:
256
+ A ``datetime`` object.
257
+ """
258
+ when = when or datetime.now(tz=UTC)
259
+ if self.status(when) <= status:
260
+ return when.astimezone(UTC)
261
+ when = datetime(when.year, when.month, when.day, when.hour, tzinfo=UTC)
262
+ when += timedelta(hours=1)
263
+ while self.status(when) > status:
264
+ when += timedelta(hours=1)
265
+ return when
266
+
267
+ def dump(self):
268
+ """Dump a mapping of hour to status"""
269
+ return self.hour_map
270
+
271
+ def _get_bounces(self, hours, color):
272
+ """
273
+ Return a list of hours mapped to bounce objects
274
+
275
+ :param hours:
276
+ A list of integers representing hours
277
+
278
+ :param color:
279
+ The risk-level color name.
280
+ """
281
+ return zip(hours, [self.BOUNCE_STATUS[color]] * len(hours))
282
+
283
+ def _map_bounces(self, hdict, default=None):
284
+ """
285
+ Map a dictionary of colors and hours into a dictionary keyed by hour and
286
+ the appropriate BounceStatus object.
287
+
288
+ :param hdict:
289
+ Dictionary mapping of hours to status objects.
290
+
291
+ :param default:
292
+ The default bounce status name.
293
+ """
294
+ if default is None:
295
+ default = self.default
296
+ status = []
297
+ for color, hours in hdict.items():
298
+ status.extend(self._get_bounces(hours, color))
299
+
300
+ # Fill in missing keys with the default color
301
+ missing = [i for i in range(24) if i not in dict(status)]
302
+ if missing:
303
+ status.extend(self._get_bounces(missing, default))
304
+
305
+ return dict(status)
306
+
307
+ def _parse_hours(self, hs):
308
+ """
309
+ Parse hour strings into lists of hours. Or if a list of hours is passed
310
+ in, just return it as is.
311
+
312
+ >>> parse_hours('0-3, 23')
313
+ [0, 1, 2, 3, 23]
314
+ parse_hours(range(3))
315
+ [0, 1, 2]
316
+
317
+ :param hs:
318
+ A string representation of hours.
319
+ """
320
+ myhours = []
321
+ if hs is None:
322
+ return myhours
323
+
324
+ # Assume it's a list of integers?
325
+ if isinstance(hs, list):
326
+ return hs
327
+
328
+ # Split the pattern by ',' and then trim whitespace, carve hyphenated
329
+ # ranges out and then return a list of hours. More error-checking
330
+ # Coming "Soon".
331
+ blocks = hs.split(",")
332
+ for block in blocks:
333
+ # Clean whitespace and split on hyphens
334
+ parts = block.strip().split("-")
335
+ parts = [int(p) for p in parts] # make ints
336
+ if len(parts) == 1: # no hyphen
337
+ parts.append(parts[0] + 1)
338
+ elif len(parts) == 2:
339
+ parts[1] += 1
340
+ else:
341
+ raise RuntimeError("This should not have happened!")
342
+
343
+ # Return the individual hours
344
+ for i in range(*parts):
345
+ myhours.append(i)
346
+
347
+ return myhours
348
+
349
+
350
+ # Load ``bounce()`` from the location of ``bounce.py`` or provide a dummy that
351
+ # returns a hard-coded bounce window
352
+ from .bounce import bounce
@@ -0,0 +1,57 @@
1
+ """
2
+ This module controls how bounce windows get auto-applied to network devices.
3
+
4
+ This is primarily used by `~trigger.changemgmt`.
5
+
6
+ No changes should be made to this module. You must specify the path to the
7
+ bounce logic inside of ``settings.py`` as :setting:`BOUNCE_FILE`. This will be
8
+ exported as ``bounce()`` so that the module path for the :func:`bounce()`
9
+ function will still be `~trigger.changemgmt.bounce`.
10
+
11
+ This trickery allows us to keep the business-logic for how bounce windows are
12
+ mapped to devices out of the Trigger packaging.
13
+
14
+ If you do not specify a location for :setting:`BOUNCE_FILE`` or the module
15
+ cannot be loaded, then a default :func:`bounce()` function ill be used.
16
+ """
17
+
18
+ __author__ = "Jathan McCollum"
19
+ __maintainer__ = "Jathan McCollum"
20
+ __email__ = "jathan.mccollum@teamaol.com"
21
+ __copyright__ = "Copyright 2012, AOL Inc."
22
+ __version__ = "0.1"
23
+
24
+
25
+ # Imports
26
+ import warnings
27
+
28
+ from trigger.conf import settings
29
+ from trigger.utils.importlib import import_module_from_path
30
+
31
+ # Exports
32
+ __all__ = ("bounce",)
33
+
34
+
35
+ # Load ``bounce()`` from the location of ``bounce.py``
36
+ bounce_mpath = settings.BOUNCE_FILE
37
+ try:
38
+ _bounce_module = import_module_from_path(bounce_mpath, "_bounce_module")
39
+ from _bounce_module import bounce
40
+ except ImportError:
41
+ msg = f"Bounce mappings could not be found in {bounce_mpath}. using default!"
42
+ warnings.warn(msg, RuntimeWarning)
43
+ from . import BounceWindow
44
+
45
+ DEFAULT_BOUNCE = BounceWindow(green="5-7", yellow="0-4, 8-15", red="16-23")
46
+
47
+ def bounce(device, default=DEFAULT_BOUNCE):
48
+ """
49
+ Return the bounce window for a given device.
50
+
51
+ :param device:
52
+ A `~trigger.netdevices.NetDevice` object.
53
+
54
+ :param default:
55
+ A `~trigger.changemgmt.BounceWindow` object.
56
+ """
57
+ return default