Remove f5-sdk from bigip_iapp_template (#48512)

pull/48515/head
Tim Rupp 6 years ago committed by GitHub
parent 8dabd0cde3
commit 4579b6516f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

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

Loading…
Cancel
Save