diff --git a/lib/ansible/module_utils/network/f5/common.py b/lib/ansible/module_utils/network/f5/common.py index 69986b884b..61774a33f1 100644 --- a/lib/ansible/module_utils/network/f5/common.py +++ b/lib/ansible/module_utils/network/f5/common.py @@ -60,7 +60,8 @@ f5_provider_spec = { default='rest' ), 'timeout': dict(type='int'), - 'auth_provider': dict() + 'auth_provider': dict(), + 'proxy_to': dict(), } f5_argument_spec = { @@ -70,27 +71,22 @@ f5_argument_spec = { f5_top_spec = { 'server': dict( removed_in_version=2.9, - fallback=(env_fallback, ['F5_SERVER']) ), 'user': dict( removed_in_version=2.9, - fallback=(env_fallback, ['F5_USER', 'ANSIBLE_NET_USERNAME']) ), 'password': dict( removed_in_version=2.9, no_log=True, aliases=['pass', 'pwd'], - fallback=(env_fallback, ['F5_PASSWORD', 'ANSIBLE_NET_PASSWORD']) ), 'validate_certs': dict( removed_in_version=2.9, type='bool', - fallback=(env_fallback, ['F5_VALIDATE_CERTS']) ), 'server_port': dict( removed_in_version=2.9, type='int', - fallback=(env_fallback, ['F5_SERVER_PORT']) ), 'transport': dict( removed_in_version=2.9, @@ -135,7 +131,7 @@ def fqdn_name(partition, value): return fq_name(partition, value) -def fq_name(partition, value): +def fq_name(partition, value, sub_path=''): """Returns a 'Fully Qualified' name A BIG-IP expects most names of resources to be in a fully-qualified @@ -167,16 +163,29 @@ def fq_name(partition, value): value (string): The name that you want to attach a partition to. This value will be returned unchanged if it has a partition attached to it already. + sub_path (string): The sub path element. If defined the sub_path + will be inserted between partition and value. + This will also work on FQ names. Returns: string: The fully qualified name, given the input parameters. """ - if value is not None: + if value is not None and sub_path == '': try: int(value) return '/{0}/{1}'.format(partition, value) except (ValueError, TypeError): if not value.startswith('/'): return '/{0}/{1}'.format(partition, value) + if value is not None and sub_path != '': + try: + int(value) + return '/{0}/{1}/{2}'.format(partition, sub_path, value) + except (ValueError, TypeError): + if value.startswith('/'): + dummy, partition, name = value.split('/') + return '/{0}/{1}/{2}'.format(partition, sub_path, name) + if not value.startswith('/'): + return '/{0}/{1}/{2}'.format(partition, sub_path, value) return value @@ -211,8 +220,8 @@ def run_commands(module, commands, check_rc=True): def flatten_boolean(value): - truthy = list(BOOLEANS_TRUE) + ['enabled'] - falsey = list(BOOLEANS_FALSE) + ['disabled'] + truthy = list(BOOLEANS_TRUE) + ['enabled', 'True'] + falsey = list(BOOLEANS_FALSE) + ['disabled', 'False'] if value is None: return None elif value in truthy: @@ -222,6 +231,7 @@ def flatten_boolean(value): def cleanup_tokens(client=None): + # TODO(Remove this. No longer needed with iControlRestSession destructor) if client is None: return try: @@ -394,12 +404,14 @@ def is_ansible_debug(module): def fail_json(module, ex, client=None): + # TODO(Remove this. No longer needed with iControlRestSession destructor) if is_ansible_debug(module) and client: module.fail_json(msg=str(ex), __f5debug__=client.api.debug_output) module.fail_json(msg=str(ex)) def exit_json(module, results, client=None): + # TODO(Remove this. No longer needed with iControlRestSession destructor) if is_ansible_debug(module) and client: results['__f5debug__'] = client.api.debug_output module.exit_json(**results) @@ -532,7 +544,9 @@ class F5BaseClient(object): def merge_provider_params(self): result = dict() - provider = self.params.get('provider', {}) + provider = self.params.get('provider', None) + if not provider: + provider = {} self.merge_provider_server_param(result, provider) self.merge_provider_server_port_param(result, provider) @@ -540,6 +554,7 @@ class F5BaseClient(object): self.merge_provider_auth_provider_param(result, provider) self.merge_provider_user_param(result, provider) self.merge_provider_password_param(result, provider) + self.merge_proxy_to_param(result, provider) return result @@ -626,6 +641,12 @@ class F5BaseClient(object): else: result['password'] = None + def merge_proxy_to_param(self, result, provider): + if self.validate_params('proxy_to', provider): + result['proxy_to'] = provider['proxy_to'] + else: + result['proxy_to'] = None + class AnsibleF5Parameters(object): def __init__(self, *args, **kwargs): @@ -644,6 +665,16 @@ class AnsibleF5Parameters(object): if params: self._params.update(params) for k, v in iteritems(params): + # Adding this here because ``username`` is a connection parameter + # and in cases where it is also an API parameter, we run the risk + # of overriding the specified parameter with the connection parameter. + # + # Since this is a problem, and since "username" is never a valid + # parameter outside its usage in connection params (where we do not + # use the ApiParameter or ModuleParameters classes) it is safe to + # skip over it if it is provided. + if k == 'password': + continue if self.api_map is not None and k in self.api_map: map_key = self.api_map[k] else: diff --git a/lib/ansible/modules/network/f5/bigiq_regkey_pool.py b/lib/ansible/modules/network/f5/bigiq_regkey_pool.py index 349f637593..ac78b56ea0 100644 --- a/lib/ansible/modules/network/f5/bigiq_regkey_pool.py +++ b/lib/ansible/modules/network/f5/bigiq_regkey_pool.py @@ -72,25 +72,15 @@ description: from ansible.module_utils.basic import AnsibleModule try: - from library.module_utils.network.f5.bigiq import HAS_F5SDK - from library.module_utils.network.f5.bigiq import F5Client + from library.module_utils.network.f5.bigiq 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 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.bigiq import HAS_F5SDK - from ansible.module_utils.network.f5.bigiq import F5Client + from ansible.module_utils.network.f5.bigiq 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 f5_argument_spec - try: - from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError - except ImportError: - HAS_F5SDK = False class Parameters(AnsibleF5Parameters): @@ -135,13 +125,34 @@ class ModuleParameters(Parameters): :return: """ - collection = self.client.api.cm.device.licensing.pool.regkey.licenses_s.get_collection() + collection = self.read_current_from_device() resource = next((x for x in collection if x.name == self._values['name']), None) if resource: return resource.id else: return "none" + def read_current_from_device(self): + uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses".format( + self.client.provider['server'], + self.client.provider['server_port'], + ) + resp = self.client.api.get(uri) + 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) + if 'items' not in response: + return [] + result = [ApiParameters(params=r) for r in response['items']] + return result + class ApiParameters(Parameters): @property @@ -187,6 +198,7 @@ class ModuleManager(object): def __init__(self, *args, **kwargs): self.module = kwargs.get('module', None) self.client = kwargs.get('client', None) + self.client = F5RestClient(**self.module.params) self.want = ModuleParameters(client=self.client, params=self.module.params) self.have = ApiParameters() self.changes = UsableChanges() @@ -228,13 +240,10 @@ class ModuleManager(object): 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() reportable = ReportableChanges(params=self.changes.to_return()) changes = reportable.to_return() @@ -258,10 +267,19 @@ class ModuleManager(object): return self.create() def exists(self): - result = self.client.api.cm.device.licensing.pool.regkey.licenses_s.licenses.exists( - id=self.want.uuid + uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + self.want.uuid, ) - return result + 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 update(self): self.have = self.read_current_from_device() @@ -289,17 +307,41 @@ class ModuleManager(object): def create_on_device(self): params = self.want.api_params() - self.client.api.cm.device.licensing.pool.regkey.licenses_s.licenses.create( - name=self.want.name, - **params + params['name'] = self.want.name + uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/".format( + self.client.provider['server'], + self.client.provider['server_port'], ) + 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 update_on_device(self): params = self.changes.api_params() - resource = self.client.api.cm.device.licensing.pool.regkey.licenses_s.licenses.load( - id=self.want.uuid + uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + self.want.uuid ) - resource.modify(**params) + 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 absent(self): if self.exists(): @@ -307,18 +349,33 @@ class ModuleManager(object): return False def remove_from_device(self): - resource = self.client.api.cm.device.licensing.pool.regkey.licenses_s.licenses.load( - id=self.want.uuid + uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + self.want.uuid ) - if resource: - resource.delete() + resp = self.client.api.delete(uri) + if resp.status == 200: + return True def read_current_from_device(self): - resource = self.client.api.cm.device.licensing.pool.regkey.licenses_s.licenses.load( - id=self.want.uuid + uri = "https://{0}:{1}/mgmt/cm/device/licensing/pool/regkey/licenses/{2}".format( + self.client.provider['server'], + self.client.provider['server_port'], + self.want.uuid ) - result = resource.attrs - return ApiParameters(params=result) + resp = self.client.api.get(uri) + 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) class ArgumentSpec(object): @@ -342,18 +399,15 @@ def main(): module = AnsibleModule( 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") try: - client = F5Client(**module.params) - mm = ModuleManager(module=module, client=client) + mm = ModuleManager(module=module) results = mm.exec_module() module.exit_json(**results) - except F5ModuleError as e: - module.fail_json(msg=str(e)) + except F5ModuleError as ex: + module.fail_json(msg=str(ex)) if __name__ == '__main__': diff --git a/test/units/modules/network/f5/test_bigip_ssl_certificate.py b/test/units/modules/network/f5/test_bigip_ssl_certificate.py index 4cbc479365..48a2a43843 100644 --- a/test/units/modules/network/f5/test_bigip_ssl_certificate.py +++ b/test/units/modules/network/f5/test_bigip_ssl_certificate.py @@ -14,9 +14,6 @@ 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: @@ -24,19 +21,25 @@ try: from library.modules.bigip_ssl_certificate import ApiParameters from library.modules.bigip_ssl_certificate import ModuleParameters from library.modules.bigip_ssl_certificate import ModuleManager - from library.modules.bigip_ssl_certificate import HAS_F5SDK - 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_ssl_certificate import ArgumentSpec from ansible.modules.network.f5.bigip_ssl_certificate import ApiParameters from ansible.modules.network.f5.bigip_ssl_certificate import ModuleParameters from ansible.modules.network.f5.bigip_ssl_certificate import ModuleManager - from ansible.modules.network.f5.bigip_ssl_certificate import HAS_F5SDK - 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") @@ -71,9 +74,6 @@ class TestParameters(unittest.TestCase): name="cert1", partition="Common", state="present", - password='password', - server='localhost', - user='admin' ) p = ModuleParameters(params=args) assert p.name == 'cert1' @@ -83,10 +83,6 @@ class TestParameters(unittest.TestCase): assert '-----END CERTIFICATE-----' in p.content assert p.checksum == '1e55aa57ee166a380e756b5aa4a835c5849490fe' assert p.state == 'present' - assert p.user == 'admin' - assert p.server == 'localhost' - assert p.password == 'password' - assert p.partition == 'Common' def test_module_issuer_cert_key(self): args = dict( diff --git a/test/units/modules/network/f5/test_bigip_ssl_key.py b/test/units/modules/network/f5/test_bigip_ssl_key.py index 336c0a19a1..b8e79f136c 100644 --- a/test/units/modules/network/f5/test_bigip_ssl_key.py +++ b/test/units/modules/network/f5/test_bigip_ssl_key.py @@ -14,27 +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_ssl_key import ArgumentSpec - from library.modules.bigip_ssl_key import Parameters + from library.modules.bigip_ssl_key import ModuleParameters from library.modules.bigip_ssl_key import ModuleManager - from library.modules.bigip_ssl_key import HAS_F5SDK - 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_ssl_key import ArgumentSpec - from ansible.modules.network.f5.bigip_ssl_key import Parameters + from ansible.modules.network.f5.bigip_ssl_key import ModuleParameters from ansible.modules.network.f5.bigip_ssl_key import ModuleManager - from ansible.modules.network.f5.bigip_ssl_key import HAS_F5SDK - 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") @@ -73,17 +76,13 @@ class TestParameters(unittest.TestCase): server='localhost', user='admin' ) - p = Parameters(params=args) + p = ModuleParameters(params=args) assert p.name == 'cert1' assert p.key_filename == 'cert1.key' assert '-----BEGIN RSA PRIVATE KEY-----' in p.content assert '-----END RSA PRIVATE KEY-----' in p.content assert p.key_checksum == '91bdddcf0077e2bb2a0258aae2ae3117be392e83' assert p.state == 'present' - assert p.user == 'admin' - assert p.server == 'localhost' - assert p.password == 'password' - assert p.partition == 'Common' class TestModuleManager(unittest.TestCase): diff --git a/test/units/modules/network/f5/test_bigip_user.py b/test/units/modules/network/f5/test_bigip_user.py index 1be7054986..c4e6ee5b8d 100644 --- a/test/units/modules/network/f5/test_bigip_user.py +++ b/test/units/modules/network/f5/test_bigip_user.py @@ -15,9 +15,6 @@ 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: @@ -26,9 +23,15 @@ try: from library.modules.bigip_user import ArgumentSpec from library.modules.bigip_user import UnparitionedManager from library.modules.bigip_user import PartitionedManager + 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_user import Parameters @@ -36,8 +39,14 @@ except ImportError: from ansible.modules.network.f5.bigip_user import ArgumentSpec from ansible.modules.network.f5.bigip_user import UnparitionedManager from ansible.modules.network.f5.bigip_user import PartitionedManager + 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") @@ -87,14 +96,12 @@ class TestParameters(unittest.TestCase): args = dict( name='someuser', description='Fake Person', - password='testpass', partitionAccess=access, shell='none' ) p = Parameters(params=args) assert p.name == 'someuser' - assert p.password == 'testpass' assert p.full_name == 'Fake Person' assert p.partition_access == access assert p.shell == 'none' diff --git a/test/units/modules/network/f5/test_bigip_virtual_server.py b/test/units/modules/network/f5/test_bigip_virtual_server.py index 1fcc24f17d..414e7cbbea 100644 --- a/test/units/modules/network/f5/test_bigip_virtual_server.py +++ b/test/units/modules/network/f5/test_bigip_virtual_server.py @@ -14,9 +14,6 @@ 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: @@ -24,17 +21,25 @@ try: from library.modules.bigip_virtual_server import ApiParameters from library.modules.bigip_virtual_server import ModuleManager from library.modules.bigip_virtual_server 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_virtual_server import ApiParameters from ansible.modules.network.f5.bigip_virtual_server import ModuleParameters from ansible.modules.network.f5.bigip_virtual_server import ModuleManager from ansible.modules.network.f5.bigip_virtual_server import ArgumentSpec - 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") @@ -183,9 +188,6 @@ class TestParameters(unittest.TestCase): assert p.name == 'my-virtual-server' assert p.partition == 'Common' assert p.port == 443 - assert p.server == 'localhost' - assert p.user == 'admin' - assert p.password == 'secret' assert p.destination == '/Common/10.10.10.10:443' assert p.pool == '/Common/my-pool' assert p.snat == {'type': 'automap'} @@ -220,9 +222,6 @@ class TestParameters(unittest.TestCase): assert p.name == 'my-virtual-server' assert p.partition == 'Common' assert p.port == 443 - assert p.server == 'localhost' - assert p.user == 'admin' - assert p.password == 'secret' assert p.destination == '/Common/10.10.10.10:443' assert p.pool == '/Common/my-pool' assert p.snat == {'type': 'automap'} diff --git a/test/units/modules/network/f5/test_bigiq_regkey_pool.py b/test/units/modules/network/f5/test_bigiq_regkey_pool.py index 366b3cb4ac..ec4f33da02 100644 --- a/test/units/modules/network/f5/test_bigiq_regkey_pool.py +++ b/test/units/modules/network/f5/test_bigiq_regkey_pool.py @@ -8,16 +8,12 @@ __metaclass__ = type import os import json -import pytest import sys 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: @@ -25,17 +21,25 @@ try: from library.modules.bigiq_regkey_pool import ApiParameters from library.modules.bigiq_regkey_pool import ModuleManager from library.modules.bigiq_regkey_pool 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.bigiq_regkey_pool import ModuleParameters from ansible.modules.network.f5.bigiq_regkey_pool import ApiParameters from ansible.modules.network.f5.bigiq_regkey_pool import ModuleManager from ansible.modules.network.f5.bigiq_regkey_pool import ArgumentSpec - 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")