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
This commit is contained in:
Peter Sprygada 2018-01-27 08:03:06 -05:00 committed by GitHub
parent 1f1402ea68
commit c2d3b9cbd5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 90 additions and 70 deletions

View file

@ -25,18 +25,9 @@ import argparse
from ansible.parsing.dataloader import DataLoader from ansible.parsing.dataloader import DataLoader
from ansible.module_utils.six import iteritems from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_text 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 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 = [ CONFIG_FILES = [
'/etc/ansible/infoblox.yaml', '/etc/ansible/infoblox.yaml',
@ -70,7 +61,7 @@ def main():
loader = DataLoader() loader = DataLoader()
config = loader.load_from_file(config_file) config = loader.load_from_file(config_file)
provider = config.get('provider') or {} provider = config.get('provider') or {}
connector = get_connector(**provider) wapi = WapiInventory(provider)
except Exception as exc: except Exception as exc:
sys.stdout.write(to_text(exc)) sys.stdout.write(to_text(exc))
sys.exit(-1) sys.exit(-1)
@ -99,7 +90,7 @@ def main():
return_fields = ['name', 'view', 'extattrs', 'ipv4addrs'] return_fields = ['name', 'view', 'extattrs', 'ipv4addrs']
hosts = connector.get_object('record:host', hosts = wapi.get_object('record:host',
host_filter, host_filter,
extattrs=extattrs, extattrs=extattrs,
return_fields=return_fields) return_fields=return_fields)

View file

@ -45,6 +45,7 @@ nios_provider_spec = {
'username': dict(), 'username': dict(),
'password': dict(no_log=True), 'password': dict(no_log=True),
'ssl_verify': dict(type='bool', default=False), 'ssl_verify': dict(type='bool', default=False),
'silent_ssl_warnings': dict(type='bool', default=True),
'http_request_timeout': dict(type='int', default=10), 'http_request_timeout': dict(type='int', default=10),
'http_pool_connections': dict(type='int', default=10), 'http_pool_connections': dict(type='int', default=10),
'http_pool_maxsize': 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): 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
:returns: Connector
'''
if not HAS_INFOBLOX_CLIENT: if not HAS_INFOBLOX_CLIENT:
raise Exception('infoblox-client is required but does not appear ' raise Exception('infoblox-client is required but does not appear '
'to be installed. It can be installed using the ' '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()): if not set(kwargs.keys()).issubset(nios_provider_spec.keys()):
raise Exception('invalid or unsupported keyword argument for connector') 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: 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() env = ('INFOBLOX_%s' % key).upper()
if env in os.environ: if env in os.environ:
kwargs[key] = os.environ.get(env) kwargs[key] = os.environ.get(env)
@ -115,14 +126,10 @@ def flatten_extattrs(value):
class WapiBase(object): class WapiBase(object):
''' Base class for implementing Infoblox WAPI API ''' ''' Base class for implementing Infoblox WAPI API '''
def __init__(self, module): provider_spec = {'provider': dict(type='dict', options=nios_provider_spec)}
self.module = module
try: def __init__(self, provider):
provider = module.params['provider'] or {}
self.connector = get_connector(**provider) self.connector = get_connector(**provider)
except Exception as exc:
module.fail_json(msg=to_text(exc))
def __getattr__(self, name): def __getattr__(self, name):
try: try:
@ -137,20 +144,50 @@ class WapiBase(object):
method = getattr(self.connector, name) method = getattr(self.connector, name)
return method(*args, **kwargs) return method(*args, **kwargs)
except InfobloxException as exc: except InfobloxException as exc:
if hasattr(self, 'handle_exception'):
self.handle_exception(name, exc)
else:
raise
class WapiLookup(WapiBase):
''' Implements WapiBase for lookup plugins '''
pass
class WapiInventory(WapiBase):
''' Implements WapiBase for dynamic inventory script '''
pass
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( self.module.fail_json(
msg=exc.response['text'], msg=exc.response['text'],
type=exc.response['Error'].split(':')[0], type=exc.response['Error'].split(':')[0],
code=exc.response.get('code'), code=exc.response.get('code'),
action=name operation=method_name
) )
def run(self, ib_obj_type, ib_spec):
raise NotImplementedError
class Wapi(WapiBase):
''' Implements WapiBase for executing a NIOS module '''
def run(self, ib_obj_type, ib_spec): def run(self, ib_obj_type, ib_spec):
''' Runs the module and performans configuration tasks ''' Runs the module and performans configuration tasks

View file

@ -96,7 +96,7 @@ EXAMPLES = '''
RETURN = ''' # ''' RETURN = ''' # '''
from ansible.module_utils.basic import AnsibleModule 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(): def main():
@ -116,12 +116,12 @@ def main():
) )
argument_spec.update(ib_spec) argument_spec.update(ib_spec)
argument_spec.update(get_provider_spec()) argument_spec.update(WapiModule.provider_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
wapi = Wapi(module) wapi = WapiModule(module)
result = wapi.run('view', ib_spec) result = wapi.run('view', ib_spec)
module.exit_json(**result) module.exit_json(**result)

View file

@ -141,7 +141,7 @@ RETURN = ''' # '''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems 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): def ipaddr(module, key, filtered_keys=None):
@ -204,12 +204,12 @@ def main():
) )
argument_spec.update(ib_spec) argument_spec.update(ib_spec)
argument_spec.update(get_provider_spec()) argument_spec.update(WapiModule.provider_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
wapi = Wapi(module) wapi = WapiModule(module)
result = wapi.run('record:host', ib_spec) result = wapi.run('record:host', ib_spec)
module.exit_json(**result) module.exit_json(**result)

View file

@ -138,7 +138,7 @@ RETURN = ''' # '''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems 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): def options(module):
@ -200,12 +200,12 @@ def main():
) )
argument_spec.update(ib_spec) argument_spec.update(ib_spec)
argument_spec.update(get_provider_spec()) argument_spec.update(WapiModule.provider_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
wapi = Wapi(module) wapi = WapiModule(module)
result = wapi.run('network', ib_spec) result = wapi.run('network', ib_spec)
module.exit_json(**result) module.exit_json(**result)

View file

@ -91,7 +91,7 @@ EXAMPLES = '''
RETURN = ''' # ''' RETURN = ''' # '''
from ansible.module_utils.basic import AnsibleModule 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(): def main():
@ -110,12 +110,12 @@ def main():
) )
argument_spec.update(ib_spec) argument_spec.update(ib_spec)
argument_spec.update(get_provider_spec()) argument_spec.update(WapiModule.provider_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
wapi = Wapi(module) wapi = WapiModule(module)
result = wapi.run('networkview', ib_spec) result = wapi.run('networkview', ib_spec)
module.exit_json(**result) module.exit_json(**result)

View file

@ -128,7 +128,7 @@ EXAMPLES = '''
RETURN = ''' # ''' RETURN = ''' # '''
from ansible.module_utils.basic import AnsibleModule 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(): def main():
@ -157,12 +157,12 @@ def main():
) )
argument_spec.update(ib_spec) argument_spec.update(ib_spec)
argument_spec.update(get_provider_spec()) argument_spec.update(WapiModule.provider_spec)
module = AnsibleModule(argument_spec=argument_spec, module = AnsibleModule(argument_spec=argument_spec,
supports_check_mode=True) supports_check_mode=True)
wapi = Wapi(module) wapi = WapiModule(module)
result = wapi.run('zone_auth', ib_spec) result = wapi.run('zone_auth', ib_spec)
module.exit_json(**result) module.exit_json(**result)

View file

@ -95,8 +95,7 @@ obj_type:
""" """
from ansible.plugins.lookup import LookupBase 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 WapiLookup
from ansible.module_utils.net_tools.nios.api import get_connector
from ansible.module_utils.net_tools.nios.api import normalize_extattrs, flatten_extattrs from ansible.module_utils.net_tools.nios.api import normalize_extattrs, flatten_extattrs
from ansible.errors import AnsibleError from ansible.errors import AnsibleError
@ -113,8 +112,8 @@ class LookupModule(LookupBase):
filter_data = kwargs.pop('filter', {}) filter_data = kwargs.pop('filter', {})
extattrs = normalize_extattrs(kwargs.pop('extattrs', {})) extattrs = normalize_extattrs(kwargs.pop('extattrs', {}))
provider = kwargs.pop('provider', {}) provider = kwargs.pop('provider', {})
connector = get_connector(**provider) wapi = WapiLookup(provider)
res = connector.get_object(obj_type, filter_data, return_fields=return_fields) res = wapi.get_object(obj_type, filter_data, return_fields=return_fields)
for obj in res: for obj in res:
if 'extattrs' in obj: if 'extattrs' in obj:
obj['extattrs'] = flatten_extattrs(obj['extattrs']) obj['extattrs'] = flatten_extattrs(obj['extattrs'])

View file

@ -60,8 +60,7 @@ _list:
""" """
from ansible.plugins.lookup import LookupBase 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 WapiLookup
from ansible.module_utils.net_tools.nios.api import get_connector
from ansible.module_utils._text import to_text from ansible.module_utils._text import to_text
from ansible.errors import AnsibleError 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') raise AnsibleError('missing argument in the form of A.B.C.D/E')
provider = kwargs.pop('provider', {}) 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: if network_obj is None:
raise AnsibleError('unable to find network object %s' % network) raise AnsibleError('unable to find network object %s' % network)
@ -85,7 +84,7 @@ class LookupModule(LookupBase):
try: try:
ref = network_obj[0]['_ref'] 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']] return [avail_ips['ips']]
except Exception as exc: except Exception as exc:
raise AnsibleError(to_text(exc)) raise AnsibleError(to_text(exc))

View file

@ -30,24 +30,18 @@ class TestNiosApi(unittest.TestCase):
self.mock_connector.stop() self.mock_connector.stop()
def test_get_provider_spec(self): 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_request_timeout', 'http_pool_connections',
'http_pool_maxsize', 'max_retries', 'wapi_version'] 'http_pool_maxsize', 'max_retries', 'wapi_version']
res = api.get_provider_spec() res = api.WapiBase.provider_spec
self.assertIsNotNone(res) self.assertIsNotNone(res)
self.assertIn('provider', res) self.assertIn('provider', res)
self.assertIn('options', res['provider']) self.assertIn('options', res['provider'])
returned_options = res['provider']['options'] returned_options = res['provider']['options']
self.assertEqual(sorted(provider_options), sorted(returned_options.keys())) 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): 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.get_object = Mock(name='get_object', return_value=test_object)
wapi.create_object = Mock(name='create_object') wapi.create_object = Mock(name='create_object')
wapi.update_object = Mock(name='update_object') wapi.update_object = Mock(name='update_object')