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.
- trigger/__init__.py +7 -0
- trigger/acl/__init__.py +32 -0
- trigger/acl/autoacl.py +70 -0
- trigger/acl/db.py +324 -0
- trigger/acl/dicts.py +357 -0
- trigger/acl/grammar.py +112 -0
- trigger/acl/ios.py +222 -0
- trigger/acl/junos.py +422 -0
- trigger/acl/models.py +118 -0
- trigger/acl/parser.py +168 -0
- trigger/acl/queue.py +296 -0
- trigger/acl/support.py +1431 -0
- trigger/acl/tools.py +746 -0
- trigger/bin/__init__.py +0 -0
- trigger/bin/acl.py +233 -0
- trigger/bin/acl_script.py +574 -0
- trigger/bin/aclconv.py +82 -0
- trigger/bin/check_access.py +93 -0
- trigger/bin/check_syntax.py +66 -0
- trigger/bin/fe.py +197 -0
- trigger/bin/find_access.py +191 -0
- trigger/bin/gnng.py +434 -0
- trigger/bin/gong.py +86 -0
- trigger/bin/load_acl.py +841 -0
- trigger/bin/load_config.py +18 -0
- trigger/bin/netdev.py +317 -0
- trigger/bin/optimizer.py +638 -0
- trigger/bin/run_cmds.py +18 -0
- trigger/changemgmt/__init__.py +352 -0
- trigger/changemgmt/bounce.py +57 -0
- trigger/cmds.py +1217 -0
- trigger/conf/__init__.py +94 -0
- trigger/conf/global_settings.py +674 -0
- trigger/contrib/__init__.py +7 -0
- trigger/exceptions.py +307 -0
- trigger/gorc.py +172 -0
- trigger/netdevices/__init__.py +1288 -0
- trigger/netdevices/loader.py +174 -0
- trigger/netscreen.py +1030 -0
- trigger/packages/__init__.py +6 -0
- trigger/packages/peewee.py +8084 -0
- trigger/rancid.py +463 -0
- trigger/tacacsrc.py +584 -0
- trigger/twister.py +2203 -0
- trigger/twister2.py +745 -0
- trigger/utils/__init__.py +88 -0
- trigger/utils/cli.py +349 -0
- trigger/utils/importlib.py +77 -0
- trigger/utils/network.py +157 -0
- trigger/utils/rcs.py +178 -0
- trigger/utils/templates.py +81 -0
- trigger/utils/url.py +78 -0
- trigger/utils/xmltodict.py +298 -0
- trigger-2.0.0.dist-info/METADATA +146 -0
- trigger-2.0.0.dist-info/RECORD +61 -0
- trigger-2.0.0.dist-info/WHEEL +5 -0
- trigger-2.0.0.dist-info/entry_points.txt +15 -0
- trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
- trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
- trigger-2.0.0.dist-info/top_level.txt +2 -0
- 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
|