From c2d3b9cbd5ebb22345a253fe27b52b44d4c3a38b Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Sat, 27 Jan 2018 08:03:06 -0500 Subject: [PATCH] refactors nios api shared code to handle provider better (#35393) * refactors nios api shared code to handle provider better This change refactors the shared code to be easily shared between modules, plugins and dynamic inventory scripts. All parts now implement the provider arguments uniformly. This also provides a centralized fix to suppress urllib3 warnings coming from the requests library implemented by infoblox_client * fix up pep8 errors * fix missing var name --- contrib/inventory/infoblox.py | 21 ++--- .../module_utils/net_tools/nios/api.py | 77 ++++++++++++++----- .../modules/net_tools/nios/nios_dns_view.py | 6 +- .../net_tools/nios/nios_host_record.py | 6 +- .../modules/net_tools/nios/nios_network.py | 6 +- .../net_tools/nios/nios_network_view.py | 6 +- .../modules/net_tools/nios/nios_zone.py | 6 +- lib/ansible/plugins/lookup/nios.py | 7 +- lib/ansible/plugins/lookup/nios_next_ip.py | 9 +-- .../module_utils/net_tools/nios/test_api.py | 12 +-- 10 files changed, 88 insertions(+), 68 deletions(-) diff --git a/contrib/inventory/infoblox.py b/contrib/inventory/infoblox.py index 374a7564d44..736333faf1c 100755 --- a/contrib/inventory/infoblox.py +++ b/contrib/inventory/infoblox.py @@ -25,18 +25,9 @@ import argparse from ansible.parsing.dataloader import DataLoader from ansible.module_utils.six import iteritems from ansible.module_utils._text import to_text -from ansible.module_utils.net_tools.nios.api import get_connector +from ansible.module_utils.net_tools.nios.api import WapiInventory from ansible.module_utils.net_tools.nios.api import normalize_extattrs, flatten_extattrs -try: - # disable urllib3 warnings so as to not interfere with printing to stdout - # which is read by ansible - import urllib3 - urllib3.disable_warnings() -except ImportError: - sys.stdout.write('missing required library: urllib3\n') - sys.exit(-1) - CONFIG_FILES = [ '/etc/ansible/infoblox.yaml', @@ -70,7 +61,7 @@ def main(): loader = DataLoader() config = loader.load_from_file(config_file) provider = config.get('provider') or {} - connector = get_connector(**provider) + wapi = WapiInventory(provider) except Exception as exc: sys.stdout.write(to_text(exc)) sys.exit(-1) @@ -99,10 +90,10 @@ def main(): return_fields = ['name', 'view', 'extattrs', 'ipv4addrs'] - hosts = connector.get_object('record:host', - host_filter, - extattrs=extattrs, - return_fields=return_fields) + hosts = wapi.get_object('record:host', + host_filter, + extattrs=extattrs, + return_fields=return_fields) if hosts: for item in hosts: diff --git a/lib/ansible/module_utils/net_tools/nios/api.py b/lib/ansible/module_utils/net_tools/nios/api.py index 8ecc3a450e7..83be2b8794d 100644 --- a/lib/ansible/module_utils/net_tools/nios/api.py +++ b/lib/ansible/module_utils/net_tools/nios/api.py @@ -45,6 +45,7 @@ nios_provider_spec = { 'username': dict(), 'password': dict(no_log=True), 'ssl_verify': dict(type='bool', default=False), + 'silent_ssl_warnings': dict(type='bool', default=True), 'http_request_timeout': dict(type='int', default=10), 'http_pool_connections': dict(type='int', default=10), 'http_pool_maxsize': dict(type='int', default=10), @@ -53,11 +54,14 @@ nios_provider_spec = { } -def get_provider_spec(): - return {'provider': dict(type='dict', options=nios_provider_spec)} +def get_connector(*args, **kwargs): + ''' Returns an instance of infoblox_client.connector.Connector + :params args: positional arguments are silently ignored + :params kwargs: dict that is passed to Connector init -def get_connector(*args, **kwargs): + :returns: Connector + ''' if not HAS_INFOBLOX_CLIENT: raise Exception('infoblox-client is required but does not appear ' 'to be installed. It can be installed using the ' @@ -66,8 +70,15 @@ def get_connector(*args, **kwargs): if not set(kwargs.keys()).issubset(nios_provider_spec.keys()): raise Exception('invalid or unsupported keyword argument for connector') - for key in nios_provider_spec.keys(): + for key, value in iteritems(nios_provider_spec): if key not in kwargs: + # apply default values from nios_provider_spec since we cannot just + # assume the provider values are coming from AnsibleModule + if 'default' in value: + kwargs[key] = value['default'] + + # override any values with env variables unless they were + # explicitly set env = ('INFOBLOX_%s' % key).upper() if env in os.environ: kwargs[key] = os.environ.get(env) @@ -115,14 +126,10 @@ def flatten_extattrs(value): class WapiBase(object): ''' Base class for implementing Infoblox WAPI API ''' - def __init__(self, module): - self.module = module + provider_spec = {'provider': dict(type='dict', options=nios_provider_spec)} - try: - provider = module.params['provider'] or {} - self.connector = get_connector(**provider) - except Exception as exc: - module.fail_json(msg=to_text(exc)) + def __init__(self, provider): + self.connector = get_connector(**provider) def __getattr__(self, name): try: @@ -137,20 +144,50 @@ class WapiBase(object): method = getattr(self.connector, name) return method(*args, **kwargs) except InfobloxException as exc: - self.module.fail_json( - msg=exc.response['text'], - type=exc.response['Error'].split(':')[0], - code=exc.response.get('code'), - action=name - ) + if hasattr(self, 'handle_exception'): + self.handle_exception(name, exc) + else: + raise + + +class WapiLookup(WapiBase): + ''' Implements WapiBase for lookup plugins ''' + pass - def run(self, ib_obj_type, ib_spec): - raise NotImplementedError +class WapiInventory(WapiBase): + ''' Implements WapiBase for dynamic inventory script ''' + pass -class Wapi(WapiBase): + +class WapiModule(WapiBase): ''' Implements WapiBase for executing a NIOS module ''' + def __init__(self, module): + self.module = module + provider = module.params['provider'] + + try: + super(WapiModule, self).__init__(provider) + except Exception as exc: + self.module.fail_json(msg=to_text(exc)) + + def handle_exception(self, method_name, exc): + ''' Handles any exceptions raised + + This method will be called if an InfobloxException is raised for + any call to the instance of Connector. This method will then + gracefully fail the module. + + :args exc: instance of InfobloxException + ''' + self.module.fail_json( + msg=exc.response['text'], + type=exc.response['Error'].split(':')[0], + code=exc.response.get('code'), + operation=method_name + ) + def run(self, ib_obj_type, ib_spec): ''' Runs the module and performans configuration tasks diff --git a/lib/ansible/modules/net_tools/nios/nios_dns_view.py b/lib/ansible/modules/net_tools/nios/nios_dns_view.py index 154d43e2961..edd895d3b83 100644 --- a/lib/ansible/modules/net_tools/nios/nios_dns_view.py +++ b/lib/ansible/modules/net_tools/nios/nios_dns_view.py @@ -96,7 +96,7 @@ EXAMPLES = ''' RETURN = ''' # ''' from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi +from ansible.module_utils.net_tools.nios.api import WapiModule def main(): @@ -116,12 +116,12 @@ def main(): ) argument_spec.update(ib_spec) - argument_spec.update(get_provider_spec()) + argument_spec.update(WapiModule.provider_spec) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - wapi = Wapi(module) + wapi = WapiModule(module) result = wapi.run('view', ib_spec) module.exit_json(**result) diff --git a/lib/ansible/modules/net_tools/nios/nios_host_record.py b/lib/ansible/modules/net_tools/nios/nios_host_record.py index 00e741cf148..d2267107209 100644 --- a/lib/ansible/modules/net_tools/nios/nios_host_record.py +++ b/lib/ansible/modules/net_tools/nios/nios_host_record.py @@ -141,7 +141,7 @@ RETURN = ''' # ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six import iteritems -from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi +from ansible.module_utils.net_tools.nios.api import WapiModule def ipaddr(module, key, filtered_keys=None): @@ -204,12 +204,12 @@ def main(): ) argument_spec.update(ib_spec) - argument_spec.update(get_provider_spec()) + argument_spec.update(WapiModule.provider_spec) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - wapi = Wapi(module) + wapi = WapiModule(module) result = wapi.run('record:host', ib_spec) module.exit_json(**result) diff --git a/lib/ansible/modules/net_tools/nios/nios_network.py b/lib/ansible/modules/net_tools/nios/nios_network.py index a7e9f48f4d8..b3e36d109f4 100644 --- a/lib/ansible/modules/net_tools/nios/nios_network.py +++ b/lib/ansible/modules/net_tools/nios/nios_network.py @@ -138,7 +138,7 @@ RETURN = ''' # ''' from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.six import iteritems -from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi +from ansible.module_utils.net_tools.nios.api import WapiModule def options(module): @@ -200,12 +200,12 @@ def main(): ) argument_spec.update(ib_spec) - argument_spec.update(get_provider_spec()) + argument_spec.update(WapiModule.provider_spec) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - wapi = Wapi(module) + wapi = WapiModule(module) result = wapi.run('network', ib_spec) module.exit_json(**result) diff --git a/lib/ansible/modules/net_tools/nios/nios_network_view.py b/lib/ansible/modules/net_tools/nios/nios_network_view.py index 3efcf5e4667..fa7a9f87d3f 100644 --- a/lib/ansible/modules/net_tools/nios/nios_network_view.py +++ b/lib/ansible/modules/net_tools/nios/nios_network_view.py @@ -91,7 +91,7 @@ EXAMPLES = ''' RETURN = ''' # ''' from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi +from ansible.module_utils.net_tools.nios.api import WapiModule def main(): @@ -110,12 +110,12 @@ def main(): ) argument_spec.update(ib_spec) - argument_spec.update(get_provider_spec()) + argument_spec.update(WapiModule.provider_spec) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - wapi = Wapi(module) + wapi = WapiModule(module) result = wapi.run('networkview', ib_spec) module.exit_json(**result) diff --git a/lib/ansible/modules/net_tools/nios/nios_zone.py b/lib/ansible/modules/net_tools/nios/nios_zone.py index 14e7dbfb78d..3fd6fbf18b7 100644 --- a/lib/ansible/modules/net_tools/nios/nios_zone.py +++ b/lib/ansible/modules/net_tools/nios/nios_zone.py @@ -128,7 +128,7 @@ EXAMPLES = ''' RETURN = ''' # ''' from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi +from ansible.module_utils.net_tools.nios.api import WapiModule def main(): @@ -157,12 +157,12 @@ def main(): ) argument_spec.update(ib_spec) - argument_spec.update(get_provider_spec()) + argument_spec.update(WapiModule.provider_spec) module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) - wapi = Wapi(module) + wapi = WapiModule(module) result = wapi.run('zone_auth', ib_spec) module.exit_json(**result) diff --git a/lib/ansible/plugins/lookup/nios.py b/lib/ansible/plugins/lookup/nios.py index e545f25fb8d..761ff8c4080 100644 --- a/lib/ansible/plugins/lookup/nios.py +++ b/lib/ansible/plugins/lookup/nios.py @@ -95,8 +95,7 @@ obj_type: """ from ansible.plugins.lookup import LookupBase -from ansible.module_utils.net_tools.nios.api import nios_provider_spec -from ansible.module_utils.net_tools.nios.api import get_connector +from ansible.module_utils.net_tools.nios.api import WapiLookup from ansible.module_utils.net_tools.nios.api import normalize_extattrs, flatten_extattrs from ansible.errors import AnsibleError @@ -113,8 +112,8 @@ class LookupModule(LookupBase): filter_data = kwargs.pop('filter', {}) extattrs = normalize_extattrs(kwargs.pop('extattrs', {})) provider = kwargs.pop('provider', {}) - connector = get_connector(**provider) - res = connector.get_object(obj_type, filter_data, return_fields=return_fields) + wapi = WapiLookup(provider) + res = wapi.get_object(obj_type, filter_data, return_fields=return_fields) for obj in res: if 'extattrs' in obj: obj['extattrs'] = flatten_extattrs(obj['extattrs']) diff --git a/lib/ansible/plugins/lookup/nios_next_ip.py b/lib/ansible/plugins/lookup/nios_next_ip.py index adfe1e7a2c4..026003ce1ee 100644 --- a/lib/ansible/plugins/lookup/nios_next_ip.py +++ b/lib/ansible/plugins/lookup/nios_next_ip.py @@ -60,8 +60,7 @@ _list: """ from ansible.plugins.lookup import LookupBase -from ansible.module_utils.net_tools.nios.api import nios_provider_spec -from ansible.module_utils.net_tools.nios.api import get_connector +from ansible.module_utils.net_tools.nios.api import WapiLookup from ansible.module_utils._text import to_text from ansible.errors import AnsibleError @@ -75,9 +74,9 @@ class LookupModule(LookupBase): raise AnsibleError('missing argument in the form of A.B.C.D/E') provider = kwargs.pop('provider', {}) - connector = get_connector(**provider) + wapi = WapiLookup(provider) - network_obj = connector.get_object('network', {'network': network}) + network_obj = wapi.get_object('network', {'network': network}) if network_obj is None: raise AnsibleError('unable to find network object %s' % network) @@ -85,7 +84,7 @@ class LookupModule(LookupBase): try: ref = network_obj[0]['_ref'] - avail_ips = connector.call_func('next_available_ip', ref, {'num': num}) + avail_ips = wapi.call_func('next_available_ip', ref, {'num': num}) return [avail_ips['ips']] except Exception as exc: raise AnsibleError(to_text(exc)) diff --git a/test/units/module_utils/net_tools/nios/test_api.py b/test/units/module_utils/net_tools/nios/test_api.py index 7e65046a2d6..a93d4b3ac87 100644 --- a/test/units/module_utils/net_tools/nios/test_api.py +++ b/test/units/module_utils/net_tools/nios/test_api.py @@ -30,24 +30,18 @@ class TestNiosApi(unittest.TestCase): self.mock_connector.stop() def test_get_provider_spec(self): - provider_options = ['host', 'username', 'password', 'ssl_verify', + provider_options = ['host', 'username', 'password', 'ssl_verify', 'silent_ssl_warnings', 'http_request_timeout', 'http_pool_connections', 'http_pool_maxsize', 'max_retries', 'wapi_version'] - res = api.get_provider_spec() + res = api.WapiBase.provider_spec self.assertIsNotNone(res) self.assertIn('provider', res) self.assertIn('options', res['provider']) returned_options = res['provider']['options'] self.assertEqual(sorted(provider_options), sorted(returned_options.keys())) - def test_wapi_base(self): - wapi = api.WapiBase(self.module) - - with self.assertRaises(NotImplementedError): - wapi.run(None, None) - def _get_wapi(self, test_object): - wapi = api.Wapi(self.module) + wapi = api.WapiModule(self.module) wapi.get_object = Mock(name='get_object', return_value=test_object) wapi.create_object = Mock(name='create_object') wapi.update_object = Mock(name='update_object')