From f9a2b43cc73eb25930841ed459db71bce8e6a678 Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Wed, 18 Sep 2024 17:15:02 -0500 Subject: [PATCH 1/4] add a netmiko merge_config --- nornir_nautobot/plugins/inventory/nautobot.py | 1 + .../plugins/tasks/dispatcher/default.py | 54 ++++++++++++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/nornir_nautobot/plugins/inventory/nautobot.py b/nornir_nautobot/plugins/inventory/nautobot.py index 280a846..8190c56 100644 --- a/nornir_nautobot/plugins/inventory/nautobot.py +++ b/nornir_nautobot/plugins/inventory/nautobot.py @@ -113,6 +113,7 @@ def pynautobot_obj(self) -> pynautobot.core.api.Api: token=self.nautobot_token, threading=self.enable_threading, verify=self.ssl_verify, + api_version=2.2, ) self.api_session.params = {"depth": 1} diff --git a/nornir_nautobot/plugins/tasks/dispatcher/default.py b/nornir_nautobot/plugins/tasks/dispatcher/default.py index 44b4747..34119fe 100644 --- a/nornir_nautobot/plugins/tasks/dispatcher/default.py +++ b/nornir_nautobot/plugins/tasks/dispatcher/default.py @@ -22,7 +22,7 @@ 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 +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 @@ -499,3 +499,55 @@ def get_config( with open(backup_file, "w", encoding="utf8") as filehandler: filehandler.write(running_config) return Result(host=task.host, result={"config": running_config}) + + @classmethod + def merge_config( + cls, + task: Task, + logger, + obj, + config: str, + ) -> Result: + """Send configuration to merge on the device. + + Args: + task (Task): Nornir Task. + logger (logging.Logger): Logger that may be a Nautobot Jobs or Python logger. + obj (Device): A Nautobot Device Django ORM object instance. + config (str): The config set. + + Raises: + NornirNautobotException: Authentication error. + NornirNautobotException: Timeout error. + NornirNautobotException: Other exception. + + Returns: + Result: Nornir Result object with a dict as a result containing what changed and the result of the push. + """ + logger.info("Config merge via netmiko starting", extra={"object": obj}) + # Sending None to napalm_configure for revert_in will disable it, so we don't want a default value. + + try: + push_result = task.run( + task=netmiko_send_config, + config_commands=config.splitlines(), + enable=True, + ) + except NornirSubTaskError as exc: + error_msg = f"`E1015:` Failed with an unknown issue. `{exc.result.exception}`" + logger.error(error_msg, extra={"object": obj}) + raise NornirNautobotException(error_msg) + + logger.info( + f"result: {push_result[0].result}, changed: {push_result.changed}", + extra={"object": obj}, + ) + + if push_result.diff: + logger.info(f"Diff:\n```\n_{push_result.diff}\n```", extra={"object": obj}) + + logger.info("Config merge ended", extra={"object": obj}) + return Result( + host=task.host, + result={"changed": push_result.changed, "result": push_result[0].result}, + ) \ No newline at end of file From fa6ea74ca8fe6d5a58aaa03538820b1d6339c608 Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Thu, 19 Sep 2024 12:48:16 -0500 Subject: [PATCH 2/4] update netmiko merge error handling --- .../plugins/tasks/dispatcher/default.py | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/nornir_nautobot/plugins/tasks/dispatcher/default.py b/nornir_nautobot/plugins/tasks/dispatcher/default.py index 34119fe..c79433b 100644 --- a/nornir_nautobot/plugins/tasks/dispatcher/default.py +++ b/nornir_nautobot/plugins/tasks/dispatcher/default.py @@ -525,8 +525,6 @@ def merge_config( Result: Nornir Result object with a dict as a result containing what changed and the result of the push. """ logger.info("Config merge via netmiko starting", extra={"object": obj}) - # Sending None to napalm_configure for revert_in will disable it, so we don't want a default value. - try: push_result = task.run( task=netmiko_send_config, @@ -534,7 +532,28 @@ def merge_config( enable=True, ) except NornirSubTaskError as exc: - error_msg = f"`E1015:` Failed with an unknown issue. `{exc.result.exception}`" + if isinstance(exc.result.exception, NetmikoAuthenticationException): + error_msg = f"`E1017:` Failed with an authentication issue: `{exc.result.exception}`" + logger.error(error_msg, extra={"object": obj}) + raise NornirNautobotException(error_msg) + + if isinstance(exc.result.exception, NetmikoTimeoutException): + error_msg = f"`E1018:` Failed with a timeout issue. `{exc.result.exception}`" + logger.error(error_msg, extra={"object": obj}) + raise NornirNautobotException(error_msg) + + error_msg = f"`E1016:` Failed with an unknown issue. `{exc.result.exception}`" + logger.error(error_msg, extra={"object": obj}) + raise NornirNautobotException(error_msg) + + if push_result[0].failed: + return push_result + + push_result = push_result[0].result + + # Primarily seen in Cisco devices. + if "Invalid input detected at" in push_result: + error_msg = "`E1019:` Discovered `ERROR: % Invalid input detected at` in the output" logger.error(error_msg, extra={"object": obj}) raise NornirNautobotException(error_msg) @@ -550,4 +569,4 @@ def merge_config( return Result( host=task.host, result={"changed": push_result.changed, "result": push_result[0].result}, - ) \ No newline at end of file + ) From 159c072837bc31a77a52b3647023b4f69c34b2c5 Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Thu, 19 Sep 2024 12:59:56 -0500 Subject: [PATCH 3/4] remove api-version setting --- nornir_nautobot/plugins/inventory/nautobot.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nornir_nautobot/plugins/inventory/nautobot.py b/nornir_nautobot/plugins/inventory/nautobot.py index 8190c56..280a846 100644 --- a/nornir_nautobot/plugins/inventory/nautobot.py +++ b/nornir_nautobot/plugins/inventory/nautobot.py @@ -113,7 +113,6 @@ def pynautobot_obj(self) -> pynautobot.core.api.Api: token=self.nautobot_token, threading=self.enable_threading, verify=self.ssl_verify, - api_version=2.2, ) self.api_session.params = {"depth": 1} From 146c623cb52452c1b0c87fa88962026cab74c41a Mon Sep 17 00:00:00 2001 From: Jeff Kala Date: Wed, 29 Jan 2025 10:30:50 -0600 Subject: [PATCH 4/4] add saving config to merge_config --- .../plugins/tasks/dispatcher/default.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/nornir_nautobot/plugins/tasks/dispatcher/default.py b/nornir_nautobot/plugins/tasks/dispatcher/default.py index 11e3354..dad8a2d 100644 --- a/nornir_nautobot/plugins/tasks/dispatcher/default.py +++ b/nornir_nautobot/plugins/tasks/dispatcher/default.py @@ -22,7 +22,7 @@ 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_netmiko.tasks import netmiko_send_command, netmiko_send_config, netmiko_save_config from nornir_nautobot.exceptions import NornirNautobotException from nornir_nautobot.utils.helpers import make_folder, get_stack_trace, is_truthy @@ -547,8 +547,6 @@ def merge_config( if push_result[0].failed: return push_result - push_result = push_result[0].result - # Primarily seen in Cisco devices. if "Invalid input detected at" in push_result: error_msg = "`E1019:` Discovered `ERROR: % Invalid input detected at` in the output" @@ -556,15 +554,23 @@ def merge_config( raise NornirNautobotException(error_msg) logger.info( - f"result: {push_result[0].result}, changed: {push_result.changed}", + f"result: {push_result[0].result}, changed: {push_result[0].changed}", extra={"object": obj}, ) if push_result.diff: - logger.info(f"Diff:\n```\n_{push_result.diff}\n```", extra={"object": obj}) + logger.info(f"Diff:\n```\n_{push_result[0].diff}\n```", extra={"object": obj}) logger.info("Config merge ended", extra={"object": obj}) + try: + task.run( + task=netmiko_save_config, + confirm=True, + ) + except NornirSubTaskError as exc: + error_msg = f"`E1016:`Saving Config Failed with an unknown issue. `{exc.result.exception}`" + logger.error(error_msg, extra={"object": obj}) return Result( host=task.host, - result={"changed": push_result.changed, "result": push_result[0].result}, + result={"changed": push_result[0].changed, "result": push_result[0].result}, )