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.
Files changed (80) hide show
  1. {trigger-2.2.2/trigger.egg-info → trigger-2.2.4}/PKG-INFO +12 -25
  2. {trigger-2.2.2 → trigger-2.2.4}/README.md +8 -24
  3. {trigger-2.2.2 → trigger-2.2.4}/pyproject.toml +10 -58
  4. {trigger-2.2.2 → trigger-2.2.4}/tests/test_acl.py +4 -9
  5. {trigger-2.2.2 → trigger-2.2.4}/tests/test_acl_queue.py +1 -1
  6. {trigger-2.2.2 → trigger-2.2.4}/tests/test_changemgmt.py +0 -7
  7. {trigger-2.2.2 → trigger-2.2.4}/tests/test_except.py +1 -1
  8. {trigger-2.2.2 → trigger-2.2.4}/tests/test_netdevices.py +0 -5
  9. {trigger-2.2.2 → trigger-2.2.4}/tests/test_scripts.py +0 -5
  10. {trigger-2.2.2 → trigger-2.2.4}/tests/test_tacacsrc.py +26 -7
  11. {trigger-2.2.2 → trigger-2.2.4}/tests/test_templates.py +3 -3
  12. trigger-2.2.4/tests/test_twister.py +255 -0
  13. {trigger-2.2.2 → trigger-2.2.4}/tests/test_twister2.py +0 -6
  14. {trigger-2.2.2 → trigger-2.2.4}/trigger/__init__.py +2 -0
  15. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/__init__.py +2 -9
  16. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/autoacl.py +3 -9
  17. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/db.py +8 -13
  18. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/dicts.py +2 -8
  19. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/grammar.py +20 -25
  20. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/ios.py +45 -52
  21. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/junos.py +76 -77
  22. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/models.py +1 -1
  23. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/parser.py +41 -40
  24. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/queue.py +2 -9
  25. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/support.py +142 -152
  26. {trigger-2.2.2 → trigger-2.2.4}/trigger/acl/tools.py +58 -61
  27. trigger-2.2.4/trigger/bin/__init__.py +1 -0
  28. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/acl.py +7 -9
  29. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/acl_script.py +8 -12
  30. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/aclconv.py +2 -4
  31. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/check_access.py +4 -6
  32. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/check_syntax.py +3 -5
  33. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/fe.py +5 -8
  34. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/find_access.py +9 -11
  35. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/gnng.py +5 -8
  36. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/gong.py +3 -5
  37. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/load_acl.py +27 -29
  38. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/load_config.py +1 -1
  39. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/netdev.py +5 -6
  40. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/optimizer.py +24 -24
  41. {trigger-2.2.2 → trigger-2.2.4}/trigger/bin/run_cmds.py +1 -1
  42. {trigger-2.2.2 → trigger-2.2.4}/trigger/changemgmt/__init__.py +25 -30
  43. {trigger-2.2.2 → trigger-2.2.4}/trigger/changemgmt/bounce.py +1 -9
  44. {trigger-2.2.2 → trigger-2.2.4}/trigger/cmds.py +40 -46
  45. {trigger-2.2.2 → trigger-2.2.4}/trigger/conf/__init__.py +6 -10
  46. {trigger-2.2.2 → trigger-2.2.4}/trigger/conf/global_settings.py +7 -4
  47. {trigger-2.2.2 → trigger-2.2.4}/trigger/contrib/__init__.py +1 -1
  48. {trigger-2.2.2 → trigger-2.2.4}/trigger/exceptions.py +15 -22
  49. {trigger-2.2.2 → trigger-2.2.4}/trigger/gorc.py +4 -4
  50. {trigger-2.2.2 → trigger-2.2.4}/trigger/netdevices/__init__.py +73 -69
  51. {trigger-2.2.2 → trigger-2.2.4}/trigger/netdevices/loader.py +9 -7
  52. {trigger-2.2.2 → trigger-2.2.4}/trigger/netscreen.py +98 -94
  53. {trigger-2.2.2 → trigger-2.2.4}/trigger/rancid.py +10 -16
  54. {trigger-2.2.2 → trigger-2.2.4}/trigger/tacacsrc.py +35 -38
  55. {trigger-2.2.2 → trigger-2.2.4}/trigger/twister.py +128 -87
  56. {trigger-2.2.2 → trigger-2.2.4}/trigger/twister2.py +40 -39
  57. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/__init__.py +3 -8
  58. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/cli.py +14 -19
  59. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/importlib.py +1 -1
  60. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/network.py +6 -6
  61. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/rcs.py +10 -15
  62. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/templates.py +7 -15
  63. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/url.py +1 -7
  64. {trigger-2.2.2 → trigger-2.2.4}/trigger/utils/xmltodict.py +5 -9
  65. {trigger-2.2.2 → trigger-2.2.4/trigger.egg-info}/PKG-INFO +12 -25
  66. {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/requires.txt +4 -0
  67. {trigger-2.2.2 → trigger-2.2.4}/twisted/plugins/trigger_xmlrpc.py +0 -2
  68. trigger-2.2.2/tests/test_twister.py +0 -34
  69. trigger-2.2.2/trigger/bin/__init__.py +0 -0
  70. {trigger-2.2.2 → trigger-2.2.4}/AUTHORS.md +0 -0
  71. {trigger-2.2.2 → trigger-2.2.4}/LICENSE.md +0 -0
  72. {trigger-2.2.2 → trigger-2.2.4}/setup.cfg +0 -0
  73. {trigger-2.2.2 → trigger-2.2.4}/tests/test_acl_db.py +0 -0
  74. {trigger-2.2.2 → trigger-2.2.4}/tests/test_utils.py +0 -0
  75. {trigger-2.2.2 → trigger-2.2.4}/trigger/packages/__init__.py +0 -0
  76. {trigger-2.2.2 → trigger-2.2.4}/trigger/packages/peewee.py +0 -0
  77. {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/SOURCES.txt +0 -0
  78. {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/dependency_links.txt +0 -0
  79. {trigger-2.2.2 → trigger-2.2.4}/trigger.egg-info/entry_points.txt +0 -0
  80. {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.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
- [![Tests](https://github.com/trigger/trigger/workflows/Tests/badge.svg)](https://github.com/trigger/trigger/actions)
41
- [![Join the chat at https://gitter.im/trigger/trigger](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/trigger/trigger?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
43
+ [![CI](https://github.com/trigger/trigger/workflows/CI/badge.svg)](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 [develop](https://github.com/trigger/trigger/tree/develop) branch is
146
- the default branch that will be active when you clone this repository. While
147
- it is generally stable this branch is not considered production-ready. Use at
148
- your own risk!
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
- [contact us](https://trigger.readthedocs.io/en/latest/#getting-help)!
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
- [![Tests](https://github.com/trigger/trigger/workflows/Tests/badge.svg)](https://github.com/trigger/trigger/actions)
4
- [![Join the chat at https://gitter.im/trigger/trigger](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/trigger/trigger?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
3
+ [![CI](https://github.com/trigger/trigger/workflows/CI/badge.svg)](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 [develop](https://github.com/trigger/trigger/tree/develop) branch is
109
- the default branch that will be active when you clone this repository. While
110
- it is generally stable this branch is not considered production-ready. Use at
111
- your own risk!
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
- [contact us](https://trigger.readthedocs.io/en/latest/#getting-help)!
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.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 testInactive(self):
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)
@@ -6,7 +6,7 @@ Test the functionality of `~trigger.acl.queue` (aka task queue)
6
6
  Only tests SQLite for now.
7
7
  """
8
8
 
9
- import os
9
+ import os # noqa: F401
10
10
  import tempfile
11
11
  from pathlib import Path
12
12
 
@@ -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
 
@@ -1,5 +1,5 @@
1
1
  # a test for except processing
2
- import os
2
+ import os # noqa: F401
3
3
  import sys
4
4
  from pathlib import Path
5
5
 
@@ -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
@@ -2,11 +2,6 @@
2
2
 
3
3
  # tests/scripts.py
4
4
 
5
- __author__ = "Michael Shields"
6
- __maintainer__ = "Jathan McCollum"
7
- __copyright__ = "Copyright 2005-2011 AOL Inc."
8
- __version__ = "1.1"
9
-
10
5
  import os
11
6
  import subprocess
12
7
  import unittest
@@ -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