trigger 2.2.2__tar.gz → 2.2.4__tar.gz
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-2.2.2/trigger.egg-info → trigger-2.2.4}/PKG-INFO +12 -25
- {trigger-2.2.2 → trigger-2.2.4}/README.md +8 -24
- {trigger-2.2.2 → trigger-2.2.4}/pyproject.toml +10 -58
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_acl.py +4 -9
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_acl_queue.py +1 -1
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_changemgmt.py +0 -7
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_except.py +1 -1
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_netdevices.py +0 -5
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_scripts.py +0 -5
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_tacacsrc.py +26 -7
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_templates.py +3 -3
- trigger-2.2.4/tests/test_twister.py +255 -0
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_twister2.py +0 -6
- {trigger-2.2.2 → trigger-2.2.4}/trigger/__init__.py +2 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/__init__.py +2 -9
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/autoacl.py +3 -9
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/db.py +8 -13
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/dicts.py +2 -8
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/grammar.py +20 -25
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/ios.py +45 -52
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/junos.py +76 -77
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/models.py +1 -1
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/parser.py +41 -40
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/queue.py +2 -9
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/support.py +142 -152
- {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/tools.py +58 -61
- trigger-2.2.4/trigger/bin/__init__.py +1 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/acl.py +7 -9
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/acl_script.py +8 -12
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/aclconv.py +2 -4
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/check_access.py +4 -6
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/check_syntax.py +3 -5
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/fe.py +5 -8
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/find_access.py +9 -11
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/gnng.py +5 -8
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/gong.py +3 -5
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/load_acl.py +27 -29
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/load_config.py +1 -1
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/netdev.py +5 -6
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/optimizer.py +24 -24
- {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/run_cmds.py +1 -1
- {trigger-2.2.2 → trigger-2.2.4}/trigger/changemgmt/__init__.py +25 -30
- {trigger-2.2.2 → trigger-2.2.4}/trigger/changemgmt/bounce.py +1 -9
- {trigger-2.2.2 → trigger-2.2.4}/trigger/cmds.py +40 -46
- {trigger-2.2.2 → trigger-2.2.4}/trigger/conf/__init__.py +6 -10
- {trigger-2.2.2 → trigger-2.2.4}/trigger/conf/global_settings.py +7 -4
- {trigger-2.2.2 → trigger-2.2.4}/trigger/contrib/__init__.py +1 -1
- {trigger-2.2.2 → trigger-2.2.4}/trigger/exceptions.py +15 -22
- {trigger-2.2.2 → trigger-2.2.4}/trigger/gorc.py +4 -4
- {trigger-2.2.2 → trigger-2.2.4}/trigger/netdevices/__init__.py +73 -69
- {trigger-2.2.2 → trigger-2.2.4}/trigger/netdevices/loader.py +9 -7
- {trigger-2.2.2 → trigger-2.2.4}/trigger/netscreen.py +98 -94
- {trigger-2.2.2 → trigger-2.2.4}/trigger/rancid.py +10 -16
- {trigger-2.2.2 → trigger-2.2.4}/trigger/tacacsrc.py +35 -38
- {trigger-2.2.2 → trigger-2.2.4}/trigger/twister.py +128 -87
- {trigger-2.2.2 → trigger-2.2.4}/trigger/twister2.py +40 -39
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/__init__.py +3 -8
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/cli.py +14 -19
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/importlib.py +1 -1
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/network.py +6 -6
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/rcs.py +10 -15
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/templates.py +7 -15
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/url.py +1 -7
- {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/xmltodict.py +5 -9
- {trigger-2.2.2 → trigger-2.2.4/trigger.egg-info}/PKG-INFO +12 -25
- {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/requires.txt +4 -0
- {trigger-2.2.2 → trigger-2.2.4}/twisted/plugins/trigger_xmlrpc.py +0 -2
- trigger-2.2.2/tests/test_twister.py +0 -34
- trigger-2.2.2/trigger/bin/__init__.py +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/AUTHORS.md +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/LICENSE.md +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/setup.cfg +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_acl_db.py +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/tests/test_utils.py +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger/packages/__init__.py +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger/packages/peewee.py +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/SOURCES.txt +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/dependency_links.txt +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/entry_points.txt +0 -0
- {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trigger
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.4
|
|
4
4
|
Summary: Network automation toolkit for managing network devices
|
|
5
5
|
Author-email: Jathan McCollum <jathan@gmail.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -33,12 +33,14 @@ Requires-Dist: pytest>=7.4.0; extra == "dev"
|
|
|
33
33
|
Requires-Dist: pytest-twisted>=1.14.0; extra == "dev"
|
|
34
34
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
35
35
|
Requires-Dist: python-semantic-release>=9.0.0; extra == "dev"
|
|
36
|
+
Provides-Extra: docs
|
|
37
|
+
Requires-Dist: sphinx>=7.0; extra == "docs"
|
|
38
|
+
Requires-Dist: sphinx_rtd_theme>=2.0; extra == "docs"
|
|
36
39
|
Dynamic: license-file
|
|
37
40
|
|
|
38
41
|
# What is Trigger?
|
|
39
42
|
|
|
40
|
-
[](https://gitter.im/trigger/trigger?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
43
|
+
[](https://github.com/trigger/trigger/actions/workflows/ci.yml)
|
|
42
44
|
|
|
43
45
|
Trigger is a robust network automation toolkit written in Python that was
|
|
44
46
|
designed for interfacing with network devices and managing network
|
|
@@ -79,16 +81,9 @@ network devices with ease. Here are some of things that make Trigger tick:
|
|
|
79
81
|
+ Flexible access-list & firewall policy parser that can test access if access
|
|
80
82
|
is permitted, or easily convert ACLs from one format to another.
|
|
81
83
|
+ Detailed support for timezones and maintenance windows.
|
|
84
|
+
+ Import your metadata from an existing [RANCID](http://shrubbery.net/rancid/) installation or a CSV file to get up-and-running quickly.
|
|
82
85
|
+ A suite of tools for simplifying many common tasks.
|
|
83
86
|
|
|
84
|
-
New in version 1.2:
|
|
85
|
-
|
|
86
|
-
+ Import your metadata from an existing [RANCID](http://shrubbery.net/rancid/) installation to get up-and-running quickly!
|
|
87
|
-
|
|
88
|
-
New in version 1.3:
|
|
89
|
-
|
|
90
|
-
+ Import your metadata from a CSV file and get up-and-running even quicker!
|
|
91
|
-
|
|
92
87
|
## Getting Started
|
|
93
88
|
|
|
94
89
|
The best way to get started is to read the documentation hosted by [Read the
|
|
@@ -142,21 +137,13 @@ See the [Migration Guide](https://trigger.readthedocs.io/en/latest/migration.htm
|
|
|
142
137
|
|
|
143
138
|
### Before you begin
|
|
144
139
|
|
|
145
|
-
+ The [
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
+ The [master](https://github.com/trigger/trigger/tree/master) branch is
|
|
150
|
-
the stable branch, and will reflect the latest production-ready changes. It
|
|
151
|
-
is recommended that this is the branch you use if you are installing Trigger
|
|
152
|
-
for the first time.
|
|
153
|
-
+ Each point release of Trigger is maintained as a [tag branch](https://github.com/trigger/trigger/tags). If you require a
|
|
140
|
+
+ The [main](https://github.com/trigger/trigger/tree/main) branch is the
|
|
141
|
+
primary branch for all development and releases. All pull requests target
|
|
142
|
+
`main`.
|
|
143
|
+
+ Each point release of Trigger is maintained as a [tag](https://github.com/trigger/trigger/tags). If you require a
|
|
154
144
|
specific Trigger version, please refer to these.
|
|
155
145
|
|
|
156
146
|
### Get in touch!
|
|
157
147
|
|
|
158
|
-
If you run into any snags, have questions, feedback, or just want to talk shop
|
|
159
|
-
[
|
|
160
|
-
|
|
161
|
-
**Pro tip**: Find us on IRC at `#trigger` on Freenode
|
|
162
|
-
([irc://irc.freenode.net/trigger](irc://irc.freenode.net/trigger)).
|
|
148
|
+
If you run into any snags, have questions, feedback, or just want to talk shop,
|
|
149
|
+
please open an issue on [GitHub Issues](https://github.com/trigger/trigger/issues).
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# What is Trigger?
|
|
2
2
|
|
|
3
|
-
[](https://gitter.im/trigger/trigger?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
3
|
+
[](https://github.com/trigger/trigger/actions/workflows/ci.yml)
|
|
5
4
|
|
|
6
5
|
Trigger is a robust network automation toolkit written in Python that was
|
|
7
6
|
designed for interfacing with network devices and managing network
|
|
@@ -42,16 +41,9 @@ network devices with ease. Here are some of things that make Trigger tick:
|
|
|
42
41
|
+ Flexible access-list & firewall policy parser that can test access if access
|
|
43
42
|
is permitted, or easily convert ACLs from one format to another.
|
|
44
43
|
+ Detailed support for timezones and maintenance windows.
|
|
44
|
+
+ Import your metadata from an existing [RANCID](http://shrubbery.net/rancid/) installation or a CSV file to get up-and-running quickly.
|
|
45
45
|
+ A suite of tools for simplifying many common tasks.
|
|
46
46
|
|
|
47
|
-
New in version 1.2:
|
|
48
|
-
|
|
49
|
-
+ Import your metadata from an existing [RANCID](http://shrubbery.net/rancid/) installation to get up-and-running quickly!
|
|
50
|
-
|
|
51
|
-
New in version 1.3:
|
|
52
|
-
|
|
53
|
-
+ Import your metadata from a CSV file and get up-and-running even quicker!
|
|
54
|
-
|
|
55
47
|
## Getting Started
|
|
56
48
|
|
|
57
49
|
The best way to get started is to read the documentation hosted by [Read the
|
|
@@ -105,21 +97,13 @@ See the [Migration Guide](https://trigger.readthedocs.io/en/latest/migration.htm
|
|
|
105
97
|
|
|
106
98
|
### Before you begin
|
|
107
99
|
|
|
108
|
-
+ The [
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
+ The [master](https://github.com/trigger/trigger/tree/master) branch is
|
|
113
|
-
the stable branch, and will reflect the latest production-ready changes. It
|
|
114
|
-
is recommended that this is the branch you use if you are installing Trigger
|
|
115
|
-
for the first time.
|
|
116
|
-
+ Each point release of Trigger is maintained as a [tag branch](https://github.com/trigger/trigger/tags). If you require a
|
|
100
|
+
+ The [main](https://github.com/trigger/trigger/tree/main) branch is the
|
|
101
|
+
primary branch for all development and releases. All pull requests target
|
|
102
|
+
`main`.
|
|
103
|
+
+ Each point release of Trigger is maintained as a [tag](https://github.com/trigger/trigger/tags). If you require a
|
|
117
104
|
specific Trigger version, please refer to these.
|
|
118
105
|
|
|
119
106
|
### Get in touch!
|
|
120
107
|
|
|
121
|
-
If you run into any snags, have questions, feedback, or just want to talk shop
|
|
122
|
-
[
|
|
123
|
-
|
|
124
|
-
**Pro tip**: Find us on IRC at `#trigger` on Freenode
|
|
125
|
-
([irc://irc.freenode.net/trigger](irc://irc.freenode.net/trigger)).
|
|
108
|
+
If you run into any snags, have questions, feedback, or just want to talk shop,
|
|
109
|
+
please open an issue on [GitHub Issues](https://github.com/trigger/trigger/issues).
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "trigger"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.4"
|
|
8
8
|
description = "Network automation toolkit for managing network devices"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
@@ -42,6 +42,10 @@ dev = [
|
|
|
42
42
|
"ruff>=0.1.0",
|
|
43
43
|
"python-semantic-release>=9.0.0",
|
|
44
44
|
]
|
|
45
|
+
docs = [
|
|
46
|
+
"sphinx>=7.0",
|
|
47
|
+
"sphinx_rtd_theme>=2.0",
|
|
48
|
+
]
|
|
45
49
|
|
|
46
50
|
[project.scripts]
|
|
47
51
|
acl = "trigger.bin.acl:main"
|
|
@@ -106,69 +110,15 @@ select = [
|
|
|
106
110
|
"UP", # pyupgrade - Python 3 modernization
|
|
107
111
|
"W", # pycodestyle warnings
|
|
108
112
|
]
|
|
113
|
+
extend-select = [
|
|
114
|
+
"D401", # non-imperative-mood (not in google convention, enabled explicitly)
|
|
115
|
+
]
|
|
109
116
|
ignore = [
|
|
110
117
|
# --- Formatter conflicts ---
|
|
111
118
|
"COM812", # trailing-comma-missing (handled by ruff formatter)
|
|
112
119
|
|
|
113
120
|
# --- Kept from v2.0.0 baseline ---
|
|
114
|
-
"E402", # module-import-not-at-top-of-file
|
|
115
121
|
"E501", # line-too-long (handled by formatter)
|
|
116
|
-
"E721", # type-comparison (existing code style)
|
|
117
|
-
"E722", # bare-except (intentional in many places)
|
|
118
|
-
"E741", # ambiguous-variable-name (existing code style)
|
|
119
|
-
"F401", # unused-import (may be used dynamically)
|
|
120
|
-
"F402", # import-shadowed-by-loop-var
|
|
121
|
-
"F403", # undefined-local-with-import-star
|
|
122
|
-
"F405", # undefined-local-with-import-star-usage
|
|
123
|
-
"F523", # string-dot-format-extra-positional-arguments
|
|
124
|
-
"F811", # redefined-while-unused
|
|
125
|
-
"F821", # undefined-name
|
|
126
|
-
"UP031", # printf-string-formatting
|
|
127
|
-
|
|
128
|
-
# --- Bugbear exceptions ---
|
|
129
|
-
"B016", # raise-literal (used intentionally in tests)
|
|
130
|
-
"B018", # useless-expression (test assertions)
|
|
131
|
-
|
|
132
|
-
# --- Pre-commit specific ignores ---
|
|
133
|
-
"PLW1641", # __hash__ implementation (false positive, requires __eq__ impl)
|
|
134
|
-
"PT028", # pytest parameter defaults (false positive, not pytest functions)
|
|
135
|
-
|
|
136
|
-
# --- Docstring exceptions (not enforcing coverage/style) ---
|
|
137
|
-
"D100", # undocumented-public-module
|
|
138
|
-
"D101", # undocumented-public-class
|
|
139
|
-
"D102", # undocumented-public-method
|
|
140
|
-
"D103", # undocumented-public-function
|
|
141
|
-
"D104", # undocumented-public-package
|
|
142
|
-
"D105", # undocumented-magic-method
|
|
143
|
-
"D107", # undocumented-public-init
|
|
144
|
-
"D200", # unnecessary-multiline-docstring
|
|
145
|
-
"D205", # missing-blank-line-after-summary
|
|
146
|
-
"D401", # non-imperative-mood
|
|
147
|
-
"D402", # signature-in-docstring
|
|
148
|
-
"D404", # docstring-starts-with-this
|
|
149
|
-
|
|
150
|
-
# --- Complexity (deep refactoring territory) ---
|
|
151
|
-
"PLR0911", # too-many-return-statements
|
|
152
|
-
"PLR0912", # too-many-branches
|
|
153
|
-
"PLR0913", # too-many-arguments
|
|
154
|
-
"PLR0915", # too-many-statements
|
|
155
|
-
"PLR2004", # magic-value-comparison
|
|
156
|
-
"PLW0603", # global-statement (parser pattern)
|
|
157
|
-
|
|
158
|
-
# --- Security false positives for network toolkit ---
|
|
159
|
-
"S101", # assert (used in tests)
|
|
160
|
-
"S105", # hardcoded-password-string (false positives)
|
|
161
|
-
"S110", # try-except-pass
|
|
162
|
-
"S112", # try-except-continue
|
|
163
|
-
"S314", # suspicious-xml-element-tree-usage
|
|
164
|
-
"S603", # subprocess-without-shell-equals-true
|
|
165
|
-
"S605", # start-process-with-a-shell (fix manually)
|
|
166
|
-
"S606", # start-process-with-no-shell
|
|
167
|
-
"S608", # hardcoded-sql-expression
|
|
168
|
-
|
|
169
|
-
# --- Other ---
|
|
170
|
-
"RUF012", # mutable-class-default (too many in device metadata)
|
|
171
|
-
"SLF001", # private-member-access (common in Twisted patterns)
|
|
172
122
|
]
|
|
173
123
|
|
|
174
124
|
[tool.ruff.lint.pydocstyle]
|
|
@@ -233,6 +183,8 @@ minor_tags = ["feat"]
|
|
|
233
183
|
patch_tags = ["fix", "perf"]
|
|
234
184
|
|
|
235
185
|
[tool.semantic_release.changelog]
|
|
186
|
+
|
|
187
|
+
[tool.semantic_release.changelog.default_templates]
|
|
236
188
|
changelog_file = "CHANGELOG.md"
|
|
237
189
|
|
|
238
190
|
[tool.semantic_release.branches.main]
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/python
|
|
2
2
|
|
|
3
|
-
__author__ = "Jathan McCollum, Michael Shields"
|
|
4
|
-
__maintainer__ = "Jathan McCollum"
|
|
5
|
-
__copyright__ = "Copyright 2005-2011 AOL Inc.; 2013 Salesforce.com"
|
|
6
|
-
__version__ = "2.0"
|
|
7
|
-
|
|
8
3
|
import contextlib
|
|
9
4
|
import unittest
|
|
10
5
|
from io import StringIO
|
|
@@ -72,7 +67,7 @@ class CheckRangeList(unittest.TestCase):
|
|
|
72
67
|
"""Check RangeList behavior as a container type."""
|
|
73
68
|
r = acl.RangeList([1, (3, 6)])
|
|
74
69
|
self.assertTrue(1 in r)
|
|
75
|
-
self.assertTrue(5 in r)
|
|
70
|
+
self.assertTrue(5 in r) # noqa: PLR2004
|
|
76
71
|
self.assertTrue(0 not in r)
|
|
77
72
|
r = acl.RangeList([acl.TIP("10/8"), acl.TIP("172.16/12")])
|
|
78
73
|
self.assertTrue(acl.TIP("10.1.1.1") in r)
|
|
@@ -695,7 +690,7 @@ class CheckMiscIOS(unittest.TestCase):
|
|
|
695
690
|
# Python 3: map() returns an iterator, convert to list for comparison
|
|
696
691
|
self.assertEqual(
|
|
697
692
|
t.output_ios(),
|
|
698
|
-
list(map(lambda x: "permit icmp any any %d" % x, types)),
|
|
693
|
+
list(map(lambda x: "permit icmp any any %d" % x, types)), # noqa: UP031
|
|
699
694
|
)
|
|
700
695
|
|
|
701
696
|
def testCounterSuppression(self):
|
|
@@ -748,8 +743,8 @@ class CheckTriggerIP(unittest.TestCase):
|
|
|
748
743
|
# Until we fix inactive testing, this is legit
|
|
749
744
|
self.assertTrue(obj in self.test_net)
|
|
750
745
|
|
|
751
|
-
def
|
|
752
|
-
"""Test an inactive IP object"""
|
|
746
|
+
def testInactiveAndNegated(self):
|
|
747
|
+
"""Test an inactive and negated IP object"""
|
|
753
748
|
test = "inactive: 1.2.3.4/32 except"
|
|
754
749
|
obj = acl.TIP(test)
|
|
755
750
|
self.assertEqual(str(obj), test)
|
|
@@ -4,13 +4,6 @@
|
|
|
4
4
|
Tests for bounce windows and the stuff that goes with them.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
__author__ = "Jathan McCollum, Michael Shields"
|
|
8
|
-
__maintainer__ = "Jathan McCollum"
|
|
9
|
-
__email__ = "jmccollum@salesforce.com"
|
|
10
|
-
__copyright__ = "Copyright 2013 Salesforce.com"
|
|
11
|
-
__version__ = "2.0"
|
|
12
|
-
|
|
13
|
-
|
|
14
7
|
# Make sure we load the mock redis library
|
|
15
8
|
from utils import mock_redis
|
|
16
9
|
|
|
@@ -7,11 +7,6 @@ This uses the mockups of netdevices.xml, acls.db, and autoacls.py in
|
|
|
7
7
|
tests/data.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__author__ = "Jathan McCollum, Michael Shields"
|
|
11
|
-
__maintainer__ = "Jathan McCollum"
|
|
12
|
-
__copyright__ = "Copyright 2005-2011 AOL Inc.; 2013 Salesforce.com"
|
|
13
|
-
__version__ = "2.0"
|
|
14
|
-
|
|
15
10
|
import unittest
|
|
16
11
|
|
|
17
12
|
# Make sure we load the mock redis library
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python
|
|
2
2
|
|
|
3
|
-
__author__ = "Jathan McCollum, Michael Shields"
|
|
4
|
-
__maintainer__ = "Jathan McCollum"
|
|
5
|
-
__copyright__ = "Copyright 2005-2011 AOL Inc.; 2013 Salesforce.com"
|
|
6
|
-
__version__ = "2.0.1"
|
|
7
|
-
|
|
8
|
-
|
|
9
3
|
import os
|
|
10
4
|
import tempfile
|
|
11
5
|
import unittest
|
|
@@ -13,7 +7,7 @@ from pathlib import Path
|
|
|
13
7
|
from unittest.mock import patch
|
|
14
8
|
|
|
15
9
|
from trigger.conf import settings
|
|
16
|
-
from trigger.tacacsrc import Credentials, Tacacsrc
|
|
10
|
+
from trigger.tacacsrc import Credentials, Tacacsrc, convert_tacacsrc
|
|
17
11
|
|
|
18
12
|
# Constants
|
|
19
13
|
aol = Credentials("jschmoe", "abc123", "aol")
|
|
@@ -147,6 +141,31 @@ class TacacsrcTest(unittest.TestCase):
|
|
|
147
141
|
new_perms = self._get_perms(fname)
|
|
148
142
|
self.assertNotEqual(new_perms, RIGHT_PERMS)
|
|
149
143
|
|
|
144
|
+
def test_convert_tacacsrc(self):
|
|
145
|
+
"""Test that convert_tacacsrc reads old creds and writes via GPG."""
|
|
146
|
+
captured = {}
|
|
147
|
+
|
|
148
|
+
def spy_encrypt_and_write(self):
|
|
149
|
+
captured["creds"] = dict(self.creds)
|
|
150
|
+
captured["use_gpg"] = self.use_gpg
|
|
151
|
+
captured["file_name"] = self.file_name
|
|
152
|
+
|
|
153
|
+
with (
|
|
154
|
+
patch("trigger.tacacsrc.Tacacsrc", MockTacacsrc),
|
|
155
|
+
patch.object(MockTacacsrc, "_encrypt_and_write", spy_encrypt_and_write),
|
|
156
|
+
patch.object(MockTacacsrc, "_update_perms"),
|
|
157
|
+
):
|
|
158
|
+
convert_tacacsrc()
|
|
159
|
+
|
|
160
|
+
# GPG write path should have been reached
|
|
161
|
+
self.assertIn("creds", captured)
|
|
162
|
+
# All original credentials should be preserved
|
|
163
|
+
for name, value in ALL_CREDS:
|
|
164
|
+
self.assertEqual(captured["creds"][name], value)
|
|
165
|
+
# Should be in GPG mode targeting the .gpg file
|
|
166
|
+
self.assertTrue(captured["use_gpg"])
|
|
167
|
+
self.assertEqual(captured["file_name"], settings.TACACSRC + ".gpg")
|
|
168
|
+
|
|
150
169
|
|
|
151
170
|
if __name__ == "__main__":
|
|
152
171
|
unittest.main()
|
|
@@ -3,7 +3,7 @@ from io import StringIO
|
|
|
3
3
|
|
|
4
4
|
from trigger.cmds import Commando
|
|
5
5
|
from trigger.netdevices import NetDevices
|
|
6
|
-
from trigger.utils.templates import *
|
|
6
|
+
from trigger.utils.templates import * # noqa: F403
|
|
7
7
|
|
|
8
8
|
# Constants
|
|
9
9
|
DEVICE_NAME = "test1-abc.net.aol.com"
|
|
@@ -112,12 +112,12 @@ class CheckTemplates(unittest.TestCase):
|
|
|
112
112
|
|
|
113
113
|
def testTemplatePath(self):
|
|
114
114
|
"""Test that template path is correct."""
|
|
115
|
-
t_path = get_template_path("show clock", dev_type="cisco_ios")
|
|
115
|
+
t_path = get_template_path("show clock", dev_type="cisco_ios") # noqa: F405
|
|
116
116
|
self.assertTrue("vendor/ntc_templates/cisco_ios_show_clock.template" in t_path)
|
|
117
117
|
|
|
118
118
|
def testGetTextFsmObject(self):
|
|
119
119
|
"""Test that we get structured data back from cli output."""
|
|
120
|
-
data = get_textfsm_object(self.re_table, cli_data)
|
|
120
|
+
data = get_textfsm_object(self.re_table, cli_data) # noqa: F405
|
|
121
121
|
self.assertTrue(isinstance(data, dict))
|
|
122
122
|
keys = ["dayweek", "time", "timezone", "year", "day", "month"]
|
|
123
123
|
# Python 3: dict.has_key() removed, use 'in' operator
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from trigger.conf import settings
|
|
6
|
+
from trigger.twister import compile_prompt_pattern, prompt_match_start
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_ioslike_prompt_pattern_enabled():
|
|
10
|
+
"""Test enabled that IOS-like prompt patterns match correctly."""
|
|
11
|
+
pat = settings.IOSLIKE_PROMPT_PAT
|
|
12
|
+
|
|
13
|
+
prompt_tests = [
|
|
14
|
+
"foo-bar1#",
|
|
15
|
+
"foo-bar1# ",
|
|
16
|
+
"foo-bar1(config)# ",
|
|
17
|
+
"\rfoo-bar01(config)# \x08 ", # "Bonus" backspace in there
|
|
18
|
+
"foo-bar01(config) \r#", # "Bonus" '\s\r' in there
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
for prompt in prompt_tests:
|
|
22
|
+
assert re.search(pat, prompt) is not None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_ioslike_prompt_pattern_nonenabled():
|
|
26
|
+
"""Test non-enabled that IOS-like prompt patterns match correctly."""
|
|
27
|
+
pat = settings.IOSLIKE_ENABLE_PAT
|
|
28
|
+
|
|
29
|
+
prompt_tests = [
|
|
30
|
+
"foo-bar1>",
|
|
31
|
+
"foo-bar1> ",
|
|
32
|
+
"\rfoo-bar01)> \x08 ", # "Bonus" backspace in there
|
|
33
|
+
"foo-bar01) \r>", # "Bonus" '\s\r' in there
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
for prompt in prompt_tests:
|
|
37
|
+
assert re.search(pat, prompt) is not None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# False-positive prevention tests (Issue #317)
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestFalsePositivePrevention:
|
|
46
|
+
"""Verify that command output containing '>' or '#' is NOT matched as a prompt."""
|
|
47
|
+
|
|
48
|
+
def test_juniper_rsync_flag_not_matched(self):
|
|
49
|
+
"""Juniper 'Flags: <Sync RSync>' must not match as a prompt (issue #317)."""
|
|
50
|
+
pat = compile_prompt_pattern(settings.PROMPT_PATTERNS["juniper"])
|
|
51
|
+
buffer = "Flags: <Sync RSync>\r\n"
|
|
52
|
+
assert pat.search(buffer) is None
|
|
53
|
+
|
|
54
|
+
def test_ioslike_comment_not_matched(self):
|
|
55
|
+
"""A '# comment' line must not match IOS-like prompt pattern."""
|
|
56
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
57
|
+
buffer = "some output\r\n# This is a comment in output\r\n"
|
|
58
|
+
assert pat.search(buffer) is None
|
|
59
|
+
|
|
60
|
+
def test_enable_angle_bracket_not_matched(self):
|
|
61
|
+
"""A '>' inside command output must not match the enable pattern."""
|
|
62
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_ENABLE_PAT)
|
|
63
|
+
buffer = "Description: Traffic >1Gbps\r\nMore output here\r\n"
|
|
64
|
+
assert pat.search(buffer) is None
|
|
65
|
+
|
|
66
|
+
def test_ioslike_prompt_mid_line_not_matched(self):
|
|
67
|
+
"""IOS-like prompt pattern should not match '#' in the middle of a line."""
|
|
68
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
69
|
+
buffer = "some leading text device-name# "
|
|
70
|
+
assert pat.search(buffer) is None
|
|
71
|
+
|
|
72
|
+
def test_juniper_xml_angle_bracket_not_matched(self):
|
|
73
|
+
"""Juniper XML output with '>' should not match as prompt."""
|
|
74
|
+
pat = compile_prompt_pattern(settings.PROMPT_PATTERNS["juniper"])
|
|
75
|
+
buffer = '<rpc-reply xmlns:junos="http://xml.juniper.net">\r\n'
|
|
76
|
+
assert pat.search(buffer) is None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# =============================================================================
|
|
80
|
+
# Valid prompt regression tests
|
|
81
|
+
# =============================================================================
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestValidPromptMatching:
|
|
85
|
+
"""Verify that real prompts still match correctly after the anchoring fix."""
|
|
86
|
+
|
|
87
|
+
def test_first_prompt_no_preceding_newline(self):
|
|
88
|
+
"""A prompt at the very start of the buffer (no preceding newline) must match."""
|
|
89
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
90
|
+
buffer = "router1# "
|
|
91
|
+
m = pat.search(buffer)
|
|
92
|
+
assert m is not None
|
|
93
|
+
|
|
94
|
+
def test_prompt_after_newline(self):
|
|
95
|
+
"""A prompt after \\n must match."""
|
|
96
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
97
|
+
buffer = "some output\nrouter1# "
|
|
98
|
+
m = pat.search(buffer)
|
|
99
|
+
assert m is not None
|
|
100
|
+
assert prompt_match_start(m) == len("some output\n")
|
|
101
|
+
|
|
102
|
+
def test_prompt_after_crlf(self):
|
|
103
|
+
"""A prompt after \\r\\n must match."""
|
|
104
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
105
|
+
buffer = "some output\r\nrouter1# "
|
|
106
|
+
m = pat.search(buffer)
|
|
107
|
+
assert m is not None
|
|
108
|
+
assert prompt_match_start(m) == len("some output\r\n")
|
|
109
|
+
|
|
110
|
+
def test_juniper_prompt_after_master_banner(self):
|
|
111
|
+
"""Juniper prompt after {master} banner must match."""
|
|
112
|
+
pat = compile_prompt_pattern(settings.PROMPT_PATTERNS["juniper"])
|
|
113
|
+
buffer = "{master}\nuser@router> "
|
|
114
|
+
m = pat.search(buffer)
|
|
115
|
+
assert m is not None
|
|
116
|
+
|
|
117
|
+
def test_ioslike_config_mode_prompt(self):
|
|
118
|
+
"""IOS config mode prompt must match."""
|
|
119
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
120
|
+
buffer = "output line\nswitch1(config)# "
|
|
121
|
+
m = pat.search(buffer)
|
|
122
|
+
assert m is not None
|
|
123
|
+
|
|
124
|
+
def test_enable_prompt_at_start(self):
|
|
125
|
+
"""Enable prompt at start of buffer must match."""
|
|
126
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_ENABLE_PAT)
|
|
127
|
+
buffer = "router1> "
|
|
128
|
+
m = pat.search(buffer)
|
|
129
|
+
assert m is not None
|
|
130
|
+
assert prompt_match_start(m) == 0
|
|
131
|
+
|
|
132
|
+
def test_paloalto_prompt_with_crlf(self):
|
|
133
|
+
"""Palo Alto prompt with \\r\\n prefix must match."""
|
|
134
|
+
pat = compile_prompt_pattern(settings.PROMPT_PATTERNS["paloalto"])
|
|
135
|
+
buffer = "output\r\nadmin@fw1> "
|
|
136
|
+
m = pat.search(buffer)
|
|
137
|
+
assert m is not None
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# =============================================================================
|
|
141
|
+
# prompt_match_start correctness tests
|
|
142
|
+
# =============================================================================
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class TestPromptMatchStart:
|
|
146
|
+
"""Verify prompt_match_start returns the correct index."""
|
|
147
|
+
|
|
148
|
+
def test_match_at_buffer_start(self):
|
|
149
|
+
"""Match at buffer start returns 0."""
|
|
150
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
151
|
+
buffer = "router1# "
|
|
152
|
+
m = pat.search(buffer)
|
|
153
|
+
assert m is not None
|
|
154
|
+
assert prompt_match_start(m) == 0
|
|
155
|
+
|
|
156
|
+
def test_match_after_lf(self):
|
|
157
|
+
"""Match after \\n skips the newline character."""
|
|
158
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
159
|
+
buffer = "output\nrouter1# "
|
|
160
|
+
m = pat.search(buffer)
|
|
161
|
+
assert m is not None
|
|
162
|
+
# The \n is at index 6, prompt starts at index 7
|
|
163
|
+
assert prompt_match_start(m) == 7 # noqa: PLR2004
|
|
164
|
+
|
|
165
|
+
def test_match_after_crlf(self):
|
|
166
|
+
"""Match after \\r\\n skips both characters."""
|
|
167
|
+
pat = compile_prompt_pattern(settings.IOSLIKE_PROMPT_PAT)
|
|
168
|
+
buffer = "output\r\nrouter1# "
|
|
169
|
+
m = pat.search(buffer)
|
|
170
|
+
assert m is not None
|
|
171
|
+
# \r at 6, \n at 7, prompt starts at 8
|
|
172
|
+
assert prompt_match_start(m) == 8 # noqa: PLR2004
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# =============================================================================
|
|
176
|
+
# compile_prompt_pattern behavior tests
|
|
177
|
+
# =============================================================================
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class TestCompilePromptPattern:
|
|
181
|
+
"""Verify compile_prompt_pattern handles various inputs correctly."""
|
|
182
|
+
|
|
183
|
+
def test_already_compiled_pattern_returned_unchanged(self):
|
|
184
|
+
"""An already-compiled re.Pattern should be returned as-is."""
|
|
185
|
+
compiled = re.compile(r"foo#\s?$")
|
|
186
|
+
result = compile_prompt_pattern(compiled)
|
|
187
|
+
assert result is compiled
|
|
188
|
+
|
|
189
|
+
def test_pattern_starting_with_caret_not_double_anchored(self):
|
|
190
|
+
"""A pattern starting with ^ should not get a redundant prefix."""
|
|
191
|
+
pat = compile_prompt_pattern(r"^\S+#\s?$")
|
|
192
|
+
assert pat.search("router1# ") is not None
|
|
193
|
+
# Verify no double-anchor by checking pattern string
|
|
194
|
+
assert not pat.pattern.startswith(r"(?:^|\r?\n)^")
|
|
195
|
+
|
|
196
|
+
def test_pattern_starting_with_backslash_r_not_prefixed(self):
|
|
197
|
+
"""A pattern starting with \\r should not get a prefix (e.g. paloalto)."""
|
|
198
|
+
pat = compile_prompt_pattern(r"\r\n\S+(?:\>|#)\s?$")
|
|
199
|
+
# Pattern should still work
|
|
200
|
+
assert pat.search("\r\nadmin@fw1> ") is not None
|
|
201
|
+
# Verify no prefix added
|
|
202
|
+
assert not pat.pattern.startswith(r"(?:^|\r?\n)\r")
|
|
203
|
+
|
|
204
|
+
def test_pattern_starting_with_backslash_n_not_prefixed(self):
|
|
205
|
+
"""A pattern starting with \\n should not get a prefix."""
|
|
206
|
+
pat = compile_prompt_pattern(r"\n\S+#\s?$")
|
|
207
|
+
assert not pat.pattern.startswith(r"(?:^|\r?\n)\n")
|
|
208
|
+
|
|
209
|
+
def test_multiline_flag_is_set(self):
|
|
210
|
+
"""The compiled pattern should have re.MULTILINE enabled."""
|
|
211
|
+
pat = compile_prompt_pattern(r"\S+#\s?$")
|
|
212
|
+
assert pat.flags & re.MULTILINE
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# =============================================================================
|
|
216
|
+
# Parametrized vendor pattern tests
|
|
217
|
+
# =============================================================================
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# Map each vendor pattern to a sample prompt that should match on its own line
|
|
221
|
+
VENDOR_PROMPT_SAMPLES = {
|
|
222
|
+
"aruba": "(Aruba7010) #",
|
|
223
|
+
"avocent": "admin-0->",
|
|
224
|
+
"citrix": " Done\n",
|
|
225
|
+
"cumulus": "cumulus@switch# ",
|
|
226
|
+
"f5": "admin@(bigip1)(cfg-sync Standalone)(Active)(/Common)(tmos)# ",
|
|
227
|
+
"juniper": "user@router> ",
|
|
228
|
+
"mrv": "\r\nMRV OptiSwitch 1 >>",
|
|
229
|
+
"netscreen": "fw1-> ",
|
|
230
|
+
"paloalto": "\r\nadmin@fw1> ",
|
|
231
|
+
"pica8": "admin@switch> ",
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@pytest.mark.parametrize(
|
|
236
|
+
("vendor", "sample_prompt"),
|
|
237
|
+
list(VENDOR_PROMPT_SAMPLES.items()),
|
|
238
|
+
ids=list(VENDOR_PROMPT_SAMPLES.keys()),
|
|
239
|
+
)
|
|
240
|
+
def test_vendor_pattern_matches_sample_prompt(vendor, sample_prompt):
|
|
241
|
+
"""Each vendor pattern must match its expected sample prompt."""
|
|
242
|
+
pat = compile_prompt_pattern(settings.PROMPT_PATTERNS[vendor])
|
|
243
|
+
assert pat.search(sample_prompt) is not None, (
|
|
244
|
+
f"Vendor {vendor!r} pattern failed to match sample prompt {sample_prompt!r}"
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@pytest.mark.parametrize(
|
|
249
|
+
"vendor",
|
|
250
|
+
list(settings.PROMPT_PATTERNS.keys()),
|
|
251
|
+
)
|
|
252
|
+
def test_vendor_pattern_compiles_without_error(vendor):
|
|
253
|
+
"""Every vendor pattern in PROMPT_PATTERNS must compile successfully."""
|
|
254
|
+
pat = compile_prompt_pattern(settings.PROMPT_PATTERNS[vendor])
|
|
255
|
+
assert isinstance(pat, re.Pattern)
|
|
@@ -7,10 +7,4 @@ This uses the mockups of netdevices.xml in
|
|
|
7
7
|
tests/data.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__author__ = "Thomas Cuthbert, Jathan McCollum"
|
|
11
|
-
__maintainer__ = "Jathan McCollum"
|
|
12
|
-
__copyright__ = "Copyright 2005-2011 AOL Inc.; 2013 Salesforce.com.; 2016 Dropbox"
|
|
13
|
-
__version__ = "1.0"
|
|
14
|
-
|
|
15
|
-
|
|
16
10
|
# TODO: see http://twistedmatrix.com/trac/wiki/TwistedTrial
|