diff --git a/lib/ansible/modules/network/f5/_bigip_security_address_list.py b/lib/ansible/modules/network/f5/_bigip_security_address_list.py new file mode 120000 index 00000000000..6bef19600f5 --- /dev/null +++ b/lib/ansible/modules/network/f5/_bigip_security_address_list.py @@ -0,0 +1 @@ +bigip_firewall_address_list.py \ No newline at end of file diff --git a/lib/ansible/modules/network/f5/bigip_security_address_list.py b/lib/ansible/modules/network/f5/bigip_firewall_address_list.py similarity index 94% rename from lib/ansible/modules/network/f5/bigip_security_address_list.py rename to lib/ansible/modules/network/f5/bigip_firewall_address_list.py index 123a554a869..d157a735b48 100644 --- a/lib/ansible/modules/network/f5/bigip_security_address_list.py +++ b/lib/ansible/modules/network/f5/bigip_firewall_address_list.py @@ -14,7 +14,7 @@ ANSIBLE_METADATA = {'metadata_version': '1.1', DOCUMENTATION = r''' --- -module: bigip_security_address_list +module: bigip_firewall_address_list short_description: Manage address lists on BIG-IP AFM description: - Manages the AFM address lists on a BIG-IP. This module can be used to add @@ -92,7 +92,7 @@ author: EXAMPLES = r''' - name: Create an address list - bigip_security_address_list: + bigip_firewall_address_list: name: foo addresses: - 3.3.3.3 @@ -161,6 +161,11 @@ try: 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 + from library.module_utils.compat.ipaddress import ip_address + from library.module_utils.compat.ipaddress import ip_interface + from library.module_utils.network.f5.ipaddress import is_valid_ip + from library.module_utils.network.f5.ipaddress import is_valid_ip_interface + from library.module_utils.network.f5.ipaddress import is_valid_ip_network try: from library.module_utils.network.f5.common import iControlUnexpectedHTTPError except ImportError: @@ -173,17 +178,16 @@ except ImportError: 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 + from ansible.module_utils.compat.ipaddress import ip_address + from ansible.module_utils.compat.ipaddress import ip_interface + from ansible.module_utils.network.f5.ipaddress import is_valid_ip + from ansible.module_utils.network.f5.ipaddress import is_valid_ip_interface + from ansible.module_utils.network.f5.ipaddress import is_valid_ip_network try: from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError except ImportError: HAS_F5SDK = False -try: - import netaddr - HAS_NETADDR = True -except ImportError: - HAS_NETADDR = False - class Parameters(AnsibleF5Parameters): api_map = { @@ -192,17 +196,29 @@ class Parameters(AnsibleF5Parameters): } api_attributes = [ - 'addressLists', 'addresses', 'description', 'fqdns', 'geo' + 'addressLists', + 'addresses', + 'description', + 'fqdns', + 'geo', ] returnables = [ - 'addresses', 'address_ranges', 'address_lists', 'description', - 'fqdns', 'geo_locations' + 'addresses', + 'address_ranges', + 'address_lists', + 'description', + 'fqdns', + 'geo_locations', ] updatables = [ - 'addresses', 'address_ranges', 'address_lists', 'description', - 'fqdns', 'geo_locations' + 'addresses', + 'address_ranges', + 'address_lists', + 'description', + 'fqdns', + 'geo_locations', ] def to_return(self): @@ -524,21 +540,16 @@ class ModuleParameters(Parameters): def addresses(self): if self._values['addresses'] is None: return None + result = [] for x in self._values['addresses']: - try: - netaddr.IPAddress(x) - except netaddr.core.AddrFormatError: + if is_valid_ip(x): + result.append(str(ip_address(u'{0}'.format(x)))) + elif is_valid_ip_interface(x): + result.append(str(ip_interface(u'{0}'.format(x)))) + else: raise F5ModuleError( "Address {0} must be either an IPv4 or IPv6 address or network.".format(x) ) - except ValueError: - try: - netaddr.IPNetwork(x) - except netaddr.core.AddrFormatError: - raise F5ModuleError( - "Address {0} must be either an IPv4 or IPv6 address or network.".format(x) - ) - result = [str(x) for x in self._values['addresses']] result = sorted(result) return result @@ -552,13 +563,13 @@ class ModuleParameters(Parameters): start = start.strip() stop = stop.strip() - start = netaddr.IPAddress(start) - stop = netaddr.IPAddress(stop) + start = ip_address(u'{0}'.format(start)) + stop = ip_address(u'{0}'.format(stop)) if start.version != stop.version: raise F5ModuleError( "When specifying a range, IP addresses must be of the same type; IPv4 or IPv6." ) - if start > stop: + if int(start) > int(stop): stop, start = start, stop item = '{0}-{1}'.format(str(start), str(stop)) result.append(item) @@ -638,13 +649,13 @@ class ReportableChanges(Changes): start = start.strip() stop = stop.strip() - start = netaddr.IPAddress(start) - stop = netaddr.IPAddress(stop) + start = ip_address(u'{0}'.format(start)) + stop = ip_address(u'{0}'.format(stop)) if start.version != stop.version: raise F5ModuleError( "When specifying a range, IP addresses must be of the same type; IPv4 or IPv6." ) - if start > stop: + if int(start) > int(stop): stop, start = start, stop item = '{0}-{1}'.format(str(start), str(stop)) result.append(item) @@ -924,8 +935,6 @@ def main(): ) if not HAS_F5SDK: module.fail_json(msg="The python f5-sdk module is required") - if not HAS_NETADDR: - module.fail_json(msg="The python netaddr module is required") try: client = F5Client(**module.params) diff --git a/test/units/modules/network/f5/test_bigip_firewall_address_list.py b/test/units/modules/network/f5/test_bigip_firewall_address_list.py new file mode 100644 index 00000000000..1a7ceb938b5 --- /dev/null +++ b/test/units/modules/network/f5/test_bigip_firewall_address_list.py @@ -0,0 +1,138 @@ +# -*- 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 + +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 ansible.compat.tests import unittest +from ansible.compat.tests.mock import Mock +from ansible.compat.tests.mock import patch +from ansible.module_utils.basic import AnsibleModule + +try: + from library.modules.bigip_firewall_address_list import ApiParameters + from library.modules.bigip_firewall_address_list import ModuleParameters + from library.modules.bigip_firewall_address_list import ModuleManager + from library.modules.bigip_firewall_address_list 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 +except ImportError: + try: + from ansible.modules.network.f5.bigip_firewall_address_list import ApiParameters + from ansible.modules.network.f5.bigip_firewall_address_list import ModuleParameters + from ansible.modules.network.f5.bigip_firewall_address_list import ModuleManager + from ansible.modules.network.f5.bigip_firewall_address_list import ArgumentSpec + from ansible.module_utils.network.f5.common import F5ModuleError + from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError + from units.modules.utils import set_module_args + except ImportError: + raise SkipTest("F5 Ansible modules require the f5-sdk Python library") + +fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') +fixture_data = {} + + +def load_fixture(name): + path = os.path.join(fixture_path, name) + + if path in fixture_data: + return fixture_data[path] + + with open(path) as f: + data = f.read() + + try: + data = json.loads(data) + except Exception: + pass + + fixture_data[path] = data + return data + + +class TestParameters(unittest.TestCase): + def test_module_parameters(self): + args = dict( + name='foo', + description='this is a description', + addresses=['1.1.1.1', '2.2.2.2'], + address_ranges=['3.3.3.3-4.4.4.4', '5.5.5.5-6.6.6.6'], + address_lists=['/Common/foo', 'foo'] + ) + + p = ModuleParameters(params=args) + assert p.name == 'foo' + assert p.description == 'this is a description' + assert len(p.addresses) == 2 + assert len(p.address_ranges) == 2 + assert len(p.address_lists) == 2 + + def test_api_parameters(self): + args = load_fixture('load_security_address_list_1.json') + + p = ApiParameters(params=args) + assert len(p.addresses) == 2 + assert len(p.address_ranges) == 2 + assert len(p.address_lists) == 1 + assert len(p.fqdns) == 1 + assert len(p.geo_locations) == 5 + assert sorted(p.addresses) == ['1.1.1.1', '2700:bc00:1f10:101::6'] + assert sorted(p.address_ranges) == ['2.2.2.2-3.3.3.3', '5.5.5.5-6.6.6.6'] + assert p.address_lists[0] == '/Common/foo' + + +class TestManager(unittest.TestCase): + + def setUp(self): + self.spec = ArgumentSpec() + + def test_create(self, *args): + set_module_args(dict( + name='foo', + description='this is a description', + addresses=['1.1.1.1', '2.2.2.2'], + address_ranges=['3.3.3.3-4.4.4.4', '5.5.5.5-6.6.6.6'], + address_lists=['/Common/foo', 'foo'], + geo_locations=[ + dict(country='US', region='Los Angeles'), + dict(country='China'), + dict(country='EU') + ], + fqdns=['google.com', 'mit.edu'], + password='password', + server='localhost', + user='admin' + )) + + module = AnsibleModule( + argument_spec=self.spec.argument_spec, + supports_check_mode=self.spec.supports_check_mode + ) + mm = ModuleManager(module=module) + + # Override methods to force specific logic in the module to happen + mm.exists = Mock(return_value=False) + mm.create_on_device = Mock(return_value=True) + + results = mm.exec_module() + + assert results['changed'] is True + assert 'addresses' in results + assert 'address_lists' in results + assert 'address_ranges' in results + assert len(results['addresses']) == 2 + assert len(results['address_ranges']) == 2 + assert len(results['address_lists']) == 2 + assert results['description'] == 'this is a description'