mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-24 22:00:22 -07:00
Adds the bigip_data_group module (#39180)
This module can be used to manipulate bigip data groups.
This commit is contained in:
parent
c262dbfd30
commit
d38ae9b6c9
13 changed files with 2413 additions and 59 deletions
|
@ -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):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue