forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds log publisher module for f5 (ansible#39518)
This module can be used to manage log publishers on a bigip
- Loading branch information
1 parent
19fee0e
commit 609b182
Showing
3 changed files
with
556 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,394 @@ | ||
#!/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_log_publisher | ||
short_description: Manages log publishers on a BIG-IP | ||
description: | ||
- Manages log publishers on a BIG-IP. | ||
version_added: 2.6 | ||
options: | ||
name: | ||
description: | ||
- Specifies the name of the log publisher. | ||
required: True | ||
description: | ||
description: | ||
- Specifies a description for the log publisher. | ||
destinations: | ||
description: | ||
- Specifies log destinations for this log publisher to use. | ||
partition: | ||
description: | ||
- Device partition to manage resources on. | ||
default: Common | ||
state: | ||
description: | ||
- When C(present), ensures that the resource exists. | ||
- When C(absent), ensures the resource is removed. | ||
default: present | ||
choices: | ||
- present | ||
- absent | ||
extends_documentation_fragment: f5 | ||
author: | ||
- Tim Rupp (@caphrim007) | ||
''' | ||
|
||
EXAMPLES = r''' | ||
- name: Create a log publisher for use in high speed loggins | ||
bigip_log_publisher: | ||
name: publisher1 | ||
destinations: | ||
- hsl1 | ||
- security-log-servers-logging | ||
password: secret | ||
server: lb.mydomain.com | ||
state: present | ||
user: admin | ||
delegate_to: localhost | ||
''' | ||
|
||
RETURN = r''' | ||
description: | ||
description: The new description of the resource. | ||
returned: changed | ||
type: string | ||
sample: "Security log publisher" | ||
destinations: | ||
description: The new list of destinations for the resource. | ||
returned: changed | ||
type: list | ||
sample: ['/Common/destination1', '/Common/destination2'] | ||
''' | ||
|
||
from ansible.module_utils.basic import AnsibleModule | ||
from ansible.module_utils.basic import env_fallback | ||
|
||
try: | ||
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 fq_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 | ||
except ImportError: | ||
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 fq_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 = { | ||
|
||
} | ||
|
||
api_attributes = [ | ||
'destinations', | ||
'description', | ||
] | ||
|
||
returnables = [ | ||
'destinations', | ||
'description', | ||
] | ||
|
||
updatables = [ | ||
'destinations', | ||
'description', | ||
] | ||
|
||
|
||
class ApiParameters(Parameters): | ||
@property | ||
def destinations(self): | ||
if self._values['destinations'] is None: | ||
return None | ||
results = [] | ||
for destination in self._values['destinations']: | ||
result = fq_name(destination['partition'], destination['name']) | ||
results.append(result) | ||
results.sort() | ||
return results | ||
|
||
|
||
class ModuleParameters(Parameters): | ||
@property | ||
def destinations(self): | ||
if self._values['destinations'] is None: | ||
return None | ||
if len(self._values['destinations']) == 1 and self._values['destinations'][0] == '': | ||
return '' | ||
result = [fq_name(self.partition, x) for x in self._values['destinations']] | ||
result = list(set(result)) | ||
result.sort() | ||
return result | ||
|
||
|
||
class Changes(Parameters): | ||
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 | ||
|
||
|
||
class UsableChanges(Changes): | ||
pass | ||
|
||
|
||
class ReportableChanges(Changes): | ||
pass | ||
|
||
|
||
class Difference(object): | ||
def __init__(self, want, have=None): | ||
self.want = want | ||
self.have = have | ||
|
||
def compare(self, param): | ||
try: | ||
result = getattr(self, param) | ||
return result | ||
except AttributeError: | ||
return self.__default(param) | ||
|
||
def __default(self, param): | ||
attr1 = getattr(self.want, param) | ||
try: | ||
attr2 = getattr(self.have, param) | ||
if attr1 != attr2: | ||
return attr1 | ||
except AttributeError: | ||
return attr1 | ||
|
||
@property | ||
def destinations(self): | ||
if self.want.destinations is None: | ||
return None | ||
if self.have.destinations is None and self.want.destinations == '': | ||
return None | ||
if self.have.destinations is not None and self.want.destinations == '': | ||
return [] | ||
if self.have.destinations is None: | ||
return self.want.destinations | ||
if set(self.want.destinations) != set(self.have.destinations): | ||
return self.want.destinations | ||
|
||
|
||
class ModuleManager(object): | ||
def __init__(self, *args, **kwargs): | ||
self.module = kwargs.get('module', None) | ||
self.client = kwargs.get('client', None) | ||
self.want = ModuleParameters(params=self.module.params) | ||
self.have = ApiParameters() | ||
self.changes = UsableChanges() | ||
|
||
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 = UsableChanges(params=changed) | ||
|
||
def _update_changed_options(self): | ||
diff = Difference(self.want, self.have) | ||
updatables = Parameters.updatables | ||
changed = dict() | ||
for k in updatables: | ||
change = diff.compare(k) | ||
if change is None: | ||
continue | ||
else: | ||
if isinstance(change, dict): | ||
changed.update(change) | ||
else: | ||
changed[k] = change | ||
if changed: | ||
self.changes = UsableChanges(params=changed) | ||
return True | ||
return False | ||
|
||
def should_update(self): | ||
result = self._update_changed_options() | ||
if result: | ||
return True | ||
return False | ||
|
||
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)) | ||
|
||
reportable = ReportableChanges(params=self.changes.to_return()) | ||
changes = reportable.to_return() | ||
result.update(**changes) | ||
result.update(dict(changed=changed)) | ||
self._announce_deprecations(result) | ||
return result | ||
|
||
def _announce_deprecations(self, result): | ||
warnings = result.pop('__warnings', []) | ||
for warning in warnings: | ||
self.client.module.deprecate( | ||
msg=warning['msg'], | ||
version=warning['version'] | ||
) | ||
|
||
def present(self): | ||
if self.exists(): | ||
return self.update() | ||
else: | ||
return self.create() | ||
|
||
def exists(self): | ||
result = self.client.api.tm.sys.log_config.publishers.publisher.exists( | ||
name=self.want.name, | ||
partition=self.want.partition | ||
) | ||
return result | ||
|
||
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 remove(self): | ||
if self.module.check_mode: | ||
return True | ||
self.remove_from_device() | ||
if self.exists(): | ||
raise F5ModuleError("Failed to delete the resource.") | ||
return True | ||
|
||
def create(self): | ||
self._set_changed_options() | ||
if self.module.check_mode: | ||
return True | ||
self.create_on_device() | ||
return True | ||
|
||
def create_on_device(self): | ||
params = self.changes.api_params() | ||
self.client.api.tm.sys.log_config.publishers.publisher.create( | ||
name=self.want.name, | ||
partition=self.want.partition, | ||
**params | ||
) | ||
|
||
def update_on_device(self): | ||
params = self.changes.api_params() | ||
resource = self.client.api.tm.sys.log_config.publishers.publisher.load( | ||
name=self.want.name, | ||
partition=self.want.partition | ||
) | ||
resource.modify(**params) | ||
|
||
def absent(self): | ||
if self.exists(): | ||
return self.remove() | ||
return False | ||
|
||
def remove_from_device(self): | ||
resource = self.client.api.tm.sys.log_config.publishers.publisher.load( | ||
name=self.want.name, | ||
partition=self.want.partition | ||
) | ||
if resource: | ||
resource.delete() | ||
|
||
def read_current_from_device(self): | ||
resource = self.client.api.tm.sys.log_config.publishers.publisher.load( | ||
name=self.want.name, | ||
partition=self.want.partition | ||
) | ||
result = resource.attrs | ||
return ApiParameters(params=result) | ||
|
||
|
||
class ArgumentSpec(object): | ||
def __init__(self): | ||
self.supports_check_mode = True | ||
argument_spec = dict( | ||
name=dict(required=True), | ||
description=dict(), | ||
destinations=dict(type='list'), | ||
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) | ||
|
||
|
||
def main(): | ||
spec = ArgumentSpec() | ||
|
||
module = AnsibleModule( | ||
argument_spec=spec.argument_spec, | ||
supports_check_mode=spec.supports_check_mode | ||
) | ||
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 ex: | ||
cleanup_tokens(client) | ||
module.fail_json(msg=str(ex)) | ||
|
||
|
||
if __name__ == '__main__': | ||
main() |
Oops, something went wrong.