Created
November 15, 2016 14:44
-
-
Save komeda-shinji/2f8e5bd4168b75b552e70692afa4899e to your computer and use it in GitHub Desktop.
ansible IOS telnet support
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -u ansible/module_utils/ios.py ansible/module_utils/ios.py | |
--- ansible/module_utils/ios.py 2016-05-11 16:27:12.000000000 +0900 | |
+++ ansible/module_utils/ios.py 2016-05-18 21:57:24.000000000 +0900 | |
@@ -21,11 +21,13 @@ | |
NET_COMMON_ARGS = dict( | |
host=dict(required=True), | |
- port=dict(default=22, type='int'), | |
+ port=dict(type='int'), | |
username=dict(required=True), | |
password=dict(no_log=True), | |
authorize=dict(default=False, type='bool'), | |
auth_pass=dict(no_log=True), | |
+ method=dict(type='str', default='ssh', choices=['ssh', 'telnet']), | |
+ provider=dict() | |
) | |
def to_list(val): | |
@@ -44,13 +46,25 @@ | |
def connect(self, **kwargs): | |
host = self.module.params['host'] | |
- port = self.module.params['port'] or 22 | |
+ method = self.module.params['method'] or 'ssh' | |
+ if method == 'telnet': | |
+ port = self.module.params['port'] or 23 | |
+ else: | |
+ port = self.module.params['port'] or 22 | |
username = self.module.params['username'] | |
password = self.module.params['password'] | |
- self.shell = Shell() | |
- self.shell.open(host, port=port, username=username, password=password) | |
+ if method == 'telnet': | |
+ self.shell = Telnet() | |
+ else: | |
+ self.shell = Shell() | |
+ | |
+ try: | |
+ self.shell.open(host, port=port, username=username, password=password) | |
+ except Exception, exc: | |
+ msg = 'failed to connecto to %s:%s - %s' % (host, port, str(exc)) | |
+ self.module.fail_json(msg=msg) | |
def authorize(self): | |
passwd = self.module.params['auth_pass'] | |
@@ -59,10 +73,16 @@ | |
def send(self, commands): | |
return self.shell.send(commands) | |
-class IosModule(AnsibleModule): | |
+ def receive(self): | |
+ return self.shell.receive() | |
+ | |
+ def close(self): | |
+ return self.shell.close() | |
+ | |
+class NetworkModule(AnsibleModule): | |
def __init__(self, *args, **kwargs): | |
- super(IosModule, self).__init__(*args, **kwargs) | |
+ super(NetworkModule, self).__init__(*args, **kwargs) | |
self.connection = None | |
self._config = None | |
@@ -72,10 +92,19 @@ | |
self._config = self.get_config() | |
return self._config | |
+ def _load_params(self): | |
+ params = super(NetworkModule, self)._load_params() | |
+ provider = params.get('provider') or dict() | |
+ for key, value in provider.items(): | |
+ if key in NET_COMMON_ARGS.keys(): | |
+ params[key] = value | |
+ return params | |
+ | |
def connect(self): | |
try: | |
self.connection = Cli(self) | |
self.connection.connect() | |
+ self.connection.receive() | |
self.execute('terminal length 0') | |
if self.params['authorize']: | |
@@ -92,7 +121,10 @@ | |
return responses | |
def execute(self, commands, **kwargs): | |
- return self.connection.send(commands) | |
+ try: | |
+ return self.connection.send(commands, **kwargs) | |
+ except Exception, exc: | |
+ self.fail_json(msg=exc.message, commands=commands) | |
def disconnect(self): | |
self.connection.close() | |
@@ -107,26 +139,21 @@ | |
return self.execute(cmd)[0] | |
def get_module(**kwargs): | |
- """Return instance of IosModule | |
+ """Return instance of NetworkModule | |
""" | |
argument_spec = NET_COMMON_ARGS.copy() | |
if kwargs.get('argument_spec'): | |
argument_spec.update(kwargs['argument_spec']) | |
kwargs['argument_spec'] = argument_spec | |
- kwargs['check_invalid_arguments'] = False | |
- module = IosModule(**kwargs) | |
+ module = NetworkModule(**kwargs) | |
# HAS_PARAMIKO is set by module_utils/shell.py | |
- if not HAS_PARAMIKO: | |
+ method = module.params['method'] or 'ssh' | |
+ if method == 'ssh' and not HAS_PARAMIKO: | |
module.fail_json(msg='paramiko is required but does not appear to be installed') | |
- # copy in values from local action. | |
- params = json_dict_unicode_to_bytes(json.loads(MODULE_COMPLEX_ARGS)) | |
- for key, value in params.iteritems(): | |
- module.params[key] = value | |
- | |
module.connect() | |
return module |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff -u ansible/modules/core/network/ios/ios_command.py ansible/modules/core/network/ios/ios_command.py | |
--- ansible/modules/core/network/ios/ios_command.py 2016-05-11 16:37:37.000000000 +0900 | |
+++ ansible/modules/core/network/ios/ios_command.py 2016-05-11 23:06:14.000000000 +0900 | |
@@ -120,7 +120,8 @@ | |
commands=dict(type='list'), | |
waitfor=dict(type='list'), | |
retries=dict(default=10, type='int'), | |
- interval=dict(default=1, type='int') | |
+ interval=dict(default=1, type='int'), | |
+ method=dict(type='str', default='ssh', choices=['ssh', 'telnet']), | |
) | |
module = get_module(argument_spec=spec, | |
@@ -163,6 +164,7 @@ | |
from ansible.module_utils.basic import * | |
from ansible.module_utils.urls import * | |
from ansible.module_utils.shell import * | |
+from ansible.module_utils.telnet import * | |
from ansible.module_utils.netcfg import * | |
from ansible.module_utils.ios import * | |
if __name__ == '__main__': |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
cp ansible/module_utils/shell.py ansible/module_utils/telnet.py | |
diff -u ansible/module_utils/telnet.py ansible/module_utils/telnet.py | |
--- ansible/module_utils/telnet.py 2016-05-11 16:27:12.000000000 +0900 | |
+++ ansible/module_utils/telnet.py 2016-05-18 21:58:22.000000000 +0900 | |
@@ -18,19 +18,9 @@ | |
# | |
import re | |
import socket | |
+import telnetlib | |
-# py2 vs py3; replace with six via ziploader | |
-try: | |
- from StringIO import StringIO | |
-except ImportError: | |
- from io import StringIO | |
- | |
-try: | |
- import paramiko | |
- HAS_PARAMIKO = True | |
-except ImportError: | |
- HAS_PARAMIKO = False | |
- | |
+from StringIO import StringIO | |
ANSI_RE = re.compile(r'(\x1b\[\?1h\x1b=)') | |
@@ -48,6 +38,8 @@ | |
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") | |
] | |
def to_list(val): | |
@@ -58,10 +50,10 @@ | |
else: | |
return list() | |
-class ShellError(Exception): | |
+class TelnetError(Exception): | |
def __init__(self, msg, command=None): | |
- super(ShellError, self).__init__(msg) | |
+ super(TelnetError, self).__init__(msg) | |
self.message = msg | |
self.command = command | |
@@ -75,11 +67,12 @@ | |
def __str__(self): | |
return self.command | |
-class Shell(object): | |
+class Telnet(object): | |
def __init__(self): | |
- self.ssh = None | |
- self.shell = None | |
+ self.telnet = None | |
+ | |
+ self._matched_prompt = None | |
self.prompts = list() | |
self.prompts.extend(CLI_PROMPTS_RE) | |
@@ -87,22 +80,16 @@ | |
self.errors = list() | |
self.errors.extend(CLI_ERRORS_RE) | |
- def open(self, host, port=22, username=None, password=None, | |
- timeout=10, key_filename=None): | |
- | |
- self.ssh = paramiko.SSHClient() | |
- self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) | |
- | |
- use_keys = password is None | |
- | |
- self.ssh.connect(host, port=port, username=username, password=password, | |
- timeout=timeout, allow_agent=use_keys, look_for_keys=use_keys, | |
- key_filename=key_filename) | |
- | |
- self.shell = self.ssh.invoke_shell() | |
- self.shell.settimeout(10) | |
- self.shell.sendall("\n") | |
- self.receive() | |
+ def open(self, host, port=23, username=None, password=None, | |
+ timeout=10, key_filename=None, pkey=None, look_for_keys=None, | |
+ allow_agent=False): | |
+ | |
+ self.telnet = telnetlib.Telnet(host, port, timeout) | |
+ if username: | |
+ self.telnet.read_until("Username: ", timeout) | |
+ self.telnet.write(username + "\n") | |
+ self.telnet.read_until("Password: ", timeout) | |
+ self.telnet.write(password + "\n") | |
def strip(self, data): | |
return ANSI_RE.sub('', data) | |
@@ -111,10 +98,10 @@ | |
recv = StringIO() | |
while True: | |
- data = self.shell.recv(200) | |
+ data = self.telnet.read_very_eager() | |
recv.write(data) | |
- recv.seek(recv.tell() - 200) | |
+ recv.seek(recv.tell() - len(data)) | |
window = self.strip(recv.read()) | |
@@ -126,7 +113,7 @@ | |
if self.read(window): | |
resp = self.strip(recv.getvalue()) | |
return self.sanitize(cmd, resp) | |
- except ShellError, exc: | |
+ except TelnetError, exc: | |
exc.command = cmd | |
raise | |
@@ -134,15 +121,15 @@ | |
responses = list() | |
try: | |
for command in to_list(commands): | |
- cmd = '%s\r' % str(command) | |
- self.shell.sendall(cmd) | |
+ cmd = '%s\n' % str(command) | |
+ self.telnet.write(cmd) | |
responses.append(self.receive(command)) | |
except socket.timeout, exc: | |
- raise ShellError("timeout trying to send command", cmd) | |
+ raise TelnetError("timeout trying to send command", cmd) | |
return responses | |
def close(self): | |
- self.shell.close() | |
+ self.telnet.close() | |
def handle_input(self, resp, prompt, response): | |
if not prompt or not response: | |
@@ -155,7 +142,7 @@ | |
match = pr.search(resp) | |
if match: | |
cmd = '%s\r' % ans | |
- self.shell.sendall(cmd) | |
+ self.telnet.sendall(cmd) | |
def sanitize(self, cmd, resp): | |
cleaned = [] | |
@@ -168,17 +155,19 @@ | |
def read(self, response): | |
for regex in self.errors: | |
if regex.search(response): | |
- raise ShellError('%s' % response) | |
+ raise TelnetError('%s' % response) | |
for regex in self.prompts: | |
- if regex.search(response): | |
+ match = regex.search(response) | |
+ if match: | |
+ self._matched_prompt = match.group() | |
return True | |
def get_cli_connection(module): | |
host = module.params['host'] | |
port = module.params['port'] | |
if not port: | |
- port = 22 | |
+ port = 23 | |
username = module.params['username'] | |
password = module.params['password'] | |
@@ -186,8 +175,6 @@ | |
try: | |
cli = Cli() | |
cli.open(host, port=port, username=username, password=password) | |
- except paramiko.ssh_exception.AuthenticationException, exc: | |
- module.fail_json(msg=exc.message) | |
except socket.error, exc: | |
host = '%s:%s' % (host, port) | |
module.fail_json(msg=exc.strerror, errno=exc.errno, host=host) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Have you ever tried to submit this as a Pull Request to the Ansible sources?