1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Refactored bigip_device_dns (#32483)

Module was using old coding standards. This updates the module
This commit is contained in:
Tim Rupp 2017-11-01 20:31:26 -07:00 committed by GitHub
parent cbc5c2d556
commit 60281b85fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 424 additions and 280 deletions

View file

@ -4,11 +4,15 @@
# 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
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1', ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'], 'status': ['preview'],
'supported_by': 'community'} 'supported_by': 'community'}
DOCUMENTATION = ''' DOCUMENTATION = r'''
--- ---
module: bigip_device_dns module: bigip_device_dns
short_description: Manage BIG-IP device DNS settings short_description: Manage BIG-IP device DNS settings
@ -22,17 +26,17 @@ options:
operation each time a lookup is needed. Please note that this applies operation each time a lookup is needed. Please note that this applies
only to Access Policy Manager features, such as ACLs, web application only to Access Policy Manager features, such as ACLs, web application
rewrites, and authentication. rewrites, and authentication.
required: false
default: disable default: disable
choices: choices:
- enable - enabled
- disable - disabled
name_servers: name_servers:
description: description:
- A list of name serverz that the system uses to validate DNS lookups - A list of name servers that the system uses to validate DNS lookups
forwarders: forwarders:
description: description:
- A list of BIND servers that the system can use to perform DNS lookups - A list of BIND servers that the system can use to perform DNS lookups
- Deprecated in 2.4. Use the GUI or edit named.conf.
search: search:
description: description:
- A list of domains that the system searches for local domain lookups, - A list of domains that the system searches for local domain lookups,
@ -40,7 +44,6 @@ options:
ip_version: ip_version:
description: description:
- Specifies whether the DNS specifies IP addresses using IPv4 or IPv6. - Specifies whether the DNS specifies IP addresses using IPv4 or IPv6.
required: false
choices: choices:
- 4 - 4
- 6 - 6
@ -48,14 +51,13 @@ options:
description: description:
- The state of the variable on the system. When C(present), guarantees - The state of the variable on the system. When C(present), guarantees
that an existing variable is set to C(value). that an existing variable is set to C(value).
required: false
default: present default: present
choices: choices:
- absent - absent
- present - present
notes: notes:
- Requires the f5-sdk Python package on the host. This is as easy as pip - Requires the f5-sdk Python package on the host. This is as easy as pip
install requests install f5-sdk.
extends_documentation_fragment: f5 extends_documentation_fragment: f5
requirements: requirements:
- f5-sdk - f5-sdk
@ -63,327 +65,326 @@ author:
- Tim Rupp (@caphrim007) - Tim Rupp (@caphrim007)
''' '''
EXAMPLES = ''' EXAMPLES = r'''
- name: Set the DNS settings on the BIG-IP - name: Set the DNS settings on the BIG-IP
bigip_device_dns: bigip_device_dns:
name_servers: name_servers:
- 208.67.222.222 - 208.67.222.222
- 208.67.220.220 - 208.67.220.220
search: search:
- localdomain - localdomain
- lab.local - lab.local
state: present password: secret
password: "secret" server: lb.mydomain.com
server: "lb.mydomain.com" user: admin
user: "admin" validate_certs: no
validate_certs: "no"
delegate_to: localhost delegate_to: localhost
''' '''
RETURN = ''' RETURN = r'''
cache: cache:
description: The new value of the DNS caching description: The new value of the DNS caching
returned: changed returned: changed
type: string type: string
sample: "enabled" sample: enabled
name_servers: name_servers:
description: List of name servers that were added or removed description: List of name servers that were set
returned: changed returned: changed
type: list type: list
sample: "['192.0.2.10', '172.17.12.10']" sample: ['192.0.2.10', '172.17.12.10']
forwarders:
description: List of forwarders that were added or removed
returned: changed
type: list
sample: "['192.0.2.10', '172.17.12.10']"
search: search:
description: List of search domains that were added or removed description: List of search domains that were set
returned: changed returned: changed
type: list type: list
sample: "['192.0.2.10', '172.17.12.10']" sample: ['192.0.2.10', '172.17.12.10']
ip_version: ip_version:
description: IP version that was set that DNS will specify IP addresses in description: IP version that was set that DNS will specify IP addresses in
returned: changed returned: changed
type: int type: int
sample: 4 sample: 4
warnings:
description: The list of warnings (if any) generated by module based on arguments
returned: always
type: list
sample: ['...', '...']
''' '''
from ansible.module_utils.f5_utils import AnsibleF5Client
from ansible.module_utils.f5_utils import AnsibleF5Parameters
from ansible.module_utils.f5_utils import HAS_F5SDK
from ansible.module_utils.f5_utils import F5ModuleError
try: try:
from f5.bigip.contexts import TransactionContextManager from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
from f5.bigip import ManagementRoot
HAS_F5SDK = True
except ImportError: except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
REQUIRED = ['name_servers', 'search', 'forwarders', 'ip_version', 'cache'] class Parameters(AnsibleF5Parameters):
CACHE = ['disable', 'enable'] api_map = {
IP = [4, 6] 'dhclient.mgmt': 'dhcp',
'dns.cache': 'cache',
'nameServers': 'name_servers',
'include': 'ip_version'
}
api_attributes = [
'nameServers', 'search', 'include'
]
class BigIpDeviceDns(object): updatables = [
def __init__(self, *args, **kwargs): 'cache', 'name_servers', 'search', 'ip_version'
if not HAS_F5SDK: ]
raise F5ModuleError("The python f5-sdk module is required")
# The params that change in the module returnables = [
self.cparams = dict() 'cache', 'name_servers', 'search', 'ip_version'
]
# Stores the params that are sent to the module absentables = [
self.params = kwargs 'name_servers', 'search'
self.api = ManagementRoot(kwargs['server'], ]
kwargs['user'],
kwargs['password'],
port=kwargs['server_port'])
def flush(self): def to_return(self):
result = dict() result = {}
changed = False for returnable in self.returnables:
state = self.params['state'] result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
return result
if self.dhcp_enabled(): def api_params(self):
result = {}
for api_attribute in self.api_attributes:
if self.api_map is not None and api_attribute in self.api_map:
result[api_attribute] = getattr(self, self.api_map[api_attribute])
else:
result[api_attribute] = getattr(self, api_attribute)
result = self._filter_params(result)
return result
@property
def search(self):
result = []
if self._values['search'] is None:
return None
for server in self._values['search']:
result.append(str(server))
return result
@property
def name_servers(self):
result = []
if self._values['name_servers'] is None:
return None
for server in self._values['name_servers']:
result.append(str(server))
return result
@property
def cache(self):
if str(self._values['cache']) in ['enabled', 'enable']:
return 'enable'
else:
return 'disable'
@property
def dhcp(self):
valid = ['enable', 'enabled']
return True if self._values['dhcp'] in valid else False
@property
def forwarders(self):
if self._values['forwarders'] is None:
return None
else:
raise F5ModuleError( raise F5ModuleError(
"DHCP on the mgmt interface must be disabled to make use of " + "The modifying of forwarders is not supported."
"this module"
) )
if state == 'absent': @property
changed = self.absent() def ip_version(self):
if self._values['ip_version'] in [6, '6', 'options inet6']:
return "options inet6"
elif self._values['ip_version'] in [4, '4', '']:
return ""
else: else:
changed = self.present() return None
result.update(**self.cparams)
class ModuleManager(object):
def __init__(self, client):
self.client = client
self.have = None
self.want = Parameters(self.client.module.params)
self.changes = Parameters()
def _update_changed_options(self):
changed = {}
for key in Parameters.updatables:
if getattr(self.want, key) is not None:
attr1 = getattr(self.want, key)
attr2 = getattr(self.have, key)
if attr1 != attr2:
changed[key] = attr1
if changed:
self.changes = Parameters(changed)
return True
return False
def exec_module(self):
changed = False
result = dict()
state = self.want.state
try:
if state == "present":
changed = self.update()
elif state == "absent":
changed = self.absent()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
changes = self.changes.to_return()
result.update(**changes)
result.update(dict(changed=changed)) result.update(dict(changed=changed))
return result return result
def dhcp_enabled(self): def read_current_from_device(self):
r = self.api.tm.sys.dbs.db.load(name='dhclient.mgmt') want_keys = ['dns.cache']
if r.value == 'enable':
return True
else:
return False
def read(self):
result = dict() 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(result)
cache = self.api.tm.sys.dbs.db.load(name='dns.cache') def update(self):
proxy = self.api.tm.sys.dbs.db.load(name='dns.proxy.__iter__') self.have = self.read_current_from_device()
dns = self.api.tm.sys.dns.load() if not self.should_update():
result['cache'] = str(cache.value)
result['forwarders'] = str(proxy.value).split(' ')
if hasattr(dns, 'nameServers'):
result['name_servers'] = dns.nameServers
if hasattr(dns, 'search'):
result['search'] = dns.search
if hasattr(dns, 'include') and 'options inet6' in dns.include:
result['ip_version'] = 6
else:
result['ip_version'] = 4
return result
def present(self):
params = dict()
current = self.read()
# Temporary locations to hold the changed params
update = dict(
dns=None,
forwarders=None,
cache=None
)
nameservers = self.params['name_servers']
search_domains = self.params['search']
ip_version = self.params['ip_version']
forwarders = self.params['forwarders']
cache = self.params['cache']
check_mode = self.params['check_mode']
if nameservers:
if 'name_servers' in current:
if nameservers != current['name_servers']:
params['nameServers'] = nameservers
else:
params['nameServers'] = nameservers
if search_domains:
if 'search' in current:
if search_domains != current['search']:
params['search'] = search_domains
else:
params['search'] = search_domains
if ip_version:
if 'ip_version' in current:
if ip_version != int(current['ip_version']):
if ip_version == 6:
params['include'] = 'options inet6'
elif ip_version == 4:
params['include'] = ''
else:
if ip_version == 6:
params['include'] = 'options inet6'
elif ip_version == 4:
params['include'] = ''
if params:
self.cparams.update(camel_dict_to_snake_dict(params))
if 'include' in params:
del self.cparams['include']
if params['include'] == '':
self.cparams['ip_version'] = 4
else:
self.cparams['ip_version'] = 6
update['dns'] = params.copy()
params = dict()
if forwarders:
if 'forwarders' in current:
if forwarders != current['forwarders']:
params['forwarders'] = forwarders
else:
params['forwarders'] = forwarders
if params:
self.cparams.update(camel_dict_to_snake_dict(params))
update['forwarders'] = ' '.join(params['forwarders'])
params = dict()
if cache:
if 'cache' in current:
if cache != current['cache']:
params['cache'] = cache
if params:
self.cparams.update(camel_dict_to_snake_dict(params))
update['cache'] = params['cache']
params = dict()
if self.cparams:
changed = True
if check_mode:
return changed
else:
return False return False
if self.client.check_mode:
return True
self.update_on_device()
return True
tx = self.api.tm.transactions.transaction def should_update(self):
with TransactionContextManager(tx) as api: result = self._update_changed_options()
cache = api.tm.sys.dbs.db.load(name='dns.cache') if result:
proxy = api.tm.sys.dbs.db.load(name='dns.proxy.__iter__') return True
dns = api.tm.sys.dns.load() return False
# Empty values can be supplied, but you cannot supply the def update_on_device(self):
# None value, so we check for that specifically params = self.want.api_params()
if update['cache'] is not None: cache = self.client.api.tm.sys.dbs.db.load(name='dns.cache')
cache.update(value=update['cache']) dns = self.client.api.tm.sys.dns.load()
if update['forwarders'] is not None:
proxy.update(value=update['forwarders']) # Empty values can be supplied, but you cannot supply the
if update['dns'] is not None: # None value, so we check for that specifically
dns.update(**update['dns']) if self.want.cache is not None:
return changed 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(changed)
return True
return False
def should_absent(self):
result = self._absent_changed_options()
if result:
return True
return False
def absent(self): def absent(self):
params = dict() self.have = self.read_current_from_device()
current = self.read() if not self.should_absent():
# Temporary locations to hold the changed params
update = dict(
dns=None,
forwarders=None
)
nameservers = self.params['name_servers']
search_domains = self.params['search']
forwarders = self.params['forwarders']
check_mode = self.params['check_mode']
if forwarders and 'forwarders' in current:
set_current = set(current['forwarders'])
set_new = set(forwarders)
forwarders = set_current - set_new
if forwarders != set_current:
forwarders = list(forwarders)
params['forwarders'] = ' '.join(forwarders)
if params:
changed = True
self.cparams.update(camel_dict_to_snake_dict(params))
update['forwarders'] = params['forwarders']
params = dict()
if nameservers and 'name_servers' in current:
set_current = set(current['name_servers'])
set_new = set(nameservers)
nameservers = set_current - set_new
if nameservers != set_current:
params['nameServers'] = list(nameservers)
if search_domains and 'search' in current:
set_current = set(current['search'])
set_new = set(search_domains)
search_domains = set_current - set_new
if search_domains != set_current:
params['search'] = list(search_domains)
if params:
changed = True
self.cparams.update(camel_dict_to_snake_dict(params))
update['dns'] = params.copy()
params = dict()
if not self.cparams:
return False return False
if self.client.check_mode:
return True
self.absent_on_device()
return True
if check_mode: def absent_on_device(self):
return changed params = self.changes.api_params()
resource = self.client.api.tm.sys.dns.load()
resource.update(**params)
tx = self.api.tm.transactions.transaction
with TransactionContextManager(tx) as api:
proxy = api.tm.sys.dbs.db.load(name='dns.proxy.__iter__')
dns = api.tm.sys.dns.load()
if update['forwarders'] is not None: class ArgumentSpec(object):
proxy.update(value=update['forwarders']) def __init__(self):
if update['dns'] is not None: self.supports_check_mode = True
dns.update(**update['dns']) self.argument_spec = dict(
return changed cache=dict(
required=False,
choices=['disabled', 'enabled', 'disable', 'enable'],
default=None
),
name_servers=dict(
required=False,
default=None,
type='list'
),
forwarders=dict(
required=False,
default=None,
type='list'
),
search=dict(
required=False,
default=None,
type='list'
),
ip_version=dict(
required=False,
default=None,
choices=[4, 6],
type='int'
),
state=dict(
required=False,
default='present',
choices=['absent', 'present']
)
)
self.required_one_of = [
['name_servers', 'search', 'forwarders', 'ip_version', 'cache']
]
self.f5_product_name = 'bigip'
def main(): def main():
argument_spec = f5_argument_spec() if not HAS_F5SDK:
raise F5ModuleError("The python f5-sdk module is required")
meta_args = dict( spec = ArgumentSpec()
cache=dict(required=False, choices=CACHE, default=None),
name_servers=dict(required=False, default=None, type='list'), client = AnsibleF5Client(
forwarders=dict(required=False, default=None, type='list'), argument_spec=spec.argument_spec,
search=dict(required=False, default=None, type='list'), supports_check_mode=spec.supports_check_mode,
ip_version=dict(required=False, default=None, choices=IP, type='int') f5_product_name=spec.f5_product_name,
) required_one_of=spec.required_one_of
argument_spec.update(meta_args)
module = AnsibleModule(
argument_spec=argument_spec,
required_one_of=[REQUIRED],
supports_check_mode=True
) )
try: try:
obj = BigIpDeviceDns(check_mode=module.check_mode, **module.params) mm = ModuleManager(client)
result = obj.flush() results = mm.exec_module()
client.module.exit_json(**results)
module.exit_json(**result)
except F5ModuleError as e: except F5ModuleError as e:
module.fail_json(msg=str(e)) client.module.fail_json(msg=str(e))
from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
from ansible.module_utils.f5_utils import *
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View file

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 F5 Networks Inc.
# 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)
__metaclass__ = type
import os
import json
import pytest
import sys
from nose.plugins.skip import SkipTest
if sys.version_info < (2, 7):
raise SkipTest("F5 Ansible modules require Python >= 2.7")
from ansible.compat.tests import unittest
from ansible.compat.tests.mock import patch, Mock
from ansible.module_utils import basic
from ansible.module_utils._text import to_bytes
from ansible.module_utils.f5_utils import AnsibleF5Client
from ansible.module_utils.f5_utils import F5ModuleError
try:
from library.bigip_device_dns import Parameters
from library.bigip_device_dns import ModuleManager
from library.bigip_device_dns import ArgumentSpec
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
except ImportError:
try:
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 ArgumentSpec
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
except ImportError:
raise SkipTest("F5 Ansible modules require the f5-sdk Python library")
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
fixture_data = {}
def set_module_args(args):
args = json.dumps({'ANSIBLE_MODULE_ARGS': args})
basic._ANSIBLE_ARGS = to_bytes(args)
def load_fixture(name):
path = os.path.join(fixture_path, name)
if path in fixture_data:
return fixture_data[path]
with open(path) as f:
data = f.read()
try:
data = json.loads(data)
except Exception:
pass
fixture_data[path] = data
return data
class TestParameters(unittest.TestCase):
def test_module_parameters(self):
args = dict(
cache='disable',
forwarders=['12.12.12.12', '13.13.13.13'],
ip_version=4,
name_servers=['10.10.10.10', '11.11.11.11'],
search=['14.14.14.14', '15.15.15.15'],
server='localhost',
user='admin',
password='password'
)
p = Parameters(args)
assert p.cache == 'disable'
assert p.name_servers == ['10.10.10.10', '11.11.11.11']
assert p.search == ['14.14.14.14', '15.15.15.15']
# BIG-IP considers "ipv4" to be an empty value
assert p.ip_version == ''
def test_ipv6_parameter(self):
args = dict(
ip_version=6
)
p = Parameters(args)
assert p.ip_version == 'options inet6'
def test_ensure_forwards_raises_exception(self):
args = dict(
forwarders=['12.12.12.12', '13.13.13.13'],
)
p = Parameters(args)
with pytest.raises(F5ModuleError) as ex:
foo = p.forwarders
assert 'The modifying of forwarders is not supported' in str(ex)
class TestManager(unittest.TestCase):
def setUp(self):
self.spec = ArgumentSpec()
@patch('ansible.module_utils.f5_utils.AnsibleF5Client._get_mgmt_root',
return_value=True)
def test_update_settings(self, *args):
set_module_args(dict(
cache='disable',
forwarders=['12.12.12.12', '13.13.13.13'],
ip_version=4,
name_servers=['10.10.10.10', '11.11.11.11'],
search=['14.14.14.14', '15.15.15.15'],
server='localhost',
user='admin',
password='password'
))
# Configure the parameters that would be returned by querying the
# remote device
current = Parameters(
dict(
cache='enable'
)
)
client = AnsibleF5Client(
argument_spec=self.spec.argument_spec,
supports_check_mode=self.spec.supports_check_mode,
f5_product_name=self.spec.f5_product_name
)
mm = ModuleManager(client)
# Override methods to force specific logic in the module to happen
mm.update_on_device = Mock(return_value=True)
mm.read_current_from_device = Mock(return_value=current)
results = mm.exec_module()
assert results['changed'] is True