Refactors bigip_iapp_service and adds params (#44653)

Module was refactored to look like other f5 modules. also added new
params.

* decription
* device_group
* metadata
This commit is contained in:
Tim Rupp 2018-08-24 14:38:13 -04:00 committed by GitHub
parent 2930b28247
commit b5d45bdd1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -19,9 +19,7 @@ short_description: Manages TCL iApp services on a BIG-IP
description:
- Manages TCL iApp services on a BIG-IP.
- If you are looking for the API that is communicated with on the BIG-IP,
the one the is used is C(/mgmt/tm/sys/application/service/). There are a
couple of APIs in a BIG-IP that might seem like they are relevant to iApp
Services, but the API mentioned here is the one that is used.
the one the is used is C(/mgmt/tm/sys/application/service/).
version_added: 2.4
options:
name:
@ -32,13 +30,13 @@ options:
description:
- The iApp template from which to instantiate a new service. This
template must exist on your BIG-IP before you can successfully
create a service. This parameter is required if the C(state)
parameter is C(present).
create a service.
- When creating a new service, this parameter is required.
parameters:
description:
- A hash of all the required template variables for the iApp template.
If your parameters are stored in a file (the more common scenario)
it is recommended you use either the `file` or `template` lookups
it is recommended you use either the C(file) or C(template) lookups
to supply the expected parameters.
- These parameters typically consist of the C(lists), C(tables), and
C(variables) fields.
@ -74,7 +72,7 @@ options:
iApp.
- When C(no), allows updates outside of the iApp.
- If this option is specified in the Ansible task, it will take precedence
over any similar setting in the iApp Server payload that you provide in
over any similar setting in the iApp Service payload that you provide in
the C(parameters) field.
default: yes
type: bool
@ -85,9 +83,30 @@ options:
this value is not specified, the default of C(/Common/traffic-group-1)
will be used.
- If this option is specified in the Ansible task, it will take precedence
over any similar setting in the iApp Server payload that you provide in
over any similar setting in the iApp Service payload that you provide in
the C(parameters) field.
version_added: 2.5
metadata:
description:
- Metadata associated with the iApp service.
- If this option is specified in the Ansible task, it will take precedence
over any similar setting in the iApp Service payload that you provide in
the C(parameters) field.
version_added: 2.7
description:
description:
- Description of the iApp service.
- If this option is specified in the Ansible task, it will take precedence
over any similar setting in the iApp Service payload that you provide in
the C(parameters) field.
version_added: 2.7
device_group:
description:
- The device group for the iApp service.
- If this option is specified in the Ansible task, it will take precedence
over any similar setting in the iApp Service payload that you provide in
the C(parameters) field.
version_added: 2.7
extends_documentation_fragment: f5
author:
- Tim Rupp (@caphrim007)
@ -210,6 +229,22 @@ EXAMPLES = r'''
- 0
- name: server_pools__servers
delegate_to: localhost
- name: Override metadata that may or may not exist in parameters
bigip_iapp_service:
name: foo-service
template: f5.http
parameters: "{{ lookup('file', 'f5.http.parameters.json') }}"
metadata:
- persist: yes
name: data 1
- persist: yes
name: data 2
password: secret
server: lb.mydomain.com
state: present
user: admin
delegate_to: localhost
'''
RETURN = r'''
@ -228,6 +263,7 @@ try:
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 flatten_boolean
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
@ -240,6 +276,7 @@ except ImportError:
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 flatten_boolean
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
except ImportError:
@ -250,17 +287,45 @@ class Parameters(AnsibleF5Parameters):
api_map = {
'strictUpdates': 'strict_updates',
'trafficGroup': 'traffic_group',
'deviceGroup': 'device_group',
}
returnables = []
api_attributes = [
'tables', 'variables', 'template', 'lists', 'deviceGroup',
'inheritedDevicegroup', 'inheritedTrafficGroup', 'trafficGroup',
'strictUpdates'
returnables = [
'tables',
'variables',
'lists',
'strict_updates',
'traffic_group',
'device_group',
'metadata',
'template',
'description',
]
updatables = ['tables', 'variables', 'lists', 'strict_updates', 'traffic_group']
api_attributes = [
'tables',
'variables',
'template',
'lists',
'deviceGroup',
'inheritedDevicegroup',
'inheritedTrafficGroup',
'trafficGroup',
'strictUpdates',
# 'metadata',
'description',
]
updatables = [
'tables',
'variables',
'lists',
'strict_updates',
'device_group',
'traffic_group',
'metadata',
'description',
]
def to_return(self):
result = {}
@ -269,12 +334,8 @@ class Parameters(AnsibleF5Parameters):
result = self._filter_params(result)
return result
@property
def tables(self):
def normalize_tables(self, tables):
result = []
if not self._values['tables']:
return None
tables = self._values['tables']
for table in tables:
tmp = dict()
name = table.get('name', None)
@ -296,16 +357,8 @@ class Parameters(AnsibleF5Parameters):
result = sorted(result, key=lambda k: k['name'])
return result
@tables.setter
def tables(self, value):
self._values['tables'] = value
@property
def variables(self):
def normalize_variables(self, variables):
result = []
if not self._values['variables']:
return None
variables = self._values['variables']
for variable in variables:
tmp = dict((str(k), str(v)) for k, v in iteritems(variable))
if 'encrypted' not in tmp:
@ -323,16 +376,8 @@ class Parameters(AnsibleF5Parameters):
result = sorted(result, key=lambda k: k['name'])
return result
@variables.setter
def variables(self, value):
self._values['variables'] = value
@property
def lists(self):
def normalize_list(self, lists):
result = []
if not self._values['lists']:
return None
lists = self._values['lists']
for list in lists:
tmp = dict((str(k), str(v)) for k, v in iteritems(list) if k != 'value')
if 'encrypted' not in list:
@ -349,39 +394,148 @@ class Parameters(AnsibleF5Parameters):
result = sorted(result, key=lambda k: k['name'])
return result
@lists.setter
def lists(self, value):
self._values['lists'] = value
@property
def parameters(self):
result = dict(
tables=self.tables,
variables=self.variables,
lists=self.lists
)
def normalize_metadata(self, metadata):
result = []
for item in metadata:
name = item.get('name', None)
persist = flatten_boolean(item.get('persist', "no"))
if persist == "yes":
persist = "true"
else:
persist = "false"
result.append({
"name": name,
"persist": persist
})
return result
@parameters.setter
def parameters(self, value):
if value is None:
return
if 'tables' in value:
self.tables = value['tables']
if 'variables' in value:
self.variables = value['variables']
if 'lists' in value:
self.lists = value['lists']
if 'deviceGroup' in value:
self.deviceGroup = value['deviceGroup']
if 'inheritedDevicegroup' in value:
self.inheritedDevicegroup = value['inheritedDevicegroup']
if 'inheritedTrafficGroup' in value:
self.inheritedTrafficGroup = value['inheritedTrafficGroup']
if 'trafficGroup' in value:
self.trafficGroup = value['trafficGroup']
if 'strictUpdates' in value:
self.strictUpdates = value['strictUpdates']
class ApiParameters(Parameters):
@property
def metadata(self):
if self._values['metadata'] is None:
return None
return self._values['metadata']
@property
def tables(self):
if self._values['tables'] is None:
return None
return self.normalize_tables(self._values['tables'])
@property
def lists(self):
if self._values['lists'] is None:
return None
return self.normalize_list(self._values['lists'])
@property
def variables(self):
if self._values['variables'] is None:
return None
return self.normalize_variables(self._values['variables'])
@property
def device_group(self):
if self._values['device_group'] in [None, 'none']:
return None
return self._values['device_group']
class ModuleParameters(Parameters):
@property
def param_lists(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('lists', None)
return result
@property
def param_tables(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('tables', None)
return result
@property
def param_variables(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('variables', None)
return result
@property
def param_metadata(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('metadata', None)
return result
@property
def param_description(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('description', None)
return result
@property
def param_traffic_group(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('trafficGroup', None)
if not result:
return result
return fq_name(self.partition, result)
@property
def param_device_group(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('deviceGroup', None)
if not result:
return result
return fq_name(self.partition, result)
@property
def param_strict_updates(self):
if self._values['parameters'] is None:
return None
result = self._values['parameters'].get('strictUpdates', None)
return flatten_boolean(result)
@property
def tables(self):
if self._values['tables']:
return self.normalize_tables(self._values['tables'])
elif self.param_tables:
return self.normalize_tables(self.param_tables)
return None
@property
def lists(self):
if self._values['lists']:
return self.normalize_list(self._values['lists'])
elif self.param_lists:
return self.normalize_list(self.param_lists)
return None
@property
def variables(self):
if self._values['variables']:
return self.normalize_variables(self._values['variables'])
elif self.param_variables:
return self.normalize_variables(self.param_variables)
return None
@property
def metadata(self):
if self._values['metadata']:
result = self.normalize_metadata(self._values['metadata'])
elif self.param_metadata:
result = self.normalize_metadata(self.param_metadata)
else:
return None
return result
@property
def template(self):
@ -389,55 +543,67 @@ class Parameters(AnsibleF5Parameters):
return None
return fq_name(self.partition, self._values['template'])
@template.setter
def template(self, value):
self._values['template'] = value
@property
def strict_updates(self):
if self._values['strict_updates'] is None and self.strictUpdates is None:
return None
# Specifying the value overrides any associated value in the payload
elif self._values['strict_updates'] is True:
return 'enabled'
elif self._values['strict_updates'] is False:
return 'disabled'
# This will be automatically `None` if it was not set by the
# `parameters` setter
elif self.strictUpdates:
return self.strictUpdates
def device_group(self):
if self._values['device_group'] not in [None, 'none']:
result = fq_name(self.partition, self._values['device_group'])
elif self.param_device_group not in [None, 'none']:
result = self.param_device_group
else:
return self._values['strict_updates']
return None
if not result.startswith('/Common/'):
raise F5ModuleError(
"Device groups can only exist in /Common"
)
return result
@property
def traffic_group(self):
if self._values['traffic_group'] is None and self.trafficGroup is None:
if self._values['traffic_group']:
result = fq_name(self.partition, self._values['traffic_group'])
elif self.param_traffic_group:
result = self.param_traffic_group
else:
return None
# Specifying the value overrides any associated value in the payload
elif self._values['traffic_group']:
result = fq_name(self.partition, self._values['traffic_group'])
# This will be automatically `None` if it was not set by the
# `parameters` setter
elif self.trafficGroup:
result = fq_name(self.partition, self.trafficGroup)
else:
result = fq_name(self.partition, self._values['traffic_group'])
if result.startswith('/Common/'):
return result
else:
if not result.startswith('/Common/'):
raise F5ModuleError(
"Traffic groups can only exist in /Common"
)
return result
@property
def strict_updates(self):
if self._values['strict_updates'] is not None:
result = flatten_boolean(self._values['strict_updates'])
elif self.param_strict_updates is not None:
result = self.param_strict_updates
else:
return None
if result == 'yes':
return 'enabled'
return 'disabled'
@property
def description(self):
if self._values['description']:
return self._values['description']
elif self.param_description:
return self.param_description
return None
class Changes(Parameters):
pass
class UsableChanges(Changes):
pass
class ReportableChanges(Changes):
pass
class Difference(object):
def __init__(self, want, have=None):
self.want = want
@ -460,9 +626,17 @@ class Difference(object):
return attr1
@property
def traffic_group(self):
if self.want.traffic_group != self.have.traffic_group:
return self.want.traffic_group
def metadata(self):
if self.want.metadata is None:
return None
if self.have.metadata is None:
return self.want.metadata
want = [(k, v) for d in self.want.metadata for k, v in iteritems(d)]
have = [(k, v) for d in self.have.metadata for k, v in iteritems(d)]
if set(want) != set(have):
return dict(
metadata=self.want.metadata
)
class ModuleManager(object):
@ -470,8 +644,8 @@ class ModuleManager(object):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
self.have = None
self.want = Parameters(params=self.module.params)
self.changes = Changes()
self.want = ModuleParameters(params=self.module.params)
self.changes = UsableChanges()
def _set_changed_options(self):
changed = {}
@ -479,7 +653,7 @@ class ModuleManager(object):
if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key)
if changed:
self.changes = Changes(params=changed)
self.changes = UsableChanges(params=changed)
def _update_changed_options(self):
diff = Difference(self.want, self.have)
@ -495,7 +669,7 @@ class ModuleManager(object):
else:
changed[k] = change
if changed:
self.changes = Changes(params=changed)
self.changes = UsableChanges(params=changed)
return True
return False
@ -532,8 +706,12 @@ class ModuleManager(object):
def create(self):
self._set_changed_options()
if self.want.traffic_group is None and self.want.trafficGroup is None:
if self.want.traffic_group is None:
self.want.update({'traffic_group': '/Common/traffic-group-1'})
if not self.template_exists():
raise F5ModuleError(
"The specified template does not exist in the provided partition."
)
if self.module.check_mode:
return True
self.create_on_device()
@ -555,13 +733,20 @@ class ModuleManager(object):
return False
def update_on_device(self):
params = self.want.api_params()
params['execute-action'] = 'definition'
params = self.changes.api_params()
resource = self.client.api.tm.sys.application.services.service.load(
name=self.want.name,
partition=self.want.partition
)
resource.update(**params)
if params:
params['execute-action'] = 'definition'
resource.update(**params)
if self.changes.metadata:
params = {'execute-action': 'definition'}
resource.update(
metadata=self.changes.metadata,
**params
)
def read_current_from_device(self):
result = self.client.api.tm.sys.application.services.service.load(
@ -569,15 +754,26 @@ class ModuleManager(object):
partition=self.want.partition
).to_dict()
result.pop('_meta_data', None)
return Parameters(params=result)
return ApiParameters(params=result)
def template_exists(self):
name = fq_name(self.want.partition, self.want.template)
parts = name.split('/')
result = self.client.api.tm.sys.application.templates.template.exists(
name=parts[2],
partition=parts[1]
)
return result
def create_on_device(self):
params = self.want.api_params()
self.client.api.tm.sys.application.services.service.create(
params = self.changes.api_params()
resource = self.client.api.tm.sys.application.services.service.create(
name=self.want.name,
partition=self.want.partition,
**params
)
if self.changes.metadata:
resource.update(metadata=self.changes.metadata)
def absent(self):
if self.exists():
@ -598,6 +794,13 @@ class ModuleManager(object):
partition=self.want.partition
)
if resource:
# Metadata needs to be zero'd before the service is removed because
# otherwise, the API will error out saying that "configuration items"
# currently exist.
#
# In other words, the REST API is not able to delete a service while
# there is existing metadata
resource.update(metadata=[])
resource.delete()
@ -607,6 +810,8 @@ class ArgumentSpec(object):
argument_spec = dict(
name=dict(required=True),
template=dict(),
description=dict(),
device_group=dict(),
parameters=dict(
type='dict'
),
@ -622,6 +827,7 @@ class ArgumentSpec(object):
type='bool',
default='yes'
),
metadata=dict(type='list'),
traffic_group=dict(),
partition=dict(
default='Common',