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
trigger/bin/gnng.py
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
gnng - Fetches network devices interfaces and displays them in a table view.
|
|
5
|
+
|
|
6
|
+
Fetches interface information from routing and firewall devices. This includes
|
|
7
|
+
network and IP information along with the inbound and outbound filters that
|
|
8
|
+
may be applied to the interface. Works on Juniper, Netscreen, Foundry, and Cisco
|
|
9
|
+
devices.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "1.3.2"
|
|
13
|
+
|
|
14
|
+
import csv
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from collections import namedtuple
|
|
18
|
+
from optparse import OptionParser
|
|
19
|
+
from sqlite3 import dbapi2 as sqlite
|
|
20
|
+
|
|
21
|
+
import prettytable
|
|
22
|
+
|
|
23
|
+
from trigger.cmds import NetACLInfo
|
|
24
|
+
|
|
25
|
+
# Put this here until the default changes to not load ACLs from redis.
|
|
26
|
+
from trigger.conf import settings
|
|
27
|
+
from trigger.netdevices import NetDevices, device_match
|
|
28
|
+
|
|
29
|
+
settings.WITH_ACLS = False
|
|
30
|
+
|
|
31
|
+
# log.startLogging(sys.stdout, setStdout=False)
|
|
32
|
+
|
|
33
|
+
# Constants
|
|
34
|
+
DEBUG = os.getenv("DEBUG")
|
|
35
|
+
MAX_CONNS = 10
|
|
36
|
+
ROW_LABELS = ["Interface", "Addresses", "Subnets", "ACLs IN", "ACLs OUT", "Description"]
|
|
37
|
+
|
|
38
|
+
# Namedtuples
|
|
39
|
+
RowData = namedtuple("RowData", "all_rows subnet_table")
|
|
40
|
+
DottyData = namedtuple("DottyData", "graph links")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse_args(argv):
|
|
44
|
+
parser = OptionParser(
|
|
45
|
+
usage="%prog [options] [routers]",
|
|
46
|
+
description="""GetNets-NG
|
|
47
|
+
|
|
48
|
+
Fetches interface information from routing and firewall devices. This includes
|
|
49
|
+
network and IP information along with the inbound and outbound filters that
|
|
50
|
+
may be applied to the interface. Skips un-numbered and disabled interfaces by
|
|
51
|
+
default. Works on Cisco, Foundry, Juniper, and NetScreen devices.""",
|
|
52
|
+
)
|
|
53
|
+
parser.add_option("-a", "--all", action="store_true", help="run on all devices")
|
|
54
|
+
parser.add_option(
|
|
55
|
+
"-c",
|
|
56
|
+
"--csv",
|
|
57
|
+
action="store_true",
|
|
58
|
+
help="output the data in CSV format instead.",
|
|
59
|
+
)
|
|
60
|
+
parser.add_option(
|
|
61
|
+
"-d",
|
|
62
|
+
"--include-disabled",
|
|
63
|
+
action="store_true",
|
|
64
|
+
help="include disabled interfaces.",
|
|
65
|
+
)
|
|
66
|
+
parser.add_option(
|
|
67
|
+
"-u",
|
|
68
|
+
"--include-unnumbered",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="include un-numbered interfaces.",
|
|
71
|
+
)
|
|
72
|
+
parser.add_option(
|
|
73
|
+
"-j",
|
|
74
|
+
"--jobs",
|
|
75
|
+
type="int",
|
|
76
|
+
default=MAX_CONNS,
|
|
77
|
+
help="maximum simultaneous connections to maintain.",
|
|
78
|
+
)
|
|
79
|
+
parser.add_option(
|
|
80
|
+
"-N",
|
|
81
|
+
"--nonprod",
|
|
82
|
+
action="store_false",
|
|
83
|
+
default=True,
|
|
84
|
+
help="Include non-production devices from the query or "
|
|
85
|
+
"[routers]. Requires a legitimate query.",
|
|
86
|
+
)
|
|
87
|
+
parser.add_option("-s", "--sqldb", type="str", help="output to SQLite DB")
|
|
88
|
+
parser.add_option(
|
|
89
|
+
"",
|
|
90
|
+
"--dotty",
|
|
91
|
+
action="store_true",
|
|
92
|
+
help="output connect-to information in dotty format.",
|
|
93
|
+
)
|
|
94
|
+
parser.add_option(
|
|
95
|
+
"",
|
|
96
|
+
"--filter-on-group",
|
|
97
|
+
action="append",
|
|
98
|
+
help="Run on all devices owned by this group",
|
|
99
|
+
)
|
|
100
|
+
parser.add_option(
|
|
101
|
+
"",
|
|
102
|
+
"--filter-on-type",
|
|
103
|
+
action="append",
|
|
104
|
+
help="Run on all devices with this device type",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
opts, args = parser.parse_args(argv)
|
|
108
|
+
|
|
109
|
+
if len(args) == 1 and not opts.all and not opts.filter_on_type:
|
|
110
|
+
parser.print_help()
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
return opts, args
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def fetch_router_list(args, opts):
|
|
117
|
+
"""Turns a list of device names into device objects, skipping unsupported,
|
|
118
|
+
invalid, or filtered devices."""
|
|
119
|
+
nd = NetDevices(production_only=opts.nonprod)
|
|
120
|
+
ret = []
|
|
121
|
+
blocked_groups = []
|
|
122
|
+
if args:
|
|
123
|
+
for arg in args:
|
|
124
|
+
# Try to find the device, but fail gracefully if it can't be found
|
|
125
|
+
device = device_match(arg)
|
|
126
|
+
if not pass_filters(device, opts) or device is None:
|
|
127
|
+
continue
|
|
128
|
+
ret.append(device)
|
|
129
|
+
|
|
130
|
+
else:
|
|
131
|
+
for entry in nd.values():
|
|
132
|
+
if entry.owningTeam in blocked_groups:
|
|
133
|
+
continue
|
|
134
|
+
if not pass_filters(entry, opts):
|
|
135
|
+
continue
|
|
136
|
+
ret.append(entry)
|
|
137
|
+
|
|
138
|
+
return sorted(ret, reverse=True)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def pass_filters(device, opts):
|
|
142
|
+
"""Used by fetch_router_list() to filter a device based on command-line arguments."""
|
|
143
|
+
if opts.filter_on_group:
|
|
144
|
+
if device.owningTeam not in opts.filter_on_group:
|
|
145
|
+
return False
|
|
146
|
+
if opts.filter_on_type:
|
|
147
|
+
if device.deviceType not in opts.filter_on_type:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def write_sqldb(sqlfile, dev, rows):
|
|
154
|
+
"""Write device fields to sqlite db"""
|
|
155
|
+
create_table = False
|
|
156
|
+
|
|
157
|
+
if not os.path.isfile(sqlfile):
|
|
158
|
+
create_table = True
|
|
159
|
+
|
|
160
|
+
connection = sqlite.connect(sqlfile)
|
|
161
|
+
cursor = connection.cursor()
|
|
162
|
+
|
|
163
|
+
if create_table:
|
|
164
|
+
# if the db doesn't exist we want to create the table.
|
|
165
|
+
cursor.execute("""
|
|
166
|
+
CREATE TABLE dev_nets (
|
|
167
|
+
id INTEGER PRIMARY KEY,
|
|
168
|
+
insert_date DATE,
|
|
169
|
+
device_name VARCHAR(128),
|
|
170
|
+
iface_name VARCHAR(32),
|
|
171
|
+
iface_addrs VARCHAR(1024),
|
|
172
|
+
iface_subnets VARCHAR(1024),
|
|
173
|
+
iface_inacl VARCHAR(32),
|
|
174
|
+
iface_outacl VARCHAR(32),
|
|
175
|
+
iface_descr VARCHAR(1024)
|
|
176
|
+
);
|
|
177
|
+
""")
|
|
178
|
+
cursor.execute("""
|
|
179
|
+
CREATE TRIGGER auto_date AFTER INSERT ON dev_nets
|
|
180
|
+
BEGIN
|
|
181
|
+
UPDATE dev_nets SET insert_date = DATETIME('NOW')
|
|
182
|
+
WHERE rowid = new.rowid;
|
|
183
|
+
END;
|
|
184
|
+
""")
|
|
185
|
+
|
|
186
|
+
for row in rows:
|
|
187
|
+
iface, addrs, snets, inacl, outacl, desc = row
|
|
188
|
+
cursor.execute(
|
|
189
|
+
f"""
|
|
190
|
+
INSERT INTO dev_nets (
|
|
191
|
+
device_name,
|
|
192
|
+
iface_name,
|
|
193
|
+
iface_addrs,
|
|
194
|
+
iface_subnets,
|
|
195
|
+
iface_inacl,
|
|
196
|
+
iface_outacl,
|
|
197
|
+
iface_descr )
|
|
198
|
+
VALUES (
|
|
199
|
+
'{dev}', '{iface}', '{addrs}',
|
|
200
|
+
'{snets}', '{inacl}', '{outacl}', '{desc}'
|
|
201
|
+
);"""
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
connection.commit()
|
|
205
|
+
cursor.close()
|
|
206
|
+
connection.close()
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_interface_data(devices, production_only=True, max_conns=MAX_CONNS, opts=None):
|
|
210
|
+
"""
|
|
211
|
+
Fetch interface information from ``devices`` and return it as a dict.
|
|
212
|
+
|
|
213
|
+
:param devices:
|
|
214
|
+
List of device hostnames
|
|
215
|
+
|
|
216
|
+
:param production_only:
|
|
217
|
+
Whether to include only devices marked as "PRODUCTION"
|
|
218
|
+
|
|
219
|
+
:param max_conns:
|
|
220
|
+
Max number of simultaneous connections
|
|
221
|
+
"""
|
|
222
|
+
skip_disabled = not opts.include_disabled # Inverse of include is skip :D
|
|
223
|
+
ninfo = NetACLInfo(
|
|
224
|
+
devices=devices,
|
|
225
|
+
production_only=production_only,
|
|
226
|
+
max_conns=max_conns,
|
|
227
|
+
skip_disabled=skip_disabled,
|
|
228
|
+
)
|
|
229
|
+
ninfo.run()
|
|
230
|
+
if DEBUG:
|
|
231
|
+
print("NetACLInfo done!")
|
|
232
|
+
|
|
233
|
+
return ninfo.config
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def build_output(main_data, opts, labels=None):
|
|
237
|
+
"""
|
|
238
|
+
Iterate the interface data, then build and return row data.
|
|
239
|
+
|
|
240
|
+
:param main_data:
|
|
241
|
+
Dictionary of interface data
|
|
242
|
+
|
|
243
|
+
:param opts:
|
|
244
|
+
OptionParser object
|
|
245
|
+
|
|
246
|
+
:param labels:
|
|
247
|
+
Row labels for table output
|
|
248
|
+
"""
|
|
249
|
+
if labels is None:
|
|
250
|
+
labels = ROW_LABELS
|
|
251
|
+
|
|
252
|
+
subnet_table = {}
|
|
253
|
+
all_rows = {}
|
|
254
|
+
|
|
255
|
+
for dev, data in main_data.items():
|
|
256
|
+
rows = []
|
|
257
|
+
interfaces = sorted(data)
|
|
258
|
+
for interface in interfaces:
|
|
259
|
+
iface = data[interface]
|
|
260
|
+
|
|
261
|
+
# Maybe skip down interfaces
|
|
262
|
+
if "addr" not in iface and not opts.include_disabled:
|
|
263
|
+
continue
|
|
264
|
+
|
|
265
|
+
if DEBUG:
|
|
266
|
+
print(">>> ", interface)
|
|
267
|
+
|
|
268
|
+
addrs = iface["addr"]
|
|
269
|
+
subns = iface["subnets"]
|
|
270
|
+
acls_in = iface["acl_in"]
|
|
271
|
+
acls_out = iface["acl_out"]
|
|
272
|
+
desctext = " ".join(iface.get("description")).replace(" : ", ":")
|
|
273
|
+
|
|
274
|
+
# Maybe skip un-numbered interfaces
|
|
275
|
+
if not addrs and not opts.include_unnumbered:
|
|
276
|
+
continue
|
|
277
|
+
|
|
278
|
+
# Trim the description
|
|
279
|
+
if not opts.csv:
|
|
280
|
+
desctext = desctext[0:50]
|
|
281
|
+
|
|
282
|
+
addresses = []
|
|
283
|
+
subnets = []
|
|
284
|
+
|
|
285
|
+
for a in addrs:
|
|
286
|
+
addresses.append(a.strNormal())
|
|
287
|
+
|
|
288
|
+
for s in subns:
|
|
289
|
+
subnets.append(s.strNormal())
|
|
290
|
+
|
|
291
|
+
if s in subnet_table:
|
|
292
|
+
subnet_table[s].append((dev, interface, addrs))
|
|
293
|
+
else:
|
|
294
|
+
subnet_table[s] = [(dev, interface, addrs)]
|
|
295
|
+
|
|
296
|
+
if DEBUG:
|
|
297
|
+
print("\t in:", acls_in)
|
|
298
|
+
print("\t ou:", acls_out)
|
|
299
|
+
rows.append(
|
|
300
|
+
[
|
|
301
|
+
interface,
|
|
302
|
+
" ".join(addresses),
|
|
303
|
+
" ".join(subnets),
|
|
304
|
+
"\n".join(acls_in),
|
|
305
|
+
"\n".join(acls_out),
|
|
306
|
+
desctext,
|
|
307
|
+
]
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
all_rows[dev.nodeName] = rows
|
|
311
|
+
|
|
312
|
+
return RowData(all_rows, subnet_table)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def handle_output(all_rows, opts):
|
|
316
|
+
"""
|
|
317
|
+
Do stuff with the output data.
|
|
318
|
+
|
|
319
|
+
:param all_rows:
|
|
320
|
+
A list of lists of row data
|
|
321
|
+
|
|
322
|
+
:param opts:
|
|
323
|
+
OptionParser object
|
|
324
|
+
"""
|
|
325
|
+
for dev, rows in all_rows.items():
|
|
326
|
+
if opts.csv:
|
|
327
|
+
writer = csv.writer(sys.stdout)
|
|
328
|
+
for row in rows:
|
|
329
|
+
writer.writerow([dev] + row)
|
|
330
|
+
elif opts.dotty:
|
|
331
|
+
continue
|
|
332
|
+
elif opts.sqldb:
|
|
333
|
+
write_sqldb(opts.sqldb, dev, rows)
|
|
334
|
+
else:
|
|
335
|
+
print(f"DEVICE: {dev}")
|
|
336
|
+
print_table(rows)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def print_table(rows, labels=None):
|
|
340
|
+
"""
|
|
341
|
+
Print the interface table for a device
|
|
342
|
+
"""
|
|
343
|
+
if labels is None:
|
|
344
|
+
labels = ROW_LABELS
|
|
345
|
+
|
|
346
|
+
output_table = prettytable.PrettyTable()
|
|
347
|
+
output_table.field_names = labels
|
|
348
|
+
output_table.align = "l"
|
|
349
|
+
output_table.vrules = prettytable.prettytable.ALL
|
|
350
|
+
output_table.hrules = prettytable.prettytable.HEADER
|
|
351
|
+
|
|
352
|
+
for row in rows:
|
|
353
|
+
row = [x.strip() for x in row]
|
|
354
|
+
output_table.add_row(row)
|
|
355
|
+
|
|
356
|
+
print(output_table)
|
|
357
|
+
print("")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def output_dotty(subnet_table, display=True):
|
|
361
|
+
"""
|
|
362
|
+
Output and return dotty config for a ``subnet_table``
|
|
363
|
+
|
|
364
|
+
:param subnet_table:
|
|
365
|
+
Dict mapping subnets to devices and interfaces
|
|
366
|
+
"""
|
|
367
|
+
links = {}
|
|
368
|
+
|
|
369
|
+
for ip, devs in subnet_table.items():
|
|
370
|
+
if len(devs) > 1:
|
|
371
|
+
router1 = devs[0][0]
|
|
372
|
+
router2 = devs[1][0]
|
|
373
|
+
|
|
374
|
+
kf1 = router1 in links
|
|
375
|
+
kf2 = router2 in links
|
|
376
|
+
|
|
377
|
+
if kf1:
|
|
378
|
+
if router2 not in links[router1]:
|
|
379
|
+
links[router1].append(router2)
|
|
380
|
+
|
|
381
|
+
elif kf2:
|
|
382
|
+
if router1 not in links[router2]:
|
|
383
|
+
links[router2].append(router1)
|
|
384
|
+
|
|
385
|
+
else:
|
|
386
|
+
links[router1] = [router2]
|
|
387
|
+
|
|
388
|
+
if not links:
|
|
389
|
+
print("No valid links for dotty generation.")
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
NetDevices() # This uses the pre-existing NetDevices singleton
|
|
393
|
+
|
|
394
|
+
graph = """graph network {
|
|
395
|
+
overlap=scale; center=true; orientation=land;
|
|
396
|
+
resolution=0.10; rankdir=LR; ratio=fill;
|
|
397
|
+
node [fontname=Courier, fontsize=10]"""
|
|
398
|
+
|
|
399
|
+
for leaf, subleaves in links.items():
|
|
400
|
+
for subleaf in subleaves:
|
|
401
|
+
graph += f'"{leaf.shortName}"--"{subleaf.shortName}"\n'
|
|
402
|
+
# print >>sys.stderr, leaf,"connects to: ",','.join(subleaves)
|
|
403
|
+
graph += "\n}"
|
|
404
|
+
|
|
405
|
+
if display:
|
|
406
|
+
print(graph)
|
|
407
|
+
|
|
408
|
+
return DottyData(graph, links)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def main():
|
|
412
|
+
"""Main entry point for the CLI tool."""
|
|
413
|
+
opts, args = parse_args(sys.argv)
|
|
414
|
+
|
|
415
|
+
if opts.all or opts.filter_on_type:
|
|
416
|
+
routers = fetch_router_list(None, opts)
|
|
417
|
+
else:
|
|
418
|
+
routers = fetch_router_list(args[1:], opts)
|
|
419
|
+
|
|
420
|
+
if not routers:
|
|
421
|
+
sys.exit(1)
|
|
422
|
+
|
|
423
|
+
main_data = get_interface_data(
|
|
424
|
+
devices=routers, production_only=opts.nonprod, opts=opts
|
|
425
|
+
)
|
|
426
|
+
all_rows, subnet_table = build_output(main_data, opts)
|
|
427
|
+
handle_output(all_rows, opts)
|
|
428
|
+
|
|
429
|
+
if opts.dotty:
|
|
430
|
+
output_dotty(subnet_table)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
if __name__ == "__main__":
|
|
434
|
+
main()
|
trigger/bin/gong.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
gong (go ng) - Command-line client to log in to network devices using TACACS credentials.
|
|
5
|
+
|
|
6
|
+
An optional .gorc file may be used to specify user preferences.
|
|
7
|
+
|
|
8
|
+
Partially adapted from conch. See twisted.conch.scripts.conch and
|
|
9
|
+
http://twistedmatrix.com/projects/conch/documentation/howto/conch_client.html
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "2.0"
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
from optparse import OptionParser
|
|
17
|
+
|
|
18
|
+
from twisted.python import log
|
|
19
|
+
|
|
20
|
+
# Put this here until the default changes to not load ACLs from redis.
|
|
21
|
+
from trigger.conf import settings
|
|
22
|
+
from trigger.netdevices import device_match
|
|
23
|
+
|
|
24
|
+
settings.WITH_ACLS = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_args(argv):
|
|
28
|
+
parser = OptionParser(
|
|
29
|
+
usage="%prog [options] [device]",
|
|
30
|
+
description="""\
|
|
31
|
+
Automatically log into network devices using cached TACACS credentials.
|
|
32
|
+
""",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
parser.add_option(
|
|
36
|
+
"-o", "--oob", action="store_true", help="Connect to device out of band first."
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
opts, args = parser.parse_args(argv)
|
|
40
|
+
|
|
41
|
+
if len(args) != 2:
|
|
42
|
+
parser.print_help()
|
|
43
|
+
sys.exit(2)
|
|
44
|
+
|
|
45
|
+
return opts, args
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def connect_to_oob(dev):
|
|
49
|
+
"""Lookup out-of-band info and try to connect to the console using telnet."""
|
|
50
|
+
tn = f"telnet {dev.OOBTerminalServerFQDN} {dev.OOBTerminalServerTCPPort}"
|
|
51
|
+
|
|
52
|
+
print(f"OOB Information for {dev.nodeName}:")
|
|
53
|
+
print(tn)
|
|
54
|
+
print("Connecting you now...")
|
|
55
|
+
os.system(tn)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main():
|
|
59
|
+
"""Main entry point for the CLI tool."""
|
|
60
|
+
global opts
|
|
61
|
+
opts, args = parse_args(sys.argv)
|
|
62
|
+
|
|
63
|
+
if os.getenv("DEBUG") is not None:
|
|
64
|
+
log.startLogging(sys.stdout, setStdout=False)
|
|
65
|
+
|
|
66
|
+
# Exception handling is done in device_match, returns None if no match.
|
|
67
|
+
dev = device_match(args[1].lower(), production_only=False)
|
|
68
|
+
if dev is None:
|
|
69
|
+
return 2
|
|
70
|
+
|
|
71
|
+
if dev.adminStatus != "PRODUCTION":
|
|
72
|
+
print("WARNING: You are connecting to a non-production device.")
|
|
73
|
+
|
|
74
|
+
if opts.oob:
|
|
75
|
+
connect_to_oob(dev)
|
|
76
|
+
return 0
|
|
77
|
+
|
|
78
|
+
# Connect to the device... and make sure to capture its exit code
|
|
79
|
+
retval = dev.connect()
|
|
80
|
+
|
|
81
|
+
print("\n") # Return cursor to beginning of line
|
|
82
|
+
return retval
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
if __name__ == "__main__":
|
|
86
|
+
sys.exit(main())
|