From 9aa85470162e518c56fff930f7f12e65c7eb020c Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Mon, 28 Nov 2016 12:49:40 -0500 Subject: [PATCH] adds two new plugins that use ansible-connection for persistence (#18572) * adds new connection plugin `network_cli` which builds on paramiko * adds new plugin `terminal` used for manipulating network_cli terminals * adds new field to play_context `network_os` settable as ansible_network_os This commit adds the plugins necesary to establish a persistent cli connection to network devices of ssh. It builds on the paramiko connection plugin to create a shell environment that will persistent through ansible-connection. The `newtork_cli` plugin then uses the network_os in the instance of PlayContext to load the appropriate network OS environment plugin for handling opening and closing of shells as well as privilege escalation. --- lib/ansible/playbook/play_context.py | 2 + lib/ansible/plugins/__init__.py | 8 + lib/ansible/plugins/connection/network_cli.py | 190 ++++++++++++++++++ .../plugins/connection/paramiko_ssh.py | 5 +- lib/ansible/plugins/terminal/__init__.py | 71 +++++++ lib/ansible/plugins/terminal/asa.py | 75 +++++++ lib/ansible/plugins/terminal/eos.py | 82 ++++++++ lib/ansible/plugins/terminal/ios.py | 82 ++++++++ lib/ansible/plugins/terminal/iosxr.py | 55 +++++ lib/ansible/plugins/terminal/junos.py | 47 +++++ lib/ansible/plugins/terminal/nxos.py | 56 ++++++ lib/ansible/plugins/terminal/sros.py | 46 +++++ lib/ansible/plugins/terminal/vyos.py | 47 +++++ 13 files changed, 764 insertions(+), 2 deletions(-) create mode 100644 lib/ansible/plugins/connection/network_cli.py create mode 100644 lib/ansible/plugins/terminal/__init__.py create mode 100644 lib/ansible/plugins/terminal/asa.py create mode 100644 lib/ansible/plugins/terminal/eos.py create mode 100644 lib/ansible/plugins/terminal/ios.py create mode 100644 lib/ansible/plugins/terminal/iosxr.py create mode 100644 lib/ansible/plugins/terminal/junos.py create mode 100644 lib/ansible/plugins/terminal/nxos.py create mode 100644 lib/ansible/plugins/terminal/sros.py create mode 100644 lib/ansible/plugins/terminal/vyos.py diff --git a/lib/ansible/playbook/play_context.py b/lib/ansible/playbook/play_context.py index c50486e5eae..1eac6f05bf1 100644 --- a/lib/ansible/playbook/play_context.py +++ b/lib/ansible/playbook/play_context.py @@ -60,6 +60,7 @@ MAGIC_VARIABLE_MAPPING = dict( private_key_file = ('ansible_ssh_private_key_file', 'ansible_private_key_file'), pipelining = ('ansible_ssh_pipelining', 'ansible_pipelining'), shell = ('ansible_shell_type',), + network_os = ('ansible_network_os',), become = ('ansible_become',), become_method = ('ansible_become_method',), become_user = ('ansible_become_user',), @@ -164,6 +165,7 @@ class PlayContext(Base): _private_key_file = FieldAttribute(isa='string', default=C.DEFAULT_PRIVATE_KEY_FILE) _timeout = FieldAttribute(isa='int', default=C.DEFAULT_TIMEOUT) _shell = FieldAttribute(isa='string') + _network_os = FieldAttribute(isa='string') _ssh_args = FieldAttribute(isa='string', default=C.ANSIBLE_SSH_ARGS) _ssh_common_args = FieldAttribute(isa='string') _sftp_extra_args = FieldAttribute(isa='string') diff --git a/lib/ansible/plugins/__init__.py b/lib/ansible/plugins/__init__.py index 9e2229e8c96..e457e7e02d8 100644 --- a/lib/ansible/plugins/__init__.py +++ b/lib/ansible/plugins/__init__.py @@ -509,3 +509,11 @@ strategy_loader = PluginLoader( 'strategy_plugins', required_base_class='StrategyBase', ) + +terminal_loader = PluginLoader( + 'TerminalModule', + 'ansible.plugins.terminal', + 'terminal_plugins', + 'terminal_plugins' +) + diff --git a/lib/ansible/plugins/connection/network_cli.py b/lib/ansible/plugins/connection/network_cli.py new file mode 100644 index 00000000000..c7491d352a3 --- /dev/null +++ b/lib/ansible/plugins/connection/network_cli.py @@ -0,0 +1,190 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import socket +import json +import signal +import datetime + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils.six.moves import StringIO +from ansible.plugins import terminal_loader +from ansible.plugins.connection.paramiko_ssh import Connection as _Connection + + +class Connection(_Connection): + ''' CLI SSH based connections on Paramiko ''' + + transport = 'network_cli' + has_pipelining = False + + def __init__(self, play_context, new_stdin, *args, **kwargs): + super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs) + + assert self._play_context.network_os, 'ansible_network_os must be set' + + self._terminal = terminal_loader.get(self._play_context.network_os, self) + if not self._terminal: + raise AnsibleConnectionFailure('network os %s is not supported' % self._play_context.network_os) + + self._shell = None + + self._matched_prompt = None + self._matched_pattern = None + self._last_response = None + self._history = list() + + def update_play_context(self, play_context): + if self._play_context.become is False and play_context.become is True: + auth_pass = play_context.become_pass + self._terminal.on_authorize(passwd=auth_pass) + + elif self._play_context.become is True and not play_context.become: + self._terminal.on_deauthorize() + + self._play_context = play_context + + def _connect(self): + super(Connection, self)._connect() + return (0, 'connected', '') + + def open_shell(self, timeout=10): + self._shell = self.ssh.invoke_shell() + self._shell.settimeout(self._play_context.timeout) + + self.receive() + + if self._shell: + self._terminal.on_open_shell() + + if hasattr(self._play_context, 'become'): + if self._play_context.become: + auth_pass = self._play_context.become_pass + self._terminal.on_authorize(passwd=auth_pass) + + def close(self): + self.close_shell() + super(Connection, self).close() + + def close_shell(self): + if self._shell: + self._terminal.on_close_shell() + + if self._terminal.supports_multiplexing and self._shell: + self._shell.close() + self._shell = None + + return (0, 'shell closed', '') + + def receive(self, obj=None): + recv = StringIO() + handled = False + + self._matched_prompt = None + + while True: + data = self._shell.recv(256) + + recv.write(data) + recv.seek(recv.tell() - 256) + + window = self._strip(recv.read()) + + if obj and (obj.get('prompt') and not handled): + handled = self._handle_prompt(window, obj) + + if self._find_prompt(window): + self._last_response = recv.getvalue() + resp = self._strip(self._last_response) + return self._sanitize(resp, obj) + + def send(self, obj): + try: + command = obj['command'] + self._history.append(command) + self._shell.sendall('%s\r' % command) + return self.receive(obj) + except (socket.timeout, AttributeError): + raise AnsibleConnectionFailure("timeout trying to send command: %s" % command.strip()) + + def _strip(self, data): + for regex in self._terminal.ansi_re: + data = regex.sub('', data) + return data + + def _handle_prompt(self, resp, obj): + prompt = re.compile(obj['prompt'], re.I) + answer = obj['answer'] + match = prompt.search(resp) + if match: + self._shell.sendall('%s\r' % answer) + return True + + def _sanitize(self, resp, obj=None): + cleaned = [] + command = obj.get('command') if obj else None + for line in resp.splitlines(): + if (command and line.startswith(command.strip())) or self._find_prompt(line): + continue + cleaned.append(line) + return str("\n".join(cleaned)).strip() + + def _find_prompt(self, response): + for regex in self._terminal.terminal_errors_re: + if regex.search(response): + raise AnsibleConnectionFailure(response) + + for regex in self._terminal.terminal_prompts_re: + match = regex.search(response) + if match: + self._matched_pattern = regex.pattern + self._matched_prompt = match.group() + return True + + def alarm_handler(self, signum, frame): + self.close_shell() + + def exec_command(self, cmd): + ''' {'command': , 'prompt': , 'answer': } ''' + + try: + obj = json.loads(cmd) + except ValueError: + obj = {'command': str(cmd).strip()} + + if obj['command'] == 'close_shell()': + return self.close_shell() + elif obj['command'] == 'prompt()': + return (0, self._matched_prompt, '') + elif obj['command'] == 'history()': + return (0, self._history, '') + + try: + if self._shell is None: + self.open_shell() + except AnsibleConnectionFailure as exc: + return (1, '', str(exc)) + + try: + out = self.send(obj) + return (0, out, '') + except (AnsibleConnectionFailure, ValueError) as exc: + return (1, '', str(exc)) diff --git a/lib/ansible/plugins/connection/paramiko_ssh.py b/lib/ansible/plugins/connection/paramiko_ssh.py index 9a0dbf7e275..eac773620a0 100644 --- a/lib/ansible/plugins/connection/paramiko_ssh.py +++ b/lib/ansible/plugins/connection/paramiko_ssh.py @@ -421,8 +421,9 @@ class Connection(ConnectionBase): SSH_CONNECTION_CACHE.pop(cache_key, None) SFTP_CONNECTION_CACHE.pop(cache_key, None) - if self.sftp is not None: - self.sftp.close() + if hasattr(self, 'sftp'): + if self.sftp is not None: + self.sftp.close() if C.HOST_KEY_CHECKING and C.PARAMIKO_RECORD_HOST_KEYS and self._any_keys_added(): diff --git a/lib/ansible/plugins/terminal/__init__.py b/lib/ansible/plugins/terminal/__init__.py new file mode 100644 index 00000000000..3242559084c --- /dev/null +++ b/lib/ansible/plugins/terminal/__init__.py @@ -0,0 +1,71 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from abc import ABCMeta, abstractmethod + +from ansible.compat.six import with_metaclass +from ansible.errors import AnsibleConnectionFailure + + +class TerminalBase(with_metaclass(ABCMeta, object)): + ''' + A base class for implementing cli connections + ''' + + terminalprompts_re = [] + + terminalerrors_re = [] + + ansi_re = [ + re.compile(r'(\x1b\[\?1h\x1b=)'), + re.compile(r'\x08.') + ] + + supports_multiplexing = True + + def __init__(self, connection): + self._connection = connection + + def _exec_cli_command(self, cmd, check_rc=True): + rc, out, err = self._connection.exec_command(cmd) + if check_rc and rc != 0: + raise AnsibleConnectionFailure(err) + return rc, out, err + + def _get_prompt(self): + for cmd in ['\n', 'prompt()']: + rc, out, err = self._exec_cli_command(cmd) + return out + + def on_open_shell(self): + pass + + def on_close_shell(self): + pass + + def on_authorize(self, passwd=None): + pass + + def on_deauthorize(self): + pass + diff --git a/lib/ansible/plugins/terminal/asa.py b/lib/ansible/plugins/terminal/asa.py new file mode 100644 index 00000000000..8737b72c482 --- /dev/null +++ b/lib/ansible/plugins/terminal/asa.py @@ -0,0 +1,75 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + terminal_errors_re = [ + re.compile(r"% ?Error"), + re.compile(r"^% \w+", re.M), + re.compile(r"% ?Bad secret"), + re.compile(r"invalid input", re.I), + re.compile(r"(?:incomplete|ambiguous) command", re.I), + re.compile(r"connection timed out", re.I), + re.compile(r"[^\r\n]+ not found", re.I), + re.compile(r"'[^']' +returned error code: ?\d+"), + ] + + def authorize(self, passwd=None): + if self._get_prompt().endswith('#'): + return + + cmd = {'command': 'enable'} + if passwd: + cmd['prompt'] = r"[\r\n]?password: $" + cmd['answer'] = passwd + + try: + self._exec_cli_command(json.dumps(cmd)) + self._exec_cli_command('terminal pager 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode') + + def on_deauthorize(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if '(config' in prompt: + self._exec_cli_command('end') + self._exec_cli_command('disable') + + elif prompt.endswith('#'): + self._exec_cli_command('disable') + + diff --git a/lib/ansible/plugins/terminal/eos.py b/lib/ansible/plugins/terminal/eos.py new file mode 100644 index 00000000000..d84c6fb1d6e --- /dev/null +++ b/lib/ansible/plugins/terminal/eos.py @@ -0,0 +1,82 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + terminal_errors_re = [ + re.compile(r"% ?Error"), + re.compile(r"^% \w+", re.M), + re.compile(r"% User not present"), + re.compile(r"% ?Bad secret"), + re.compile(r"invalid input", re.I), + re.compile(r"(?:incomplete|ambiguous) command", re.I), + re.compile(r"connection timed out", re.I), + re.compile(r"[^\r\n]+ not found", re.I), + re.compile(r"'[^']' +returned error code: ?\d+"), + re.compile(r"[^\r\n]\/bin\/(?:ba)?sh") + ] + + def on_open_shell(self): + try: + self._exec_cli_command('terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + def on_authorize(self, passwd=None): + if self._get_prompt().endswith('#'): + return + + cmd = {'command': 'enable'} + if passwd: + cmd['prompt'] = r"[\r\n]?password: $" + cmd['answer'] = passwd + + try: + self._exec_cli_command(json.dumps(cmd)) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode') + + def on_deauthorize(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if '(config' in prompt: + self._exec_cli_command('end') + self._exec_cli_command('disable') + + elif prompt.endswith('#'): + self._exec_cli_command('disable') + + diff --git a/lib/ansible/plugins/terminal/ios.py b/lib/ansible/plugins/terminal/ios.py new file mode 100644 index 00000000000..5a39acf06d6 --- /dev/null +++ b/lib/ansible/plugins/terminal/ios.py @@ -0,0 +1,82 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + terminal_errors_re = [ + re.compile(r"% ?Error"), + re.compile(r"^% \w+", re.M), + re.compile(r"% ?Bad secret"), + re.compile(r"invalid input", re.I), + re.compile(r"(?:incomplete|ambiguous) command", re.I), + re.compile(r"connection timed out", re.I), + re.compile(r"[^\r\n]+ not found", re.I), + re.compile(r"'[^']' +returned error code: ?\d+"), + ] + + supports_multiplexing = False + + def on_open_shell(self): + try: + self._exec_cli_command('terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + def on_authorize(self, passwd=None): + if self._get_prompt().endswith('#'): + return + + cmd = {'command': 'enable'} + if passwd: + cmd['prompt'] = r"[\r\n]?password: $" + cmd['answer'] = passwd + + try: + self._exec_cli_command(json.dumps(cmd)) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to elevate privilege to enable mode') + + def on_deauthorize(self): + prompt = self._get_prompt() + if prompt is None: + # if prompt is None most likely the terminal is hung up at a prompt + return + + if '(config' in prompt: + self._exec_cli_command('end') + self._exec_cli_command('disable') + + elif prompt.endswith('#'): + self._exec_cli_command('disable') + + diff --git a/lib/ansible/plugins/terminal/iosxr.py b/lib/ansible/plugins/terminal/iosxr.py new file mode 100644 index 00000000000..bcbd73a0d64 --- /dev/null +++ b/lib/ansible/plugins/terminal/iosxr.py @@ -0,0 +1,55 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + terminal_errors_re = [ + re.compile(r"% ?Error"), + re.compile(r"% ?Bad secret"), + re.compile(r"invalid input", re.I), + re.compile(r"(?:incomplete|ambiguous) command", re.I), + re.compile(r"connection timed out", re.I), + re.compile(r"[^\r\n]+ not found", re.I), + re.compile(r"'[^']' +returned error code: ?\d+"), + ] + + supports_multiplexing = False + + def on_open_shell(self): + try: + for cmd in ['terminal length 0', 'terminal exec prompt no-timestamp']: + self._connection.exec_command(cmd) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + diff --git a/lib/ansible/plugins/terminal/junos.py b/lib/ansible/plugins/terminal/junos.py new file mode 100644 index 00000000000..10d8b41da77 --- /dev/null +++ b/lib/ansible/plugins/terminal/junos.py @@ -0,0 +1,47 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + ] + + terminal_errors_re = [ + re.compile(r"unknown command"), + re.compile(r"syntax error,") + ] + + def on_open_shell(self): + try: + for c in ['set cli timestamp disable', 'set cli screen-length 0']: + self._exec_cli_command(c) + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + diff --git a/lib/ansible/plugins/terminal/nxos.py b/lib/ansible/plugins/terminal/nxos.py new file mode 100644 index 00000000000..23bd7555904 --- /dev/null +++ b/lib/ansible/plugins/terminal/nxos.py @@ -0,0 +1,56 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r'[\r\n]?[a-zA-Z]{1}[a-zA-Z0-9-]*[>|#|%](?:\s*)$'), + re.compile(r'[\r\n]?[a-zA-Z]{1}[a-zA-Z0-9-]*\(.+\)#(?:\s*)$') + ] + + terminal_errors_re = [ + re.compile(r"% ?Error"), + re.compile(r"^% \w+", re.M), + re.compile(r"% ?Bad secret"), + re.compile(r"invalid input", re.I), + re.compile(r"(?:incomplete|ambiguous) command", re.I), + re.compile(r"connection timed out", re.I), + re.compile(r"[^\r\n]+ not found", re.I), + re.compile(r"'[^']' +returned error code: ?\d+"), + re.compile(r"syntax error"), + re.compile(r"unknown command"), + re.compile(r"user not present") + ] + + def on_open_shell(self): + try: + self._exec_cli_command('terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + + + diff --git a/lib/ansible/plugins/terminal/sros.py b/lib/ansible/plugins/terminal/sros.py new file mode 100644 index 00000000000..d4ce99a2670 --- /dev/null +++ b/lib/ansible/plugins/terminal/sros.py @@ -0,0 +1,46 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re +import json + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$") + ] + + terminal_errors_re = [ + re.compile(r"^\r\nError:"), + ] + + supports_multiplexing = False + + def on_open_shell(self): + try: + self._exec_cli_command('environment no more') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') diff --git a/lib/ansible/plugins/terminal/vyos.py b/lib/ansible/plugins/terminal/vyos.py new file mode 100644 index 00000000000..664025bb36b --- /dev/null +++ b/lib/ansible/plugins/terminal/vyos.py @@ -0,0 +1,47 @@ +# +# (c) 2016 Red Hat Inc. +# +# This file is part of Ansible +# +# Ansible is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Ansible is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Ansible. If not, see . +# +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import re + +from ansible.plugins.terminal import TerminalBase +from ansible.errors import AnsibleConnectionFailure + + +class TerminalModule(TerminalBase): + + terminal_prompts_re = [ + re.compile(r"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"), + re.compile(r"\@[\w\-\.]+:\S+?[>#\$] ?$") + ] + + terminal_errors_re = [ + re.compile(r"\n\s*Invalid command:"), + re.compile(r"\nCommit failed"), + re.compile(r"\n\s+Set failed"), + ] + + def on_open_shell(self): + try: + self._exec_cli_command('set terminal length 0') + except AnsibleConnectionFailure: + raise AnsibleConnectionFailure('unable to set terminal parameters') + +