Skip to content

Commit

Permalink
Common argument handling for netmiko-grep
Browse files Browse the repository at this point in the history
  • Loading branch information
ktbyers committed Oct 1, 2024
1 parent 885d574 commit 23fa073
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 114 deletions.
47 changes: 32 additions & 15 deletions netmiko/cli_tools/argument_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@

def common_args(parser):
"""Add common arguments to the parser."""
parser.add_argument(
"devices",
nargs="?",
help="Device or group to connect to",
action="store",
type=str,
)
parser.add_argument(
"--cmd",
help="Command to execute",
Expand Down Expand Up @@ -38,18 +31,42 @@ def common_args(parser):

def show_args(parser):
"""Add arguments specific to netmiko_show.py."""
pass
parser.add_argument(
"devices",
help="Device or group to connect to",
action="store",
type=str,
)


def cfg_args(parser):
"""Add arguments specific to netmiko_cfg.py."""
parser.add_argument(
"devices",
help="Device or group to connect to",
action="store",
type=str,
)
parser.add_argument(
"--infile", help="Read commands from file", type=argparse.FileType("r")
)


def grep_args(parser):
"""Add arguments specific to netmiko_grep.py."""
parser.add_argument(
"pattern", nargs="?", help="Pattern to search for", action="store", type=str
)
parser.add_argument(
"devices",
help="Device or group to connect to",
action="store",
type=str,
)


def parse_arguments(args, command):
"""Parse command-line arguments for both scripts."""
"""Parse command-line arguments for all scripts."""

if command == "netmiko-cfg":
description = "Execute configurations command using Netmiko"
Expand All @@ -58,12 +75,10 @@ def parse_arguments(args, command):
description = "Execute show command using Netmiko (defaults to 'show run')"
addl_args = show_args
elif command == "netmiko-grep":
# FIX
description = ""
# addl_args = grep_args
description = "Grep pattern search on Netmiko output (defaults to 'show run')"
addl_args = grep_args
else:
# FIX: better message
raise ValueError()
raise ValueError(f"Unknown Netmiko cli-tool: {command}")

parser = argparse.ArgumentParser(description=description)
common_args(parser)
Expand All @@ -75,7 +90,9 @@ def parse_arguments(args, command):
if not cli_args.list_devices and not cli_args.version:
if not cli_args.devices:
parser.error("Devices not specified.")
if command == "netmiko-cfg" and not cli_args.cmd and not cli_args.infile:
elif command == "netmiko-cfg" and not cli_args.cmd and not cli_args.infile:
parser.error("No configuration commands provided.")
elif command == "netmiko-grep" and not cli_args.pattern:
parser.error("Grep pattern not specified.")

return cli_args
145 changes: 46 additions & 99 deletions netmiko/cli_tools/netmiko_grep.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#!/usr/bin/env python
"""Create grep like remote behavior on show run or command output."""
import argparse
import sys
import os
import subprocess
Expand All @@ -9,11 +8,15 @@
from getpass import getpass

from netmiko.utilities import load_devices, display_inventory
from netmiko.utilities import obtain_netmiko_filename, write_tmp_file, ensure_dir_exists
from netmiko.utilities import write_tmp_file, ensure_dir_exists
from netmiko.utilities import find_netmiko_dir
from netmiko.utilities import SHOW_RUN_MAPPER
from netmiko.cli_tools import ERROR_PATTERN, GREP, MAX_WORKERS, __version__
from netmiko.cli_tools.cli_helpers import obtain_devices, update_device_params, ssh_conn
from netmiko.cli_tools.helpers import obtain_devices, update_device_params, ssh_conn
from netmiko.cli_tools.argument_handling import parse_arguments


COMMAND = "netmiko-grep"


def grepx(files, pattern, grep_options, use_colors=True):
Expand All @@ -36,63 +39,21 @@ def grepx(files, pattern, grep_options, use_colors=True):
return ""


def parse_arguments(args):
"""Parse command-line arguments."""
description = "Grep pattern search on Netmiko output (defaults to running-config)"
parser = argparse.ArgumentParser(description=description)
parser.add_argument(
"pattern", nargs="?", help="Pattern to search for", action="store", type=str
)
parser.add_argument(
"devices",
nargs="?",
help="Device or group to connect to",
action="store",
type=str,
)
parser.add_argument(
"--cmd",
help="Remote command to execute",
action="store",
default=None,
type=str,
)
parser.add_argument("--username", help="Username", action="store", type=str)
parser.add_argument("--password", help="Password", action="store_true")
parser.add_argument("--secret", help="Enable Secret", action="store_true")
parser.add_argument("--use-cache", help="Use cached files", action="store_true")
parser.add_argument(
"--list-devices", help="List devices from inventory", action="store_true"
)
parser.add_argument(
"--display-runtime", help="Display program runtime", action="store_true"
)
parser.add_argument(
"--hide-failed", help="Hide failed devices", action="store_true"
)
parser.add_argument("--version", help="Display version", action="store_true")
cli_args = parser.parse_args(args)
if not cli_args.list_devices and not cli_args.version:
if not cli_args.devices or not cli_args.pattern:
parser.error("Grep pattern or devices not specified.")
return cli_args


def main_ep():
sys.exit(main(sys.argv[1:]))


def main(args):
start_time = datetime.now()
cli_args = parse_arguments(args)
cli_args = parse_arguments(args, COMMAND)

cli_username = cli_args.username if cli_args.username else None
cli_password = getpass() if cli_args.password else None
cli_secret = getpass("Enable secret: ") if cli_args.secret else None

version = cli_args.version
if version:
print("netmiko-grep v{}".format(__version__))
print(f"{COMMAND} v{__version__}")
return 0
list_devices = cli_args.list_devices
if list_devices:
Expand All @@ -106,7 +67,6 @@ def main(args):
cmd_arg = True
device_or_group = cli_args.devices.strip()
pattern = cli_args.pattern
use_cached_files = cli_args.use_cache
hide_failed = cli_args.hide_failed

# DEVICE LOADING #####
Expand All @@ -116,57 +76,44 @@ def main(args):
my_files = []
failed_devices = []
results = {}
if not use_cached_files:

# UPDATE DEVICE PARAMS (WITH CLI ARGS) #####
device_tasks = []
for device_name, device_params in devices.items():
update_device_params(
device_params,
username=cli_username,
password=cli_password,
secret=cli_secret,
)
if not cmd_arg:
device_type = device_params["device_type"]
cli_command = SHOW_RUN_MAPPER.get(device_type, "show run")
device_tasks.append(
{
"device_name": device_name,
"device_params": device_params,
"cli_command": cli_command,
}
)

# THREADING #####
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = [executor.submit(ssh_conn, **kwargs) for kwargs in device_tasks]
for future in as_completed(futures):
device_name, output = future.result()
results[device_name] = output

netmiko_base_dir, netmiko_full_dir = find_netmiko_dir()
ensure_dir_exists(netmiko_base_dir)
ensure_dir_exists(netmiko_full_dir)
for device_name, output in results.items():

file_name = write_tmp_file(device_name, output)
if ERROR_PATTERN not in output:
my_files.append(file_name)
else:
failed_devices.append(device_name)
else:
for device_name in devices:
file_name = obtain_netmiko_filename(device_name)
try:
with open(file_name) as f:
output = f.read()
except IOError:
return "Some cache files are missing: unable to use --use-cache option."
if ERROR_PATTERN not in output:
my_files.append(file_name)
else:
failed_devices.append(device_name)

# UPDATE DEVICE PARAMS (WITH CLI ARGS) #####
device_tasks = []
for device_name, device_params in devices.items():
update_device_params(
device_params,
username=cli_username,
password=cli_password,
secret=cli_secret,
)
if not cmd_arg:
device_type = device_params["device_type"]
cli_command = SHOW_RUN_MAPPER.get(device_type, "show run")
device_tasks.append(
{
"device_name": device_name,
"device_params": device_params,
"cli_command": cli_command,
}
)

# THREADING #####
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = [executor.submit(ssh_conn, **kwargs) for kwargs in device_tasks]
for future in as_completed(futures):
device_name, output = future.result()
results[device_name] = output

netmiko_base_dir, netmiko_full_dir = find_netmiko_dir()
ensure_dir_exists(netmiko_base_dir)
ensure_dir_exists(netmiko_full_dir)
for device_name, output in results.items():

file_name = write_tmp_file(device_name, output)
if ERROR_PATTERN not in output:
my_files.append(file_name)
else:
failed_devices.append(device_name)

grep_options = []
grepx(my_files, pattern, grep_options)
Expand Down

0 comments on commit 23fa073

Please sign in to comment.