Remove f5-sdk from bigip_device_dns (#48461)

This commit is contained in:
Tim Rupp 2018-11-09 22:13:01 -08:00 committed by GitHub
commit 03ccf50466
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 320 additions and 127 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2017 F5 Networks Inc. # Copyright: (c) 2017, F5 Networks Inc.
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
@ -55,6 +55,7 @@ options:
extends_documentation_fragment: f5 extends_documentation_fragment: f5
author: author:
- Tim Rupp (@caphrim007) - Tim Rupp (@caphrim007)
- Wojciech Wypior (@wojtek0806)
''' '''
EXAMPLES = r''' EXAMPLES = r'''
@ -66,10 +67,10 @@ EXAMPLES = r'''
search: search:
- localdomain - localdomain
- lab.local - lab.local
provider:
password: secret password: secret
server: lb.mydomain.com server: lb.mydomain.com
user: admin user: admin
validate_certs: no
delegate_to: localhost delegate_to: localhost
''' '''
@ -104,153 +105,278 @@ warnings:
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.module_utils.network.f5.bigip import HAS_F5SDK from library.module_utils.network.f5.bigip import F5RestClient
from library.module_utils.network.f5.bigip import F5Client
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import AnsibleF5Parameters from library.module_utils.network.f5.common import AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens from library.module_utils.network.f5.common import cleanup_tokens
from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import f5_argument_spec from library.module_utils.network.f5.common import f5_argument_spec
try: from library.module_utils.network.f5.common import exit_json
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import fail_json
except ImportError: from library.module_utils.network.f5.common import is_empty_list
HAS_F5SDK = False
except ImportError: except ImportError:
from ansible.module_utils.network.f5.bigip import HAS_F5SDK from ansible.module_utils.network.f5.bigip import F5RestClient
from ansible.module_utils.network.f5.bigip import F5Client
from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import F5ModuleError
from ansible.module_utils.network.f5.common import AnsibleF5Parameters from ansible.module_utils.network.f5.common import AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens from ansible.module_utils.network.f5.common import cleanup_tokens
from ansible.module_utils.network.f5.common import fq_name
from ansible.module_utils.network.f5.common import f5_argument_spec from ansible.module_utils.network.f5.common import f5_argument_spec
try: from ansible.module_utils.network.f5.common import exit_json
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import fail_json
except ImportError: from ansible.module_utils.network.f5.common import is_empty_list
HAS_F5SDK = False
class Parameters(AnsibleF5Parameters): class Parameters(AnsibleF5Parameters):
api_map = { api_map = {
'dhclient.mgmt': 'dhcp',
'dns.cache': 'cache', 'dns.cache': 'cache',
'nameServers': 'name_servers', 'nameServers': 'name_servers',
'include': 'ip_version' 'include': 'ip_version',
} }
api_attributes = [ api_attributes = [
'nameServers', 'search', 'include' 'nameServers', 'search', 'include',
] ]
updatables = [ updatables = [
'cache', 'name_servers', 'search', 'ip_version' 'cache', 'name_servers', 'search', 'ip_version',
] ]
returnables = [ returnables = [
'cache', 'name_servers', 'search', 'ip_version' 'cache', 'name_servers', 'search', 'ip_version',
] ]
absentables = [ absentables = [
'name_servers', 'search' 'name_servers', 'search',
] ]
def to_return(self):
result = {}
for returnable in self.returnables:
result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
return result
class ApiParameters(Parameters):
pass
class ModuleParameters(Parameters):
@property @property
def search(self): def search(self):
result = [] search = self._values['search']
if self._values['search'] is None: if search is None:
return None return None
for server in self._values['search']: if isinstance(search, str) and search != "":
result.append(str(server)) result = list()
result.append(str(search))
return result return result
if is_empty_list(search):
return []
return search
@property @property
def name_servers(self): def name_servers(self):
result = [] name_servers = self._values['name_servers']
if self._values['name_servers'] is None: if name_servers is None:
return None return None
for server in self._values['name_servers']: if isinstance(name_servers, str) and name_servers != "":
result.append(str(server)) result = list()
result.append(str(name_servers))
return result return result
if is_empty_list(name_servers):
return []
return name_servers
@property @property
def cache(self): def cache(self):
if self._values['cache'] is None:
return None
if str(self._values['cache']) in ['enabled', 'enable']: if str(self._values['cache']) in ['enabled', 'enable']:
return 'enable' return 'enable'
else: else:
return 'disable' return 'disable'
@property
def dhcp(self):
valid = ['enable', 'enabled']
return True if self._values['dhcp'] in valid else False
@property @property
def ip_version(self): def ip_version(self):
if self._values['ip_version'] in [6, '6', 'options inet6']: if self._values['ip_version'] == 6:
return "options inet6" return "options inet6"
elif self._values['ip_version'] in [4, '4', '']: elif self._values['ip_version'] == 4:
return "" return ""
else: else:
return None return None
class Changes(Parameters):
def to_return(self):
result = {}
try:
for returnable in self.returnables:
change = getattr(self, returnable)
if isinstance(change, dict):
result.update(change)
else:
result[returnable] = change
result = self._filter_params(result)
except Exception:
pass
return result
class UsableChanges(Changes):
pass
class ReportableChanges(Changes):
@property
def ip_version(self):
if self._values['ip_version'] == 'options inet6':
return 6
elif self._values['ip_version'] == "":
return 4
else:
return None
class Difference(object):
def __init__(self, want, have=None):
self.want = want
self.have = have
def compare(self, param):
try:
result = getattr(self, param)
return result
except AttributeError:
return self.__default(param)
def __default(self, param):
attr1 = getattr(self.want, param)
try:
attr2 = getattr(self.have, param)
if attr1 != attr2:
return attr1
except AttributeError:
return attr1
@property
def ip_version(self):
if self.want.ip_version is None:
return None
if self.want.ip_version == "" and self.have.ip_version is None:
return None
if self.want.ip_version == self.have.ip_version:
return None
if self.want.ip_version != self.have.ip_version:
return self.want.ip_version
@property
def name_servers(self):
state = self.want.state
if self.want.name_servers is None:
return None
if state == 'absent':
if self.have.name_servers is None and self.want.name_servers:
return None
if set(self.want.name_servers) == set(self.have.name_servers):
return []
if set(self.want.name_servers) != set(self.have.name_servers):
return list(set(self.want.name_servers).difference(self.have.name_servers))
if not self.want.name_servers:
if self.have.name_servers is None:
return None
if self.have.name_servers is not None:
return self.want.name_servers
if self.have.name_servers is None:
return self.want.name_servers
if set(self.want.name_servers) != set(self.have.name_servers):
return self.want.name_servers
@property
def search(self):
state = self.want.state
if self.want.search is None:
return None
if not self.want.search:
if self.have.search is None:
return None
if self.have.search is not None:
return self.want.search
if state == 'absent':
if self.have.search is None and self.want.search:
return None
if set(self.want.search) == set(self.have.search):
return []
if set(self.want.search) != set(self.have.search):
return list(set(self.want.search).difference(self.have.search))
if self.have.search is None:
return self.want.search
if set(self.want.search) != set(self.have.search):
return self.want.search
class ModuleManager(object): class ModuleManager(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None) self.module = kwargs.pop('module', None)
self.client = kwargs.get('client', None) self.client = kwargs.pop('client', None)
self.want = Parameters(params=self.module.params) self.want = ModuleParameters(params=self.module.params)
self.have = None self.have = ApiParameters()
self.changes = Parameters() self.changes = UsableChanges()
def _update_changed_options(self): # lgtm [py/similar-function] def _announce_deprecations(self, result):
changed = {} warnings = result.pop('__warnings', [])
for key in Parameters.updatables: for warning in warnings:
if getattr(self.want, key) is not None: self.module.deprecate(
attr1 = getattr(self.want, key) msg=warning['msg'],
attr2 = getattr(self.have, key) version=warning['version']
if attr1 != attr2: )
changed[key] = attr1
def _update_changed_options(self):
diff = Difference(self.want, self.have)
updatables = Parameters.updatables
changed = dict()
for k in updatables:
change = diff.compare(k)
if change is None:
continue
else:
if isinstance(change, dict):
changed.update(change)
else:
changed[k] = change
if changed: if changed:
self.changes = Parameters(params=changed) self.changes = UsableChanges(params=changed)
return True return True
return False return False
def exec_module(self): # lgtm [py/similar-function] def _absent_changed_options(self):
diff = Difference(self.want, self.have)
absentables = Parameters.absentables
changed = dict()
for k in absentables:
change = diff.compare(k)
if change is None:
continue
else:
if isinstance(change, dict):
changed.update(change)
else:
changed[k] = change
if changed:
self.changes = UsableChanges(params=changed)
return True
return False
def exec_module(self):
changed = False changed = False
result = dict() result = dict()
state = self.want.state state = self.want.state
try:
if state == "present": if state == "present":
changed = self.update() changed = self.update()
elif state == "absent": elif state == "absent":
changed = self.absent() changed = self.absent()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
changes = self.changes.to_return() reportable = ReportableChanges(params=self.changes.to_return())
changes = reportable.to_return()
result.update(**changes) result.update(**changes)
result.update(dict(changed=changed)) result.update(dict(changed=changed))
self._announce_deprecations(result)
return result return result
def read_current_from_device(self):
want_keys = ['dns.cache']
result = dict()
dbs = self.client.api.tm.sys.dbs.get_collection()
for db in dbs:
if db.name in want_keys:
result[db.name] = db.value
dns = self.client.api.tm.sys.dns.load()
attrs = dns.attrs
if 'include' not in attrs:
attrs['include'] = 4
result.update(attrs)
return Parameters(params=result)
def update(self): def update(self):
self.have = self.read_current_from_device() self.have = self.read_current_from_device()
if not self.should_update(): if not self.should_update():
@ -266,32 +392,6 @@ class ModuleManager(object):
return True return True
return False return False
def update_on_device(self):
params = self.want.api_params()
cache = self.client.api.tm.sys.dbs.db.load(name='dns.cache')
dns = self.client.api.tm.sys.dns.load()
# Empty values can be supplied, but you cannot supply the
# None value, so we check for that specifically
if self.want.cache is not None:
cache.update(value=self.want.cache)
if params:
dns.update(**params)
def _absent_changed_options(self):
changed = {}
for key in Parameters.absentables:
if getattr(self.want, key) is not None:
set_want = set(getattr(self.want, key))
set_have = set(getattr(self.have, key))
set_new = set_have - set_want
if set_new != set_have:
changed[key] = list(set_new)
if changed:
self.changes = Parameters(params=changed)
return True
return False
def should_absent(self): def should_absent(self):
result = self._absent_changed_options() result = self._absent_changed_options()
if result: if result:
@ -307,10 +407,100 @@ class ModuleManager(object):
self.absent_on_device() self.absent_on_device()
return True return True
def read_dns_cache_setting(self):
uri = "https://{0}:{1}/mgmt/tm/sys/db/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
'dns.cache'
)
resp = self.client.api.get(uri)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
return response
def read_current_from_device(self):
cache = self.read_dns_cache_setting()
uri = "https://{0}:{1}/mgmt/tm/sys/dns/".format(
self.client.provider['server'],
self.client.provider['server_port'],
)
resp = self.client.api.get(uri)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
if cache:
response['cache'] = cache['value']
return ApiParameters(params=response)
def update_on_device(self):
params = self.changes.api_params()
if params:
uri = "https://{0}:{1}/mgmt/tm/sys/dns/".format(
self.client.provider['server'],
self.client.provider['server_port'],
)
resp = self.client.api.patch(uri, json=params)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
if self.want.cache:
uri = "https://{0}:{1}/mgmt/tm/sys/db/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
'dns.cache'
)
payload = {"value": self.want.cache}
resp = self.client.api.patch(uri, json=payload)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
def absent_on_device(self): def absent_on_device(self):
params = self.changes.api_params() params = self.changes.api_params()
resource = self.client.api.tm.sys.dns.load() uri = "https://{0}:{1}/mgmt/tm/sys/dns/".format(
resource.update(**params) self.client.provider['server'],
self.client.provider['server_port'],
)
resp = self.client.api.patch(uri, json=params)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
class ArgumentSpec(object): class ArgumentSpec(object):
@ -351,18 +541,17 @@ def main():
supports_check_mode=spec.supports_check_mode, supports_check_mode=spec.supports_check_mode,
required_one_of=spec.required_one_of required_one_of=spec.required_one_of
) )
if not HAS_F5SDK:
module.fail_json(msg="The python f5-sdk module is required") client = F5RestClient(**module.params)
try: try:
client = F5Client(**module.params)
mm = ModuleManager(module=module, client=client) mm = ModuleManager(module=module, client=client)
results = mm.exec_module() results = mm.exec_module()
cleanup_tokens(client) cleanup_tokens(client)
module.exit_json(**results) exit_json(module, results, client)
except F5ModuleError as ex: except F5ModuleError as ex:
cleanup_tokens(client) cleanup_tokens(client)
module.fail_json(msg=str(ex)) fail_json(module, ex, client)
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -8,32 +8,36 @@ __metaclass__ = type
import os import os
import json import json
import pytest
import sys import sys
from nose.plugins.skip import SkipTest from nose.plugins.skip import SkipTest
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
raise SkipTest("F5 Ansible modules require Python >= 2.7") raise SkipTest("F5 Ansible modules require Python >= 2.7")
from units.compat import unittest
from units.compat.mock import Mock
from units.compat.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.modules.bigip_device_dns import Parameters from library.modules.bigip_device_dns import Parameters
from library.modules.bigip_device_dns import ModuleManager from library.modules.bigip_device_dns import ModuleManager
from library.modules.bigip_device_dns import ArgumentSpec from library.modules.bigip_device_dns import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError # In Ansible 2.8, Ansible changed import paths.
from test.unit.modules.utils import set_module_args from test.units.compat import unittest
from test.units.compat.mock import Mock
from test.units.compat.mock import patch
from test.units.modules.utils import set_module_args
except ImportError: except ImportError:
try: try:
from ansible.modules.network.f5.bigip_device_dns import Parameters from ansible.modules.network.f5.bigip_device_dns import Parameters
from ansible.modules.network.f5.bigip_device_dns import ModuleManager from ansible.modules.network.f5.bigip_device_dns import ModuleManager
from ansible.modules.network.f5.bigip_device_dns import ArgumentSpec from ansible.modules.network.f5.bigip_device_dns import ArgumentSpec
from ansible.module_utils.network.f5.common import F5ModuleError
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError # Ansible 2.8 imports
from units.compat import unittest
from units.compat.mock import Mock
from units.compat.mock import patch
from units.modules.utils import set_module_args from units.modules.utils import set_module_args
except ImportError: except ImportError:
raise SkipTest("F5 Ansible modules require the f5-sdk Python library") raise SkipTest("F5 Ansible modules require the f5-sdk Python library")
@ -77,14 +81,14 @@ class TestParameters(unittest.TestCase):
assert p.search == ['14.14.14.14', '15.15.15.15'] assert p.search == ['14.14.14.14', '15.15.15.15']
# BIG-IP considers "ipv4" to be an empty value # BIG-IP considers "ipv4" to be an empty value
assert p.ip_version == '' assert p.ip_version == 4
def test_ipv6_parameter(self): def test_ipv6_parameter(self):
args = dict( args = dict(
ip_version=6 ip_version=6
) )
p = Parameters(params=args) p = Parameters(params=args)
assert p.ip_version == 'options inet6' assert p.ip_version == 6
class TestManager(unittest.TestCase): class TestManager(unittest.TestCase):