From 18738c81dacbc9b274409091b29497b85cf1b442 Mon Sep 17 00:00:00 2001 From: Nathaniel Case Date: Thu, 7 Jul 2016 14:32:19 -0400 Subject: [PATCH 1/3] New ModuleStub solution --- lib/ansible/module_utils/network.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/ansible/module_utils/network.py b/lib/ansible/module_utils/network.py index 4d80de7b03..24f87a5acf 100644 --- a/lib/ansible/module_utils/network.py +++ b/lib/ansible/module_utils/network.py @@ -67,6 +67,13 @@ def disconnect(module): module.fail_json(msg=exc.message) +class ModuleStub(object): + def __init__(self, argument_spec, fail_json): + self.params = dict() + for key, value in argument_spec.items(): + self.params[key] = value.get('default') + self.fail_json = fail_json + class Command(object): def __init__(self, command, output=None, prompt=None, response=None, From b4d36f6ed4a444282c9160e5592f9f0fecf1c3c0 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Thu, 7 Jul 2016 14:32:48 -0400 Subject: [PATCH 2/3] Implement IOS restconf --- lib/ansible/module_utils/ios.py | 161 +++++++++++++++++++++++++++++++- 1 file changed, 159 insertions(+), 2 deletions(-) diff --git a/lib/ansible/module_utils/ios.py b/lib/ansible/module_utils/ios.py index a58ce10a75..c67682b394 100644 --- a/lib/ansible/module_utils/ios.py +++ b/lib/ansible/module_utils/ios.py @@ -19,8 +19,162 @@ import re -from ansible.module_utils.network import Command, NetCli, NetworkError, get_module -from ansible.module_utils.network import register_transport, to_list +from ansible.module_utils.basic import json +from ansible.module_utils.network import Command, ModuleStub, NetCli, NetworkError +from ansible.module_utils.network import add_argument, get_module, register_transport, to_list +from ansible.module_utils.netcfg import NetworkConfig +from ansible.module_utils.urls import fetch_url, url_argument_spec, urlparse + +add_argument('use_ssl', dict(default=True, type='bool')) +add_argument('validate_certs', dict(default=True, type='bool')) + +def argument_spec(): + return dict( + running_config=dict(aliases=['config']), + save_config=dict(default=False, aliases=['save']), + force=dict(type='bool', default=False) + ) +ios_argument_spec = argument_spec() + +def get_config(module, include_defaults=False): + config = module.params['running_config'] + if not config and not include_defaults: + config = module.config.get_config() + else: + config = module.cli('show running-config all')[0] + return NetworkConfig(indent=1, contents=config) + + +class Restconf(object): + + DEFAULT_HEADERS = { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + } + + def __init__(self): + self.url = None + + self.url_args = ModuleStub(url_argument_spec(), self._error) + + self.token = None + self.link = None + + self._connected = False + self.default_output = 'text' + + def _error(self, msg): + raise NetworkError(msg, url=self.url) + + def connect(self, params, **kwargs): + host = params['host'] + port = params['port'] or 55443 + + self.url_args.params['url_username'] = params['username'] + self.url_args.params['url_password'] = params['password'] + self.url_args.params['validate_certs'] = params['validate_certs'] + + self.url = 'https://%s:%s/api/v1/' % (host, port) + + response = self.post('auth/token-services') + + self.token = response['token-id'] + self.link = response['link'] + self._connected = True + + def disconnect(self): + self.delete(self.link) + self._connected = False + + def authorize(self): + pass + + ### REST methods ### + + def request(self, method, path, data=None, headers=None): + + headers = headers or self.DEFAULT_HEADERS + + if self.token: + headers['X-Auth-Token'] = self.token + + if path.startswith('/'): + path = path[1:] + + url = urlparse.urljoin(self.url, path) + + if data: + data = json.dumps(data) + + response, headers = fetch_url(self.url_args, url, data=data, + headers=headers, method=method) + + if not 200 <= headers['status'] <= 299: + raise NetworkError(response=response, **headers) + + if int(headers['content-length']) > 0: + if headers['content-type'].startswith('application/json'): + response = json.load(response) + elif headers['content-type'].startswith('text/plain'): + response = str(response.read()) + + return response + + def get(self, path, data=None, headers=None): + return self.request('GET', path, data, headers) + + def put(self, path, data=None, headers=None): + return self.request('PUT', path, data, headers) + + def post(self, path, data=None, headers=None): + return self.request('POST', path, data, headers) + + def delete(self, path, data=None, headers=None): + return self.request('DELETE', path, data, headers) + + + ### implementation of Cli ### + + def run_commands(self, commands): + responses = list() + for cmd in to_list(commands): + if str(cmd).startswith('show '): + cmd = str(cmd)[4:] + responses.append(self.execute(str(cmd))) + return responses + + def execute(self, command): + data = dict(show=command) + response = self.put('global/cli', data=data) + return response['results'] + + + ### implementation of Config ### + + def configure(self, commands): + config = list() + for c in commands: + config.append(str(c)) + data = dict(config='\n'.join(config)) + self.put('global/cli', data=data) + + def load_config(self, commands, **kwargs): + raise NotImplementedError + + def get_config(self, **kwargs): + hdrs = {'Content-type': 'text/plain', 'Accept': 'text/plain'} + return self.get('global/running-config', headers=hdrs) + + def commit_config(self, **kwargs): + raise NotImplementedError + + def abort_config(self, **kwargs): + raise NotImplementedError + + def save_config(self): + self.put('/api/v1/global/save-config') +Restconf = register_transport('restconf')(Restconf) + class Cli(NetCli): CLI_PROMPTS_RE = [ @@ -72,6 +226,9 @@ class Cli(NetCli): def abort_config(self, **kwargs): raise NotImplementedError + def save_config(self): + self.execute(['copy running-config startup-config']) + def run_commands(self, commands): cmds = to_list(commands) responses = self.execute(cmds) From af5fba759f497997184de45c2f609f775c45305d Mon Sep 17 00:00:00 2001 From: Nathaniel Case Date: Thu, 7 Jul 2016 16:06:31 -0400 Subject: [PATCH 3/3] EOS new ModuleStub As per #16575 --- lib/ansible/module_utils/eos.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/ansible/module_utils/eos.py b/lib/ansible/module_utils/eos.py index 39771d0f9c..74e43acf23 100644 --- a/lib/ansible/module_utils/eos.py +++ b/lib/ansible/module_utils/eos.py @@ -19,9 +19,9 @@ import re -from ansible.module_utils.basic import json, get_exception, AnsibleModule -from ansible.module_utils.network import Command, NetCli, NetworkError, get_module -from ansible.module_utils.network import add_argument, register_transport, to_list +from ansible.module_utils.basic import json, get_exception +from ansible.module_utils.network import Command, ModuleStub, NetCli, NetworkError +from ansible.module_utils.network import add_argument, get_module, register_transport, to_list from ansible.module_utils.netcfg import NetworkConfig from ansible.module_utils.urls import fetch_url, url_argument_spec @@ -200,8 +200,7 @@ class Eapi(EosConfigMixin): def __init__(self): self.url = None - self.url_args = AnsibleModule(url_argument_spec()) - self.url_args.fail_json = self._error + self.url_args = ModuleStub(url_argument_spec(), self._error) self.enable = None self.default_output = 'json' self._connected = False