Adds the bigip_data_group module (#39180)

This module can be used to manipulate bigip data groups.
This commit is contained in:
Tim Rupp 2018-04-23 11:52:57 -07:00 committed by GitHub
commit d38ae9b6c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 2413 additions and 59 deletions

View file

@ -6,6 +6,7 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import os
import re
from ansible.module_utils._text import to_text
@ -13,6 +14,7 @@ from ansible.module_utils.basic import env_fallback
from ansible.module_utils.connection import exec_command
from ansible.module_utils.network.common.utils import to_list, ComplexList
from ansible.module_utils.six import iteritems
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
from collections import defaultdict
try:
@ -28,7 +30,6 @@ f5_provider_spec = {
),
'server_port': dict(
type='int',
default=443,
fallback=(env_fallback, ['F5_SERVER_PORT'])
),
'user': dict(
@ -40,7 +41,6 @@ f5_provider_spec = {
fallback=(env_fallback, ['F5_PASSWORD', 'ANSIBLE_NET_PASSWORD'])
),
'ssh_keyfile': dict(
fallback=(env_fallback, ['ANSIBLE_NET_SSH_KEYFILE']),
type='path'
),
'validate_certs': dict(
@ -48,8 +48,8 @@ f5_provider_spec = {
fallback=(env_fallback, ['F5_VALIDATE_CERTS'])
),
'transport': dict(
default='rest',
choices=['cli', 'rest']
choices=['cli', 'rest'],
default='rest'
),
'timeout': dict(type='int'),
}
@ -81,12 +81,10 @@ f5_top_spec = {
'server_port': dict(
removed_in_version=2.9,
type='int',
default=443,
fallback=(env_fallback, ['F5_SERVER_PORT'])
),
'transport': dict(
removed_in_version=2.9,
default='rest',
choices=['cli', 'rest']
)
}
@ -107,8 +105,59 @@ def load_params(params):
# Fully Qualified name (with the partition)
def fqdn_name(partition, value):
if value is not None and not value.startswith('/'):
return '/{0}/{1}'.format(partition, value)
"""This method is not used
This was the original name of a method that was used throughout all
the F5 Ansible modules. This is now deprecated, and should be removed
in 2.9. All modules should be changed to use ``fq_name``.
TODO(Remove in Ansible 2.9)
"""
return fq_name(partition, value)
def fq_name(partition, value):
"""Returns a 'Fully Qualified' name
A BIG-IP expects most names of resources to be in a fully-qualified
form. This means that both the simple name, and the partition need
to be combined.
The Ansible modules, however, can accept (as names for several
resources) their name in the FQ format. This becomes an issue when
the FQ name and the partition are both specified as separate values.
Consider the following examples.
# Name not FQ
name: foo
partition: Common
# Name FQ
name: /Common/foo
partition: Common
This method will rectify the above situation and will, in both cases,
return the following for name.
/Common/foo
Args:
partition (string): The partition that you would want attached to
the name if the name has no partition.
value (string): The name that you want to attach a partition to.
This value will be returned unchanged if it has a partition
attached to it already.
Returns:
string: The fully qualified name, given the input parameters.
"""
if value is not None:
try:
int(value)
return '/{0}/{1}'.format(partition, value)
except (ValueError, TypeError):
if not value.startswith('/'):
return '/{0}/{1}'.format(partition, value)
return value
@ -137,7 +186,8 @@ def run_commands(module, commands, check_rc=True):
rc, out, err = exec_command(module, cmd)
if check_rc and rc != 0:
raise F5ModuleError(to_text(err, errors='surrogate_then_replace'))
responses.append(to_text(out, errors='surrogate_then_replace'))
result = to_text(out, errors='surrogate_then_replace')
responses.append(result)
return responses
@ -183,6 +233,101 @@ def is_valid_hostname(host):
return result
def is_valid_fqdn(host):
"""Reasonable attempt at validating a hostname
Compiled from various paragraphs outlined here
https://tools.ietf.org/html/rfc3696#section-2
https://tools.ietf.org/html/rfc1123
Notably,
* Host software MUST handle host names of up to 63 characters and
SHOULD handle host names of up to 255 characters.
* The "LDH rule", after the characters that it permits. (letters, digits, hyphen)
* If the hyphen is used, it is not permitted to appear at
either the beginning or end of a label
:param host:
:return:
"""
if len(host) > 255:
return False
host = host.rstrip(".")
allowed = re.compile(r'(?!-)[A-Z0-9-]{1,63}(?<!-)$', re.IGNORECASE)
result = all(allowed.match(x) for x in host.split("."))
if result:
parts = host.split('.')
if len(parts) > 1:
return True
return False
def dict2tuple(items):
"""Convert a dictionary to a list of tuples
This method is used in cases where dictionaries need to be compared. Due
to dictionaries inherently having no order, it is easier to compare list
of tuples because these lists can be converted to sets.
This conversion only supports dicts of simple values. Do not give it dicts
that contain sub-dicts. This will not give you the result you want when using
the returned tuple for comparison.
Args:
items (dict): The dictionary of items that should be converted
Returns:
list: Returns a list of tuples upon success. Otherwise, an empty list.
"""
result = []
for x in items:
tmp = [(str(k), str(v)) for k, v in iteritems(x)]
result += tmp
return result
def compare_dictionary(want, have):
"""Performs a dictionary comparison
Args:
want (dict): Dictionary to compare with second parameter.
have (dict): Dictionary to compare with first parameter.
Returns:
bool:
:param have:
:return:
"""
if want == [] and have is None:
return None
if want is None:
return None
w = dict2tuple(want)
h = dict2tuple(have)
if set(w) == set(h):
return None
else:
return want
def is_ansible_debug(module):
if module._debug and module._verbosity >= 4:
return True
return False
def fail_json(module, ex, client=None):
if is_ansible_debug(module) and client:
module.fail_json(msg=str(ex), __f5debug__=client.api.debug_output)
module.fail_json(msg=str(ex))
def exit_json(module, results, client=None):
if is_ansible_debug(module) and client:
results['__f5debug__'] = client.api.debug_output
module.exit_json(**results)
class Noop(object):
"""Represent no-operation required
@ -200,6 +345,7 @@ class Noop(object):
class F5BaseClient(object):
def __init__(self, *args, **kwargs):
self.params = kwargs
self.module = kwargs.get('module', None)
load_params(self.params)
self._client = None
@ -222,7 +368,73 @@ class F5BaseClient(object):
:return:
:raises iControlUnexpectedHTTPError
"""
self._client = self.mgmt
self._client = None
def merge_provider_params(self):
result = dict()
provider = self.params.get('provider', {})
if provider.get('server', None):
result['server'] = provider.get('server', None)
elif self.params.get('server', None):
result['server'] = self.params.get('server', None)
elif os.environ.get('F5_SERVER', None):
result['server'] = os.environ.get('F5_SERVER', None)
if provider.get('server_port', None):
result['server_port'] = provider.get('server_port', None)
elif self.params.get('server_port', None):
result['server_port'] = self.params.get('server_port', None)
elif os.environ.get('F5_SERVER_PORT', None):
result['server_port'] = os.environ.get('F5_SERVER_PORT', None)
else:
result['server_port'] = 443
if provider.get('validate_certs', None) is not None:
result['validate_certs'] = provider.get('validate_certs', None)
elif self.params.get('validate_certs', None) is not None:
result['validate_certs'] = self.params.get('validate_certs', None)
elif os.environ.get('F5_VALIDATE_CERTS', None) is not None:
result['validate_certs'] = os.environ.get('F5_VALIDATE_CERTS', None)
else:
result['validate_certs'] = True
if provider.get('auth_provider', None):
result['auth_provider'] = provider.get('auth_provider', None)
elif self.params.get('auth_provider', None):
result['auth_provider'] = self.params.get('auth_provider', None)
else:
result['auth_provider'] = 'tmos'
if provider.get('user', None):
result['user'] = provider.get('user', None)
elif self.params.get('user', None):
result['user'] = self.params.get('user', None)
elif os.environ.get('F5_USER', None):
result['user'] = os.environ.get('F5_USER', None)
elif os.environ.get('ANSIBLE_NET_USERNAME', None):
result['user'] = os.environ.get('ANSIBLE_NET_USERNAME', None)
else:
result['user'] = True
if provider.get('password', None):
result['password'] = provider.get('password', None)
elif self.params.get('user', None):
result['password'] = self.params.get('password', None)
elif os.environ.get('F5_PASSWORD', None):
result['password'] = os.environ.get('F5_PASSWORD', None)
elif os.environ.get('ANSIBLE_NET_PASSWORD', None):
result['password'] = os.environ.get('ANSIBLE_NET_PASSWORD', None)
else:
result['password'] = True
if result['validate_certs'] in BOOLEANS_TRUE:
result['validate_certs'] = True
else:
result['validate_certs'] = False
return result
class AnsibleF5Parameters(object):