Skip to content

Commit

Permalink
Merge branch 'develop' into jkala-netmiko-merge
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffkala authored Sep 19, 2024
2 parents fa6ea74 + 3bbc968 commit 031ac75
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 332 deletions.
14 changes: 14 additions & 0 deletions docs/dev/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Changelog

## 3.2.0

* [#144] force the enable call to allow many cisco ios platforms to work
* [#149] Enhanced Jinja Error Handling and Stack Trace Logging by @jmpettit

### New Contributors
* @jmpettit made their first contribution in https://github.com/nautobot/nornir-nautobot/pull/149

**Full Changelog**: https://github.com/nautobot/nornir-nautobot/compare/v3.1.2...v3.2.0

## 3.1.2

- [#145](https://github.com/nautobot/nornir-nautobot/pull/145) Update httpx

## 3.1.1

- [#137](https://github.com/nautobot/nornir-nautobot/pull/137) Update to new pynautobot ssl verification
Expand Down
9 changes: 8 additions & 1 deletion docs/task/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,11 @@ class DispatcherMixin:
if isinstance(config_context, int):
return config_context
return cls.tcp_port
```
```

## Environment Variables

| Environment Variable | Explanation |
| ----- | ----------- |
| NORNIR_NAUTOBOT_REVERT_IN_SECONDS | Amount in seconds to revert if a config based method fails. |
| NORNIR_NAUTOBOT_NETMIKO_ENABLE_DEFAULT | Override the default(True) to not automatically call the `enable` function before running commands. |
38 changes: 38 additions & 0 deletions examples/basic_with_task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Example with a actual dispatcher task."""

import logging
import os
from nornir import InitNornir
from nornir_utils.plugins.functions import print_result
from nornir_nautobot.plugins.tasks.dispatcher import dispatcher


LOGGER = logging.getLogger(__name__)

my_nornir = InitNornir(
inventory={
"plugin": "NautobotInventory",
"options": {
"nautobot_url": "http://localhost:8080/",
"nautobot_token": "0123456789abcdef0123456789abcdef01234567",
"filter_parameters": {"location": "Site 1"},
"ssl_verify": False,
},
},
)
my_nornir.inventory.defaults.username = os.getenv("NORNIR_USERNAME")
my_nornir.inventory.defaults.password = os.getenv("NORNIR_PASSWORD")

for nr_host, nr_obj in my_nornir.inventory.hosts.items():
network_driver = my_nornir.inventory.hosts[nr_host].platform
result = my_nornir.run(
task=dispatcher,
logger=LOGGER,
method="get_config",
obj=nr_host,
framework="netmiko",
backup_file="./ios.cfg",
remove_lines=None,
substitute_lines=None,
)
print_result(result)
50 changes: 24 additions & 26 deletions nornir_nautobot/plugins/tasks/dispatcher/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@
from nornir_jinja2.plugins.tasks import template_file
from nornir_napalm.plugins.tasks import napalm_configure, napalm_get
from nornir_netmiko.tasks import netmiko_send_command, netmiko_send_config

from nornir_nautobot.exceptions import NornirNautobotException
from nornir_nautobot.utils.helpers import make_folder
from nornir_nautobot.utils.helpers import make_folder, get_stack_trace, is_truthy


_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -149,6 +149,7 @@ def generate_config(
jinja_filters: Optional[dict] = None,
jinja_env: Optional[jinja2.Environment] = None,
) -> Result:
# pylint: disable=too-many-locals
"""A small wrapper around template_file Nornir task.
Args:
Expand All @@ -174,29 +175,22 @@ def generate_config(
jinja_env=jinja_env,
)[0].result
except NornirSubTaskError as exc:
if isinstance(exc.result.exception, jinja2.exceptions.UndefinedError): # pylint: disable=no-else-raise
error_msg = (
f"`E1010:` There was a jinja2.exceptions.UndefinedError error: ``{str(exc.result.exception)}``"
)
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

elif isinstance(exc.result.exception, jinja2.TemplateSyntaxError):
error_msg = (f"`E1011:` There was a jinja2.TemplateSyntaxError error: ``{str(exc.result.exception)}``",)
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

elif isinstance(exc.result.exception, jinja2.TemplateNotFound):
error_msg = f"`E1012:` There was an issue finding the template and a jinja2.TemplateNotFound error was raised: ``{str(exc.result.exception)}``"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

elif isinstance(exc.result.exception, jinja2.TemplateError):
error_msg = f"`E1013:` There was an issue general Jinja error: ``{str(exc.result.exception)}``"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

error_msg = f"`E1014:` Failed with an unknown issue. `{exc.result.exception}`"
stack_trace = get_stack_trace(exc.result.exception)

error_mapping = {
jinja2.exceptions.UndefinedError: ("E1010", "Undefined variable in Jinja2 template"),
jinja2.TemplateSyntaxError: ("E1011", "Syntax error in Jinja2 template"),
jinja2.TemplateNotFound: ("E1012", "Jinja2 template not found"),
jinja2.TemplateError: ("E1013", "General Jinja2 template error"),
}

for error, (code, message) in error_mapping.items():
if isinstance(exc.result.exception, error):
error_msg = f"`{code}:` {message} - ``{str(exc.result.exception)}``\n```\n{stack_trace}\n```"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

error_msg = f"`E1014:` Unknown error - `{exc.result.exception}`\n```\n{stack_trace}\n```"
logger.error(error_msg, extra={"object": obj})
raise NornirNautobotException(error_msg)

Expand Down Expand Up @@ -459,7 +453,11 @@ def get_config(
command = cls.config_command

try:
result = task.run(task=netmiko_send_command, command_string=command)
result = task.run(
task=netmiko_send_command,
command_string=command,
enable=is_truthy(os.getenv("NORNIR_NAUTOBOT_NETMIKO_ENABLE_DEFAULT", default="True")),
)
except NornirSubTaskError as exc:
if isinstance(exc.result.exception, NetmikoAuthenticationException):
error_msg = f"`E1017:` Failed with an authentication issue: `{exc.result.exception}`"
Expand Down
29 changes: 29 additions & 0 deletions nornir_nautobot/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import logging
import importlib
import traceback

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -31,3 +32,31 @@ def import_string(dotted_path):
return getattr(importlib.import_module(module_name), class_name)
except (ModuleNotFoundError, AttributeError):
return None


def get_stack_trace(exc: Exception) -> str:
"""Converts the provided exception's stack trace into a string."""
stack_trace_lines = traceback.format_exception(type(exc), exc, exc.__traceback__)
return "".join(stack_trace_lines)


def is_truthy(arg):
"""Convert "truthy" strings into Booleans.
Args:
arg (str): Truthy string (True values are y, yes, t, true, on and 1; false values are n, no,
f, false, off and 0. Raises ValueError if val is anything else.
Examples:
>>> is_truthy('yes')
True
"""
if isinstance(arg, bool):
return arg

val = str(arg).lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return True
if val in ("n", "no", "f", "false", "off", "0"):
return False
return True
Loading

0 comments on commit 031ac75

Please sign in to comment.