1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Remove f5-sdk from bigip_iapp_template (#48512)

This commit is contained in:
Tim Rupp 2018-11-10 19:12:35 -08:00 committed by GitHub
parent 8dabd0cde3
commit 4579b6516f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 233 additions and 120 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 F5 Networks Inc.
# 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
@ -66,6 +66,7 @@ options:
extends_documentation_fragment: f5
author:
- Tim Rupp (@caphrim007)
- Wojciech Wypior (@wojtek0806)
'''
EXAMPLES = r'''
@ -109,31 +110,27 @@ 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.bigip import F5RestClient
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 f5_argument_spec
from library.module_utils.network.f5.common import fq_name
try:
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from f5.utils.iapp_parser import NonextantTemplateNameException
except ImportError:
HAS_F5SDK = False
from library.module_utils.network.f5.common import f5_argument_spec
from library.module_utils.network.f5.common import exit_json
from library.module_utils.network.f5.common import fail_json
from library.module_utils.network.f5.common import transform_name
from library.module_utils.network.f5.icontrol import upload_file
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.bigip import F5RestClient
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 f5_argument_spec
from ansible.module_utils.network.f5.common import fq_name
try:
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
from f5.utils.iapp_parser import NonextantTemplateNameException
except ImportError:
HAS_F5SDK = False
from ansible.module_utils.network.f5.common import f5_argument_spec
from ansible.module_utils.network.f5.common import exit_json
from ansible.module_utils.network.f5.common import fail_json
from ansible.module_utils.network.f5.common import transform_name
from ansible.module_utils.network.f5.icontrol import upload_file
try:
from StringIO import StringIO
@ -151,13 +148,8 @@ class Parameters(AnsibleF5Parameters):
if self._values['name']:
return self._values['name']
if self._values['content']:
try:
name = self._get_template_name()
return name
except NonextantTemplateNameException:
raise F5ModuleError(
"No template name was found in the template"
)
return None
@property
@ -172,21 +164,10 @@ class Parameters(AnsibleF5Parameters):
def checksum(self):
return self._values['tmplChecksum']
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
def _squash_template_name_prefix(self):
"""Removes the template name prefix
The IappParser in the SDK treats the partition prefix as part of
the iApp's name. This method removes that partition from the name
This method removes that partition from the name
in the iApp so that comparisons can be done properly and entries
can be created properly when using REST.
@ -217,9 +198,6 @@ class Parameters(AnsibleF5Parameters):
return re.sub(pattern, replace, template)
def _get_template_name(self):
# There is a bug in the iApp parser in the F5 SDK that prevents us from
# using it in all cases to get the name of an iApp. So we'll use this
# pattern for now and file a bug with the F5 SDK
pattern = r'sys\s+application\s+template\s+(?P<path>\/[^\{}"\'*?|#]+\/)?(?P<name>[^\{}"\'*?|#]+)'
matches = re.search(pattern, self._values['content'])
try:
@ -228,33 +206,70 @@ class Parameters(AnsibleF5Parameters):
result = None
if result:
return result
raise NonextantTemplateNameException
raise F5ModuleError(
"No template name was found in the template"
)
class ApiParameters(Parameters):
pass
class ModuleParameters(Parameters):
pass
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 ModuleManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
self.have = None
self.want = Parameters(params=self.module.params)
self.changes = Parameters()
self.want = ModuleParameters(params=self.module.params)
self.have = ApiParameters()
self.changes = UsableChanges()
def _announce_deprecations(self, result):
warnings = result.pop('__warnings', [])
for warning in warnings:
self.client.module.deprecate(
msg=warning['msg'],
version=warning['version']
)
def exec_module(self):
result = dict()
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))
if state == "present":
changed = self.present()
elif state == "absent":
changed = self.absent()
changes = self.changes.to_return()
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 present(self):
@ -263,6 +278,29 @@ class ModuleManager(object):
else:
return self.create()
def absent(self):
changed = False
if self.exists():
changed = self.remove()
return changed
def create(self):
if self.module.check_mode:
return True
self.create_on_device()
if self.exists():
return True
else:
raise F5ModuleError("Failed to create the iApp template")
def remove(self):
if self.module.check_mode:
return True
self.remove_from_device()
if self.exists():
raise F5ModuleError("Failed to delete the iApp template")
return True
def update(self):
self.have = self.read_current_from_device()
@ -281,35 +319,63 @@ class ModuleManager(object):
self._generate_template_checksum_on_device()
return True
def exists(self):
uri = "https://{0}:{1}/mgmt/tm/sys/application/template/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
resp = self.client.api.get(uri)
try:
response = resp.json()
except ValueError:
return False
if resp.status == 404 or 'code' in response and response['code'] == 404:
return False
return True
def template_in_use(self):
collection = self.client.api.tm.sys.application.services.get_collection()
fullname = '/{0}/{1}'.format(self.want.partition, self.want.name)
for resource in collection:
if resource.template == fullname:
uri = "https://{0}:{1}/mgmt/tm/sys/application/service/".format(
self.client.provider['server'],
self.client.provider['server_port'],
)
name = fq_name(self.want.partition, self.want.name)
resp = self.client.api.get(uri)
try:
response = resp.json()
except ValueError:
return False
if resp.status == 404 or 'code' in response and response['code'] == 404:
return False
for item in response['items']:
if item['template'] == name:
return True
return False
def read_current_from_device(self):
self._generate_template_checksum_on_device()
resource = self.client.api.tm.sys.application.templates.template.load(
name=self.want.name,
partition=self.want.partition
uri = "https://{0}:{1}/mgmt/tm/sys/application/template/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
result = resource.attrs
return Parameters(params=result)
def absent(self):
changed = False
if self.exists():
changed = self.remove()
return changed
resp = self.client.api.get(uri)
def exists(self):
result = self.client.api.tm.sys.application.templates.template.exists(
name=self.want.name,
partition=self.want.partition
)
return result
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
return ApiParameters(params=response)
def _remove_iapp_checksum(self):
"""Removes the iApp tmplChecksum
@ -320,11 +386,25 @@ class ModuleManager(object):
:return:
"""
resource = self.client.api.tm.sys.application.templates.template.load(
name=self.want.name,
partition=self.want.partition
uri = "https://{0}:{1}/mgmt/tm/sys/application/template/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
resource.modify(tmplChecksum=None)
params = dict(tmplChecksum=None)
resp = self.client.api.patch(uri, json=params)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
def templates_differ(self):
# BIG-IP can generate checksums of iApps, but the iApp needs to be
@ -365,57 +445,86 @@ class ModuleManager(object):
return temp
def _generate_template_checksum_on_device(self):
generate = 'tmsh generate sys application template {0} checksum'.format(
command = 'tmsh generate sys application template {0} checksum'.format(
self.want.name
)
self.client.api.tm.util.bash.exec_cmd(
'run',
utilCmdArgs='-c "{0}"'.format(generate)
params = dict(
command="run",
utilCmdArgs='-c "{0}"'.format(command)
)
uri = "https://{0}:{1}/mgmt/tm/util/bash".format(
self.client.provider['server'],
self.client.provider['server_port']
)
def create(self):
if self.module.check_mode:
return True
self.create_on_device()
if self.exists():
return True
else:
raise F5ModuleError("Failed to create the iApp template")
resp = self.client.api.post(uri, json=params)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] in [400, 403]:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
def upload_file_to_device(self, content, name):
url = 'https://{0}:{1}/mgmt/shared/file-transfer/uploads'.format(
self.client.provider['server'],
self.client.provider['server_port']
)
try:
upload_file(self.client, url, content, name)
except F5ModuleError:
raise F5ModuleError(
"Failed to upload the file."
)
def create_on_device(self):
remote_path = "/var/config/rest/downloads/{0}".format(self.want.name)
load_command = 'tmsh load sys application template {0}'.format(remote_path)
template = StringIO(self.want.content)
self.upload_file_to_device(template, self.want.name)
upload = self.client.api.shared.file_transfer.uploads
upload.upload_stringio(template, self.want.name)
output = self.client.api.tm.util.bash.exec_cmd(
'run',
uri = "https://{0}:{1}/mgmt/tm/util/bash".format(
self.client.provider['server'],
self.client.provider['server_port']
)
params = dict(
command="run",
utilCmdArgs='-c "{0}"'.format(load_command)
)
if hasattr(output, 'commandResult'):
result = output.commandResult
if 'Syntax Error' in result:
raise F5ModuleError(output.commandResult)
if 'ERROR' in result:
raise F5ModuleError(output.commandResult)
resp = self.client.api.post(uri, json=params)
def remove(self):
if self.module.check_mode:
return True
self.remove_from_device()
if self.exists():
raise F5ModuleError("Failed to delete the iApp template")
try:
response = resp.json()
if 'commandResult' in response:
if 'Syntax Error' in response['commandResult']:
raise F5ModuleError(response['commandResult'])
if 'ERROR' in response['commandResult']:
raise F5ModuleError(response['commandResult'])
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] in [400, 403]:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
return True
def remove_from_device(self):
resource = self.client.api.tm.sys.application.templates.template.load(
name=self.want.name,
partition=self.want.partition
uri = "https://{0}:{1}/mgmt/tm/sys/application/template/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
resource.delete()
response = self.client.api.delete(uri)
if response.status == 200:
return True
raise F5ModuleError(response.content)
class ArgumentSpec(object):
@ -448,18 +557,17 @@ def main():
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")
client = F5RestClient(**module.params)
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:
exit_json(module, results, client)
except F5ModuleError as ex:
cleanup_tokens(client)
module.fail_json(msg=str(e))
fail_json(module, ex, client)
if __name__ == '__main__':

View file

@ -14,25 +14,30 @@ from nose.plugins.skip import SkipTest
if sys.version_info < (2, 7):
raise SkipTest("F5 Ansible modules require Python >= 2.7")
from units.compat import unittest
from units.compat.mock import Mock
from units.compat.mock import patch
from ansible.module_utils.basic import AnsibleModule
try:
from library.modules.bigip_iapp_template import Parameters
from library.modules.bigip_iapp_template import ModuleManager
from library.modules.bigip_iapp_template import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
from test.unit.modules.utils import set_module_args
# In Ansible 2.8, Ansible changed import paths.
from test.units.compat import unittest
from test.units.compat.mock import Mock
from test.units.compat.mock import patch
from test.units.modules.utils import set_module_args
except ImportError:
try:
from ansible.modules.network.f5.bigip_iapp_template import Parameters
from ansible.modules.network.f5.bigip_iapp_template import ArgumentSpec
from ansible.modules.network.f5.bigip_iapp_template import ModuleManager
from ansible.module_utils.network.f5.common import F5ModuleError
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
# Ansible 2.8 imports
from units.compat import unittest
from units.compat.mock import Mock
from units.compat.mock import patch
from units.modules.utils import set_module_args
except ImportError:
raise SkipTest("F5 Ansible modules require the f5-sdk Python library")