diff --git a/lib/ansible/module_utils/nxos.py b/lib/ansible/module_utils/nxos.py new file mode 100644 index 00000000000..5bde5cccd1d --- /dev/null +++ b/lib/ansible/module_utils/nxos.py @@ -0,0 +1,216 @@ +# +# (c) 2015 Peter Sprygada, +# +# 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 . +# +NET_PASSWD_RE = re.compile(r"[\r\n]?password: $", re.I) + +NET_COMMON_ARGS = dict( + host=dict(required=True), + port=dict(type='int'), + username=dict(required=True), + password=dict(no_log=True), + transport=dict(choices=['cli', 'nxapi']), + use_ssl=dict(default=False, type='bool') +) + +NXAPI_COMMAND_TYPES = ['cli_show', 'cli_show_ascii', 'cli_conf', 'bash'] +NXAPI_ENCODINGS = ['json', 'xml'] + +def to_list(val): + if isinstance(val, (list, tuple)): + return list(val) + elif val is not None: + return [val] + else: + return list() + +class Nxapi(object): + + def __init__(self, module): + self.module = module + + # sets the module_utils/urls.py req parameters + self.module.params['url_username'] = module.params['username'] + self.module.params['url_password'] = module.params['password'] + + self.url = None + self.enable = None + + def _get_body(self, commands, command_type, encoding, version='1.2', chunk='0', sid=None): + """Encodes a NXAPI JSON request message + """ + if isinstance(commands, (list, set, tuple)): + commands = ' ;'.join(commands) + + if encoding not in NXAPI_ENCODINGS: + self.module.fail_json("Invalid encoding. Received %s. Expected one of %s" % + (encoding, ','.join(NXAPI_ENCODINGS))) + + msg = { + 'version': version, + 'type': command_type, + 'chunk': chunk, + 'sid': sid, + 'input': commands, + 'output_format': encoding + } + return dict(ins_api=msg) + + def connect(self): + host = self.module.params['host'] + port = self.module.params['port'] + + if self.module.params['use_ssl']: + proto = 'https' + if not port: + port = 443 + else: + proto = 'http' + if not port: + port = 80 + + self.url = '%s://%s:%s/ins' % (proto, host, port) + + def send(self, commands, command_type='cli_show_ascii', encoding='json'): + """Send commands to the device. + """ + clist = to_list(commands) + + if command_type not in NXAPI_COMMAND_TYPES: + self.module.fail_json(msg="Invalid command_type. Received %s. Expected one of %s." % + (command_type, ','.join(NXAPI_COMMAND_TYPES))) + + data = self._get_body(clist, command_type, encoding) + data = self.module.jsonify(data) + + headers = {'Content-Type': 'application/json'} + + response, headers = fetch_url(self.module, self.url, data=data, headers=headers, + method='POST') + + if headers['status'] != 200: + self.module.fail_json(**headers) + + response = self.module.from_json(response.read()) + if 'error' in response: + err = response['error'] + self.module.fail_json(msg='json-rpc error % ' % str(err)) + + return response + +class Cli(object): + + def __init__(self, module): + self.module = module + self.shell = None + + def connect(self, **kwargs): + host = self.module.params['host'] + 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) + + def send(self, commands, encoding='text'): + return self.shell.send(commands) + +class NxosModule(AnsibleModule): + + def __init__(self, *args, **kwargs): + super(NxosModule, self).__init__(*args, **kwargs) + self.connection = None + self._config = None + + @property + def config(self): + if not self._config: + self._config = self.get_config() + return self._config + + def connect(self): + if self.params['transport'] == 'nxapi': + self.connection = Nxapi(self) + else: + self.connection = Cli(self) + + try: + self.connection.connect() + self.execute('terminal length 0') + except Exception, exc: + self.fail_json(msg=exc.message) + + def configure(self, commands): + commands = to_list(commands) + if self.params['transport'] == 'cli': + commands.insert(0, 'configure terminal') + responses = self.execute(commands) + responses.pop(0) + else: + responses = self.execute(commands, command_type='cli_conf') + return responses + + def execute(self, commands, **kwargs): + try: + return self.connection.send(commands, **kwargs) + except Exception, exc: + self.fail_json(msg=exc.message) + + def disconnect(self): + self.connection.close() + + def parse_config(self, cfg): + return parse(cfg, indent=2) + + def get_config(self): + cmd = 'show running-config' + if self.params['include_defaults']: + cmd += ' all' + if self.params['transport'] == 'cli': + return self.execute(cmd)[0] + else: + resp = self.execute(cmd) + if not resp.get('ins_api').get('outputs').get('output').get('body'): + self.fail_json(msg="Unrecognized response: %s" % str(resp)) + return resp['ins_api']['outputs']['output']['body'] + +def get_module(**kwargs): + """Return instance of EosModule + """ + + 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 = NxosModule(**kwargs) + + # HAS_PARAMIKO is set by module_utils/shell.py + if module.params['transport'] == 'cli' 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 diff --git a/lib/ansible/utils/module_docs_fragments/nxos.py b/lib/ansible/utils/module_docs_fragments/nxos.py new file mode 100644 index 00000000000..37d287ea722 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/nxos.py @@ -0,0 +1,69 @@ +# +# (c) 2015, Peter Sprygada +# +# 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 . + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = """ +options: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + device over the specified transport. The value of host is used as + the destination address for the transport. + required: true + port: + description: + - Specifies the port to use when buiding the connection to the remote + device. This value applies to either I(cli) or I(nxapi). The port + value will default to the approriate transport common port if + none is provided in the task. (cli=22, http=80, https=443). + required: false + default: 0 (use common port) + username: + description: + - Configures the usename to use to authenticate the connection to + the remote device. The value of I(username) is used to authenticate + either the CLI login or the nxapi authentication depending on which + transport is used. + required: true + password: + description: + - Specifies the password to use when authentication the connection to + the remote device. This is a common argument used for either I(cli) + or I(nxapi) transports. + required: false + default: null + transport: + description: + - Configures the transport connection to use when connecting to the + remote device. The transport argument supports connectivity to the + device over cli (ssh) or nxapi. + required: true + default: cli + use_ssl: + description: + - Configures the I(transport) to use SSL if set to true only when the + I(transport) argument is configured as nxapi. If the transport + argument is not nxapi, this value is ignored + required: false + default: false + choices: BOOLEANS + +"""