diff --git a/plugins/cliconf/ios.py b/plugins/cliconf/ios.py index 324b7f000..adb9c7922 100644 --- a/plugins/cliconf/ios.py +++ b/plugins/cliconf/ios.py @@ -141,14 +141,16 @@ import time from ansible.errors import AnsibleConnectionFailure -from ansible.module_utils._text import to_text +from ansible.module_utils._text import to_bytes, to_text from ansible.module_utils.common._collections_compat import Mapping from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( NetworkConfig, dumps, ) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) from ansible_collections.ansible.netcommon.plugins.plugin_utils.cliconf_base import ( CliconfBase, enable_mode, @@ -166,7 +168,9 @@ def get_config(self, source="running", flags=None, format=None): raise ValueError("fetching configuration from %s is not supported" % source) if format: - raise ValueError("'format' value %s is not supported for get_config" % format) + raise ValueError( + "'format' value %s is not supported for get_config" % format + ) if not flags: flags = [] @@ -256,7 +260,9 @@ def get_diff( if running and diff_match != "none": # running configuration have_src, have_banners = self._extract_banners(running) - running_obj = NetworkConfig(indent=1, contents=have_src, ignore_lines=diff_ignore_lines) + running_obj = NetworkConfig( + indent=1, contents=have_src, ignore_lines=diff_ignore_lines + ) configdiffobjs = candidate_obj.difference( running_obj, path=path, @@ -268,7 +274,9 @@ def get_diff( configdiffobjs = candidate_obj.items have_banners = {} - diff["config_diff"] = dumps(configdiffobjs, "commands") if configdiffobjs else "" + diff["config_diff"] = ( + dumps(configdiffobjs, "commands") if configdiffobjs else "" + ) banners = self._diff_banners(want_banners, have_banners) diff["banner_diff"] = banners if banners else {} return diff @@ -280,14 +288,18 @@ def configure(self): status of commit_confirm :return: None """ - if self.get_option("commit_confirm_timeout") or self.get_option("commit_confirm_immediate"): + if self.get_option("commit_confirm_timeout") or self.get_option( + "commit_confirm_immediate" + ): commit_timeout = ( self.get_option("commit_confirm_timeout") if self.get_option("commit_confirm_timeout") else 1 ) # add default timeout not default: 1 to support above or operation - persistent_command_timeout = self._connection.get_option("persistent_command_timeout") + persistent_command_timeout = self._connection.get_option( + "persistent_command_timeout" + ) # check archive state archive_state = self.send_command("show archive") rollback_state = self.send_command("show archive config rollback timer") @@ -317,36 +329,60 @@ def configure(self): self.send_command("configure terminal") @enable_mode - def edit_config(self, candidate=None, commit=True, replace=None, comment=None): + def edit_config( + self, + candidate=None, + commit=True, + replace=None, + comment=None, + err_responses=None, + ): resp = {} operations = self.get_device_operations() - self.check_edit_config_capability(operations, candidate, commit, replace, comment) + self.check_edit_config_capability( + operations, candidate, commit, replace, comment + ) + + if err_responses: + # update platform default stderr regexes to include modules specific ones + err_responses = [re.compile(to_bytes(err_re)) for err_re in err_responses] + current_stderr_re = self._connection._get_terminal_std_re( + "terminal_stderr_re", + ) + current_stderr_re.extend(err_responses) results = [] requests = [] # commit confirm specific attributes commit_confirm = self.get_option("commit_confirm_immediate") - if commit: - self.configure() - for line in to_list(candidate): - if not isinstance(line, Mapping): - line = {"command": line} - - cmd = line["command"] - if cmd != "end" and cmd[0] != "!": - results.append(self.send_command(**line)) - requests.append(cmd) - - self.send_command("end") - if commit_confirm: - self.send_command("configure confirm") - - else: - raise ValueError("check mode is not supported") - resp["request"] = requests - resp["response"] = results - return resp + try: + if commit: + self.configure() + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {"command": line} + + cmd = line["command"] + if cmd != "end" and cmd[0] != "!": + results.append(self.send_command(**line)) + requests.append(cmd) + + self.send_command("end") + if commit_confirm: + self.send_command("configure confirm") + + else: + raise ValueError("check mode is not supported") + + resp["request"] = requests + resp["response"] = results + return resp + finally: + # always reset terminal regexes to platform default + if err_responses: + for x in err_responses: + current_stderr_re.remove(x) def edit_macro(self, candidate=None, commit=True, replace=None, comment=None): """ @@ -359,7 +395,9 @@ def edit_macro(self, candidate=None, commit=True, replace=None, comment=None): """ resp = {} operations = self.get_device_operations() - self.check_edit_config_capability(operations, candidate, commit, replace, comment) + self.check_edit_config_capability( + operations, candidate, commit, replace, comment + ) results = [] requests = [] @@ -480,7 +518,12 @@ def get_option_values(self): def get_capabilities(self): result = super(Cliconf, self).get_capabilities() - result["rpc"] += ["edit_banner", "get_diff", "run_commands", "get_defaults_flag"] + result["rpc"] += [ + "edit_banner", + "get_diff", + "run_commands", + "get_defaults_flag", + ] result["device_operations"] = self.get_device_operations() result.update(self.get_option_values()) return json.dumps(result) @@ -531,7 +574,9 @@ def run_commands(self, commands=None, check_rc=True): output = cmd.pop("output", None) if output: - raise ValueError("'output' value %s is not supported for run_commands" % output) + raise ValueError( + "'output' value %s is not supported for run_commands" % output + ) try: out = self.send_command(**cmd) @@ -577,8 +622,12 @@ def set_cli_prompt_context(self): " response window: %s" % self._connection._last_recv_window, ) - if re.search(r"config.*\)#", to_text(out, errors="surrogate_then_replace").strip()): - self._connection.queue_message("vvvv", "wrong context, sending end to device") + if re.search( + r"config.*\)#", to_text(out, errors="surrogate_then_replace").strip() + ): + self._connection.queue_message( + "vvvv", "wrong context, sending end to device" + ) self._connection.send_command("end") def _extract_banners(self, config): diff --git a/plugins/module_utils/network/ios/config/vlans/vlans.py b/plugins/module_utils/network/ios/config/vlans/vlans.py index d195c317e..04061e53d 100644 --- a/plugins/module_utils/network/ios/config/vlans/vlans.py +++ b/plugins/module_utils/network/ios/config/vlans/vlans.py @@ -27,11 +27,15 @@ dict_merge, ) -from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import Facts +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import ( + Facts, +) from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.vlans import ( VlansTemplate, ) +err_responses = [r"You must disable"] + class Vlans(ResourceModule): """ @@ -64,7 +68,7 @@ def execute_module(self): """ if self.state not in ["parsed", "gathered"]: self.segregate_resource() - self.run_commands() + self.run_commands(err_responses=self.err_responses) return self.result def segregate_resource(self): diff --git a/plugins/terminal/ios.py b/plugins/terminal/ios.py index a2868b81c..1a324945e 100644 --- a/plugins/terminal/ios.py +++ b/plugins/terminal/ios.py @@ -27,7 +27,9 @@ from ansible.errors import AnsibleConnectionFailure from ansible.module_utils._text import to_bytes, to_text from ansible.utils.display import Display -from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import TerminalBase +from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import ( + TerminalBase, +) display = Display() @@ -64,7 +66,6 @@ class TerminalModule(TerminalBase): re.compile(rb"% BGP: Error initializing topology", re.I), re.compile(rb"%SNMP agent not enabled", re.I), re.compile(rb"% Invalid", re.I), - re.compile(rb"%You must disable", re.I), ] terminal_config_prompt = re.compile(r"^.+\(config(-.*)?\)#$") @@ -96,7 +97,9 @@ def on_open_shell(self): try: self._exec_cli_command(b"screen-length 0") # support to SD-WAN mode _is_sdWan = True - except AnsibleConnectionFailure: # fails as length required for handling prompt + except ( + AnsibleConnectionFailure + ): # fails as length required for handling prompt raise AnsibleConnectionFailure("unable to set terminal parameters") try: if _is_sdWan: