mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-04-27 04:41:26 -07:00
Primarily, this patch contains refactors to remove tracebacks that are generated when libraries are missing. There is also, * Removed re-def of cleanup_tokens. * Changed parameter args to be keywords. * Changed imports to include new module_util locations. * Imports also include developing (sideband) module_util locations. * Changed to using F5Client and plain AnsibleModule to prevent tracebacks caused by missing libraries. * Removed init and update methods from most Parameter classes (optimization) as its now included in module_utils. * Changed module and module param references to take into account the new self.module arg. Minor bug fixes made during this refactor.
450 lines
13 KiB
Python
450 lines
13 KiB
Python
#!/usr/bin/python
|
|
# -*- 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
|
|
|
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
|
'status': ['preview'],
|
|
'supported_by': 'community'}
|
|
|
|
DOCUMENTATION = r'''
|
|
---
|
|
module: bigip_irule
|
|
short_description: Manage iRules across different modules on a BIG-IP
|
|
description:
|
|
- Manage iRules across different modules on a BIG-IP.
|
|
version_added: "2.2"
|
|
options:
|
|
content:
|
|
description:
|
|
- When used instead of 'src', sets the contents of an iRule directly to
|
|
the specified value. This is for simple values, but can be used with
|
|
lookup plugins for anything complex or with formatting. Either one
|
|
of C(src) or C(content) must be provided.
|
|
module:
|
|
description:
|
|
- The BIG-IP module to add the iRule to.
|
|
required: True
|
|
choices:
|
|
- ltm
|
|
- gtm
|
|
name:
|
|
description:
|
|
- The name of the iRule.
|
|
required: True
|
|
src:
|
|
description:
|
|
- The iRule file to interpret and upload to the BIG-IP. Either one
|
|
of C(src) or C(content) must be provided.
|
|
required: True
|
|
state:
|
|
description:
|
|
- Whether the iRule should exist or not.
|
|
default: present
|
|
choices:
|
|
- present
|
|
- absent
|
|
partition:
|
|
description:
|
|
- Device partition to manage resources on.
|
|
default: Common
|
|
version_added: 2.5
|
|
extends_documentation_fragment: f5
|
|
author:
|
|
- Tim Rupp (@caphrim007)
|
|
'''
|
|
|
|
EXAMPLES = r'''
|
|
- name: Add the iRule contained in template irule.tcl to the LTM module
|
|
bigip_irule:
|
|
content: "{{ lookup('template', 'irule.tcl') }}"
|
|
module: ltm
|
|
name: MyiRule
|
|
password: secret
|
|
server: lb.mydomain.com
|
|
state: present
|
|
user: admin
|
|
delegate_to: localhost
|
|
|
|
- name: Add the iRule contained in static file irule.tcl to the LTM module
|
|
bigip_irule:
|
|
module: ltm
|
|
name: MyiRule
|
|
password: secret
|
|
server: lb.mydomain.com
|
|
src: irule.tcl
|
|
state: present
|
|
user: admin
|
|
delegate_to: localhost
|
|
'''
|
|
|
|
RETURN = r'''
|
|
module:
|
|
description: The module that the iRule was added to
|
|
returned: changed and success
|
|
type: string
|
|
sample: gtm
|
|
src:
|
|
description: The filename that included the iRule source
|
|
returned: changed and success, when provided
|
|
type: string
|
|
sample: /opt/src/irules/example1.tcl
|
|
content:
|
|
description: The content of the iRule that was managed
|
|
returned: changed and success
|
|
type: string
|
|
sample: "when LB_FAILED { set wipHost [LB::server addr] }"
|
|
'''
|
|
|
|
import os
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
from ansible.module_utils.basic import env_fallback
|
|
|
|
HAS_DEVEL_IMPORTS = False
|
|
|
|
try:
|
|
# Sideband repository used for dev
|
|
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
|
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 AnsibleF5Parameters
|
|
from library.module_utils.network.f5.common import cleanup_tokens
|
|
from library.module_utils.network.f5.common import fqdn_name
|
|
from library.module_utils.network.f5.common import f5_argument_spec
|
|
try:
|
|
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
|
except ImportError:
|
|
HAS_F5SDK = False
|
|
HAS_DEVEL_IMPORTS = True
|
|
except ImportError:
|
|
# Upstream Ansible
|
|
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
|
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 AnsibleF5Parameters
|
|
from ansible.module_utils.network.f5.common import cleanup_tokens
|
|
from ansible.module_utils.network.f5.common import fqdn_name
|
|
from ansible.module_utils.network.f5.common import f5_argument_spec
|
|
try:
|
|
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
|
except ImportError:
|
|
HAS_F5SDK = False
|
|
|
|
|
|
class Parameters(AnsibleF5Parameters):
|
|
api_map = {
|
|
'apiAnonymous': 'content'
|
|
}
|
|
|
|
updatables = [
|
|
'content'
|
|
]
|
|
|
|
api_attributes = [
|
|
'apiAnonymous'
|
|
]
|
|
|
|
returnables = [
|
|
'content', 'src', 'module'
|
|
]
|
|
|
|
def to_return(self):
|
|
result = {}
|
|
try:
|
|
for returnable in self.returnables:
|
|
result[returnable] = getattr(self, returnable)
|
|
result = self._filter_params(result)
|
|
except Exception:
|
|
pass
|
|
return result
|
|
|
|
@property
|
|
def content(self):
|
|
if self._values['content'] is None:
|
|
result = self.src_content
|
|
else:
|
|
result = self._values['content']
|
|
|
|
return str(result).strip()
|
|
|
|
@property
|
|
def src(self):
|
|
if self._values['src'] is None:
|
|
return None
|
|
return self._values['src']
|
|
|
|
@property
|
|
def src_content(self):
|
|
if not os.path.exists(self._values['src']):
|
|
raise F5ModuleError(
|
|
"The specified 'src' was not found."
|
|
)
|
|
with open(self._values['src']) as f:
|
|
result = f.read()
|
|
return result
|
|
|
|
|
|
class ModuleManager(object):
|
|
def __init__(self, *args, **kwargs):
|
|
self.client = kwargs.get('client', None)
|
|
self.module = kwargs.get('module', None)
|
|
self.kwargs = kwargs
|
|
|
|
def exec_module(self):
|
|
if self.module.params['module'] == 'ltm':
|
|
manager = self.get_manager('ltm')
|
|
elif self.module.params['module'] == 'gtm':
|
|
manager = self.get_manager('gtm')
|
|
else:
|
|
raise F5ModuleError(
|
|
"An unknown iRule module type was specified"
|
|
)
|
|
return manager.exec_module()
|
|
|
|
def get_manager(self, type):
|
|
if type == 'ltm':
|
|
return LtmManager(**self.kwargs)
|
|
elif type == 'gtm':
|
|
return GtmManager(**self.kwargs)
|
|
|
|
|
|
class BaseManager(object):
|
|
def __init__(self, *args, **kwargs):
|
|
self.module = kwargs.get('module', None)
|
|
self.client = kwargs.get('client', None)
|
|
self.want = Parameters(params=self.module.params)
|
|
self.changes = Parameters()
|
|
|
|
def exec_module(self):
|
|
changed = False
|
|
result = dict()
|
|
state = self.want.state
|
|
|
|
try:
|
|
if state == "present":
|
|
changed = self.present()
|
|
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))
|
|
return result
|
|
|
|
def _set_changed_options(self):
|
|
changed = {}
|
|
for key in Parameters.returnables:
|
|
if getattr(self.want, key) is not None:
|
|
changed[key] = getattr(self.want, key)
|
|
if changed:
|
|
self.changes = Parameters(params=changed)
|
|
|
|
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(params=changed)
|
|
return True
|
|
return False
|
|
|
|
def present(self):
|
|
if not self.want.content and not self.want.src:
|
|
raise F5ModuleError(
|
|
"Either 'content' or 'src' must be provided"
|
|
)
|
|
if self.exists():
|
|
return self.update()
|
|
else:
|
|
return self.create()
|
|
|
|
def create(self):
|
|
self._set_changed_options()
|
|
if self.module.check_mode:
|
|
return True
|
|
self.create_on_device()
|
|
if not self.exists():
|
|
raise F5ModuleError("Failed to create the iRule")
|
|
return True
|
|
|
|
def should_update(self):
|
|
result = self._update_changed_options()
|
|
if result:
|
|
return True
|
|
return False
|
|
|
|
def update(self):
|
|
self.have = self.read_current_from_device()
|
|
if not self.should_update():
|
|
return False
|
|
if self.module.check_mode:
|
|
return True
|
|
self.update_on_device()
|
|
return True
|
|
|
|
def absent(self):
|
|
if self.exists():
|
|
return self.remove()
|
|
return False
|
|
|
|
def remove(self):
|
|
if self.module.check_mode:
|
|
return True
|
|
self.remove_from_device()
|
|
if self.exists():
|
|
raise F5ModuleError("Failed to delete the iRule")
|
|
return True
|
|
|
|
|
|
class LtmManager(BaseManager):
|
|
def exists(self):
|
|
result = self.client.api.tm.ltm.rules.rule.exists(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
return result
|
|
|
|
def update_on_device(self):
|
|
params = self.changes.api_params()
|
|
resource = self.client.api.tm.ltm.rules.rule.load(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
resource.update(**params)
|
|
|
|
def create_on_device(self):
|
|
params = self.want.api_params()
|
|
resource = self.client.api.tm.ltm.rules.rule
|
|
resource.create(
|
|
name=self.want.name,
|
|
partition=self.want.partition,
|
|
**params
|
|
)
|
|
|
|
def read_current_from_device(self):
|
|
resource = self.client.api.tm.ltm.rules.rule.load(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
result = resource.attrs
|
|
return Parameters(params=result)
|
|
|
|
def remove_from_device(self):
|
|
resource = self.client.api.tm.ltm.rules.rule.load(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
resource.delete()
|
|
|
|
|
|
class GtmManager(BaseManager):
|
|
def read_current_from_device(self):
|
|
resource = self.client.api.tm.gtm.rules.rule.load(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
result = resource.attrs
|
|
return Parameters(params=result)
|
|
|
|
def remove_from_device(self):
|
|
resource = self.client.api.tm.gtm.rules.rule.load(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
resource.delete()
|
|
|
|
def exists(self):
|
|
result = self.client.api.tm.gtm.rules.rule.exists(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
return result
|
|
|
|
def update_on_device(self):
|
|
params = self.changes.api_params()
|
|
resource = self.client.api.tm.gtm.rules.rule.load(
|
|
name=self.want.name,
|
|
partition=self.want.partition
|
|
)
|
|
resource.update(**params)
|
|
|
|
def create_on_device(self):
|
|
params = self.want.api_params()
|
|
resource = self.client.api.tm.gtm.rules.rule
|
|
resource.create(
|
|
name=self.want.name,
|
|
partition=self.want.partition,
|
|
**params
|
|
)
|
|
|
|
|
|
class ArgumentSpec(object):
|
|
def __init__(self):
|
|
self.supports_check_mode = True
|
|
argument_spec = dict(
|
|
content=dict(
|
|
required=False,
|
|
default=None
|
|
),
|
|
src=dict(
|
|
required=False,
|
|
default=None
|
|
),
|
|
name=dict(required=True),
|
|
module=dict(
|
|
required=True,
|
|
choices=['gtm', 'ltm']
|
|
),
|
|
state=dict(
|
|
default='present',
|
|
choices=['present', 'absent']
|
|
),
|
|
partition=dict(
|
|
default='Common',
|
|
fallback=(env_fallback, ['F5_PARTITION'])
|
|
)
|
|
)
|
|
self.argument_spec = {}
|
|
self.argument_spec.update(f5_argument_spec)
|
|
self.argument_spec.update(argument_spec)
|
|
self.mutually_exclusive = [
|
|
['content', 'src']
|
|
]
|
|
|
|
|
|
def main():
|
|
spec = ArgumentSpec()
|
|
|
|
module = AnsibleModule(
|
|
argument_spec=spec.argument_spec,
|
|
supports_check_mode=spec.supports_check_mode,
|
|
mutually_exclusive=spec.mutually_exclusive
|
|
)
|
|
if not HAS_F5SDK:
|
|
module.fail_json(msg="The python f5-sdk module is required")
|
|
|
|
try:
|
|
client = F5Client(**module.params)
|
|
mm = ModuleManager(module=module, client=client)
|
|
results = mm.exec_module()
|
|
cleanup_tokens(client)
|
|
module.exit_json(**results)
|
|
except F5ModuleError as e:
|
|
cleanup_tokens(client)
|
|
module.fail_json(msg=str(e))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|