Adds the AFM address list module (#34814)

This module can be used to manage address lists on a BIG-IP running
AFM.
pull/34817/head
Tim Rupp 7 years ago committed by GitHub
parent 9314607c38
commit 18d33eeb89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,939 @@
#!/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_security_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
and remove address list entries.
version_added: "2.5"
options:
name:
description:
- Specifies the name of the address list.
required: True
partition:
description:
- Device partition to manage resources on.
default: Common
description:
description:
- Description of the address list
geo_locations:
description:
- List of geolocations specified by their C(country) and C(region).
suboptions:
country:
description:
- The country name, or code, of the geolocation to use.
- In addition to the country full names, you may also specify their abbreviated
form, such as C(US) instead of C(United States).
- Valid country codes can be found here https://countrycode.org/.
required: true
choices:
- Any valid 2 character ISO country code.
- Any valid country name.
region:
description:
- Region name of the country to use.
address_ranges:
description:
- A list of address ranges where the range starts with a port number, is followed
by a dash (-) and then a second number.
- If the first address is greater than the second number, the numbers will be
reversed so-as to be properly formatted. ie, C(2.2.2.2-1.1.1). would become
C(1.1.1.1-2.2.2.2).
address_lists:
description:
- Simple list of existing address lists to add to this list. Address lists can be
specified in either their fully qualified name (/Common/foo) or their short
name (foo). If a short name is used, the C(partition) argument will automatically
be prepended to the short name.
fqdns:
description:
- A list of fully qualified domain names (FQDNs).
- An FQDN has at least one decimal point in it, separating the host from the domain.
- To add FQDNs to a list requires that a global FQDN resolver be configured.
At the moment, this must either be done via C(bigip_command), or, in the GUI
of BIG-IP. If using C(bigip_command), this can be done with C(tmsh modify security
firewall global-fqdn-policy FOO) where C(FOO) is a DNS resolver configured
at C(tmsh create net dns-resolver FOO).
state:
description:
- When C(present), ensures that the address list and entries exists.
- When C(absent), ensures the address list is removed.
default: present
choices:
- present
- absent
extends_documentation_fragment: f5
author:
- Tim Rupp (@caphrim007)
'''
EXAMPLES = r'''
- name: Create an address list
bigip_security_address_list:
name: foo
addresses:
- 3.3.3.3
- 4.4.4.4
- 5.5.5.5
password: secret
server: lb.mydomain.com
state: present
user: admin
delegate_to: localhost
'''
RETURN = r'''
description:
description: The new description of the address list.
returned: changed
type: string
sample: My address list
addresses:
description: The new list of addresses applied to the address list.
returned: changed
type: list
sample: [1.1.1.1, 2.2.2.2]
address_ranges:
description: The new list of address ranges applied to the address list.
returned: changed
type: list
sample: [1.1.1.1-2.2.2.2, 3.3.3.3-4.4.4.4]
address_lists:
description: The new list of address list names applied to the address list.
returned: changed
type: list
sample: [/Common/list1, /Common/list2]
fqdns:
description: The new list of FQDN names applied to the address list.
returned: changed
type: list
sample: [google.com, mit.edu]
geo_locations:
description: The new list of geo locations applied to the address list.
returned: changed
type: complex
contains:
country:
description: Country of the geo location.
returned: changed
type: string
sample: US
region:
description: Region of the geo location.
returned: changed
type: string
sample: California
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
HAS_DEVEL_IMPORTS = False
try:
# Sideband repository used for dev
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 fqdn_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
HAS_DEVEL_IMPORTS = True
except ImportError:
# Upstream Ansible
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 fqdn_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
try:
import netaddr
HAS_NETADDR = True
except ImportError:
HAS_NETADDR = False
class Parameters(AnsibleF5Parameters):
api_map = {
'addressLists': 'address_lists',
'geo': 'geo_locations'
}
api_attributes = [
'addressLists', 'addresses', 'description', 'fqdns', 'geo'
]
returnables = [
'addresses', 'address_ranges', 'address_lists', 'description',
'fqdns', 'geo_locations'
]
updatables = [
'addresses', 'address_ranges', 'address_lists', 'description',
'fqdns', 'geo_locations'
]
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 _fqdn_name(self, value):
if value is not None and not value.startswith('/'):
return '/{0}/{1}'.format(self.partition, value)
return value
class ApiParameters(Parameters):
@property
def address_ranges(self):
if self._values['addresses'] is None:
return None
result = []
for address_range in self._values['addresses']:
if '-' not in address_range['name']:
continue
result.append(address_range['name'].strip())
result = sorted(result)
return result
@property
def address_lists(self):
if self._values['address_lists'] is None:
return None
result = []
for x in self._values['address_lists']:
item = '/{0}/{1}'.format(x['partition'], x['name'])
result.append(item)
result = sorted(result)
return result
@property
def addresses(self):
if self._values['addresses'] is None:
return None
result = [x['name'] for x in self._values['addresses'] if '-' not in x['name']]
result = sorted(result)
return result
@property
def fqdns(self):
if self._values['fqdns'] is None:
return None
result = [str(x['name']) for x in self._values['fqdns']]
result = sorted(result)
return result
@property
def geo_locations(self):
if self._values['geo_locations'] is None:
return None
result = [str(x['name']) for x in self._values['geo_locations']]
result = sorted(result)
return result
class ModuleParameters(Parameters):
def __init__(self, params=None):
super(ModuleParameters, self).__init__(params=params)
self.country_iso_map = {
'Afghanistan': 'AF',
'Albania': 'AL',
'Algeria': 'DZ',
'American Samoa': 'AS',
'Andorra': 'AD',
'Angola': 'AO',
'Anguilla': 'AI',
'Antarctica': 'AQ',
'Antigua and Barbuda': 'AG',
'Argentina': 'AR',
'Armenia': 'AM',
'Aruba': 'AW',
'Australia': 'AU',
'Austria': 'AT',
'Azerbaijan': 'AZ',
'Bahamas': 'BS',
'Bahrain': 'BH',
'Bangladesh': 'BD',
'Barbados': 'BB',
'Belarus': 'BY',
'Belgium': 'BE',
'Belize': 'BZ',
'Benin': 'BJ',
'Bermuda': 'BM',
'Bhutan': 'BT',
'Bolivia': 'BO',
'Bosnia and Herzegovina': 'BA',
'Botswana': 'BW',
'Brazil': 'BR',
'Brunei': 'BN',
'Bulgaria': 'BG',
'Burkina Faso': 'BF',
'Burundi': 'BI',
'Cameroon': 'CM',
'Canada': 'CA',
'Cape Verde': 'CV',
'Central African Republic': 'CF',
'Chile': 'CL',
'China': 'CN',
'Christmas Island': 'CX',
'Cocos Islands': 'CC',
'Colombia': 'CO',
'Cook Islands': 'CK',
'Costa Rica': 'CR',
'Cuba': 'CU',
'Curacao': 'CW',
'Cyprus': 'CY',
'Czech Republic': 'CZ',
'Democratic Republic of the Congo': 'CD',
'Denmark': 'DK',
'Djibouti': 'DJ',
'Dominica': 'DM',
'Dominican Republic': 'DO',
'Ecuador': 'EC',
'Egypt': 'EG',
'Eritrea': 'ER',
'Estonia': 'EE',
'Ethiopia': 'ET',
'Falkland Islands': 'FK',
'Faroe Islands': 'FO',
'Fiji': 'FJ',
'Finland': 'FI',
'France': 'FR',
'French Polynesia': 'PF',
'Gabon': 'GA',
'Gambia': 'GM',
'Georgia': 'GE',
'Germany': 'DE',
'Ghana': 'GH',
'Gilbraltar': 'GI',
'Greece': 'GR',
'Greenland': 'GL',
'Grenada': 'GD',
'Guam': 'GU',
'Guatemala': 'GT',
'Guernsey': 'GG',
'Guinea': 'GN',
'Guinea-Bissau': 'GW',
'Guyana': 'GY',
'Haiti': 'HT',
'Honduras': 'HN',
'Hong Kong': 'HK',
'Hungary': 'HU',
'Iceland': 'IS',
'India': 'IN',
'Indonesia': 'ID',
'Iran': 'IR',
'Iraq': 'IQ',
'Ireland': 'IE',
'Isle of Man': 'IM',
'Israel': 'IL',
'Italy': 'IT',
'Ivory Coast': 'CI',
'Jamaica': 'JM',
'Japan': 'JP',
'Jersey': 'JE',
'Jordan': 'JO',
'Kazakhstan': 'KZ',
'Laos': 'LA',
'Latvia': 'LV',
'Lebanon': 'LB',
'Lesotho': 'LS',
'Liberia': 'LR',
'Libya': 'LY',
'Liechtenstein': 'LI',
'Lithuania': 'LT',
'Luxembourg': 'LU',
'Macau': 'MO',
'Macedonia': 'MK',
'Madagascar': 'MG',
'Malawi': 'MW',
'Malaysia': 'MY',
'Maldives': 'MV',
'Mali': 'ML',
'Malta': 'MT',
'Marshall Islands': 'MH',
'Mauritania': 'MR',
'Mauritius': 'MU',
'Mayotte': 'YT',
'Mexico': 'MX',
'Micronesia': 'FM',
'Moldova': 'MD',
'Monaco': 'MC',
'Mongolia': 'MN',
'Montenegro': 'ME',
'Montserrat': 'MS',
'Morocco': 'MA',
'Mozambique': 'MZ',
'Myanmar': 'MM',
'Namibia': 'NA',
'Nauru': 'NR',
'Nepal': 'NP',
'Netherlands': 'NL',
'Netherlands Antilles': 'AN',
'New Caledonia': 'NC',
'New Zealand': 'NZ',
'Nicaragua': 'NI',
'Niger': 'NE',
'Nigeria': 'NG',
'Niue': 'NU',
'North Korea': 'KP',
'Northern Mariana Islands': 'MP',
'Norway': 'NO',
'Oman': 'OM',
'Pakistan': 'PK',
'Palau': 'PW',
'Palestine': 'PS',
'Panama': 'PA',
'Papua New Guinea': 'PG',
'Paraguay': 'PY',
'Peru': 'PE',
'Philippines': 'PH',
'Pitcairn': 'PN',
'Poland': 'PL',
'Portugal': 'PT',
'Puerto Rico': 'PR',
'Qatar': 'QA',
'Republic of the Congo': 'CG',
'Reunion': 'RE',
'Romania': 'RO',
'Russia': 'RU',
'Rwanda': 'RW',
'Saint Barthelemy': 'BL',
'Saint Helena': 'SH',
'Saint Kitts and Nevis': 'KN',
'Saint Lucia': 'LC',
'Saint Martin': 'MF',
'Saint Pierre and Miquelon': 'PM',
'Saint Vincent and the Grenadines': 'VC',
'Samoa': 'WS',
'San Marino': 'SM',
'Sao Tome and Principe': 'ST',
'Saudi Arabia': 'SA',
'Senegal': 'SN',
'Serbia': 'RS',
'Seychelles': 'SC',
'Sierra Leone': 'SL',
'Singapore': 'SG',
'Sint Maarten': 'SX',
'Slovakia': 'SK',
'Slovenia': 'SI',
'Solomon Islands': 'SB',
'Somalia': 'SO',
'South Africa': 'ZA',
'South Korea': 'KR',
'South Sudan': 'SS',
'Spain': 'ES',
'Sri Lanka': 'LK',
'Sudan': 'SD',
'Suriname': 'SR',
'Svalbard and Jan Mayen': 'SJ',
'Swaziland': 'SZ',
'Sweden': 'SE',
'Switzerland': 'CH',
'Syria': 'SY',
'Taiwan': 'TW',
'Tajikstan': 'TJ',
'Tanzania': 'TZ',
'Thailand': 'TH',
'Togo': 'TG',
'Tokelau': 'TK',
'Tonga': 'TO',
'Trinidad and Tobago': 'TT',
'Tunisia': 'TN',
'Turkey': 'TR',
'Turkmenistan': 'TM',
'Turks and Caicos Islands': 'TC',
'Tuvalu': 'TV',
'U.S. Virgin Islands': 'VI',
'Uganda': 'UG',
'Ukraine': 'UA',
'United Arab Emirates': 'AE',
'United Kingdom': 'GB',
'United States': 'US',
'Uruguay': 'UY',
'Uzbekistan': 'UZ',
'Vanuatu': 'VU',
'Vatican': 'VA',
'Venezuela': 'VE',
'Vietnam': 'VN',
'Wallis and Futuna': 'WF',
'Western Sahara': 'EH',
'Yemen': 'YE',
'Zambia': 'ZM',
'Zimbabwe': 'ZW'
}
self.choices_iso_codes = self.country_iso_map.values()
def is_valid_hostname(self, host):
"""Reasonable attempt at validating a hostname
Compiled from various paragraphs outlined here
https://tools.ietf.org/html/rfc3696#section-2
https://tools.ietf.org/html/rfc1123
Notably,
* Host software MUST handle host names of up to 63 characters and
SHOULD handle host names of up to 255 characters.
* The "LDH rule", after the characters that it permits. (letters, digits, hyphen)
* If the hyphen is used, it is not permitted to appear at
either the beginning or end of a label
:param host:
:return:
"""
if len(host) > 255:
return False
host = host.rstrip(".")
allowed = re.compile(r'(?!-)[A-Z0-9-]{1,63}(?<!-)$', re.IGNORECASE)
return all(allowed.match(x) for x in host.split("."))
@property
def addresses(self):
if self._values['addresses'] is None:
return None
for x in self._values['addresses']:
try:
netaddr.IPAddress(x)
except netaddr.core.AddrFormatError:
raise F5ModuleError(
"Address {0} must be either an IPv4 or IPv6 address".format(x)
)
result = [str(x) for x in self._values['addresses']]
result = sorted(result)
return result
@property
def address_ranges(self):
if self._values['address_ranges'] is None:
return None
result = []
for address_range in self._values['address_ranges']:
start, stop = address_range.split('-')
start = start.strip()
stop = stop.strip()
start = netaddr.IPAddress(start)
stop = netaddr.IPAddress(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:
stop, start = start, stop
item = '{0}-{1}'.format(str(start), str(stop))
result.append(item)
result = sorted(result)
return result
@property
def address_lists(self):
if self._values['address_lists'] is None:
return None
result = []
for x in self._values['address_lists']:
item = self._fqdn_name(x)
result.append(item)
result = sorted(result)
return result
@property
def fqdns(self):
if self._values['fqdns'] is None:
return None
result = []
for x in self._values['fqdns']:
if self.is_valid_hostname(x):
result.append(x)
else:
raise F5ModuleError(
"The hostname '{0}' looks invalid.".format(x)
)
result = sorted(result)
return result
@property
def geo_locations(self):
if self._values['geo_locations'] is None:
return None
result = []
for x in self._values['geo_locations']:
if x['region'] is not None and x['region'].strip() != '':
tmp = '{0}:{1}'.format(x['country'], x['region'])
else:
tmp = x['country']
result.append(tmp)
result = sorted(result)
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 ReportableChanges(Changes):
@property
def addresses(self):
result = []
for item in self._values['addresses']:
if '-' in item['name']:
continue
result.append(item['name'])
return result
@property
def address_ranges(self):
result = []
for item in self._values['addresses']:
if '-' not in item['name']:
continue
start, stop = item['name'].split('-')
start = start.strip()
stop = stop.strip()
start = netaddr.IPAddress(start)
stop = netaddr.IPAddress(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:
stop, start = start, stop
item = '{0}-{1}'.format(str(start), str(stop))
result.append(item)
result = sorted(result)
return result
@property
def address_lists(self):
result = []
for x in self._values['address_lists']:
item = '/{0}/{1}'.format(x['partition'], x['name'])
result.append(item)
result = sorted(result)
return result
class UsableChanges(Changes):
@property
def addresses(self):
if self._values['addresses'] is None and self._values['address_ranges'] is None:
return None
result = []
if self._values['addresses']:
result += [dict(name=str(x)) for x in self._values['addresses']]
if self._values['address_ranges']:
result += [dict(name=str(x)) for x in self._values['address_ranges']]
return result
@property
def address_lists(self):
if self._values['address_lists'] is None:
return None
result = []
for x in self._values['address_lists']:
partition, name = x.split('/')[1:]
result.append(dict(
name=name,
partition=partition
))
return result
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 addresses(self):
if self.want.addresses is None:
return None
elif self.have.addresses is None:
return self.want.addresses
if sorted(self.want.addresses) != sorted(self.have.addresses):
return self.want.addresses
@property
def address_lists(self):
if self.want.address_lists is None:
return None
elif self.have.address_lists is None:
return self.want.address_lists
if sorted(self.want.address_lists) != sorted(self.have.address_lists):
return self.want.address_lists
@property
def address_ranges(self):
if self.want.address_ranges is None:
return None
elif self.have.address_ranges is None:
return self.want.address_ranges
if sorted(self.want.address_ranges) != sorted(self.have.address_ranges):
return self.want.address_ranges
@property
def fqdns(self):
if self.want.fqdns is None:
return None
elif self.have.fqdns is None:
return self.want.fqdns
if sorted(self.want.fqdns) != sorted(self.have.fqdns):
return self.want.fqdns
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 _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.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.security.firewall.address_lists.address_list.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.have = ApiParameters()
self._update_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.security.firewall.address_lists.address_list.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.security.firewall.address_lists.address_list.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.security.firewall.address_lists.address_list.load(
name=self.want.name,
partition=self.want.partition
)
if resource:
resource.delete()
def read_current_from_device(self):
resource = self.client.api.tm.security.firewall.address_lists.address_list.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(
description=dict(),
name=dict(required=True),
addresses=dict(type='list'),
address_ranges=dict(type='list'),
address_lists=dict(type='list'),
geo_locations=dict(
type='list',
elements='dict',
options=dict(
country=dict(
required=True,
),
region=dict()
)
),
fqdns=dict(type='list'),
partition=dict(
default='Common',
fallback=(env_fallback, ['F5_PARTITION'])
),
state=dict(
default='present',
choices=['present', 'absent']
)
)
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")
if not HAS_NETADDR:
module.fail_json(msg="The python netaddr 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()

@ -0,0 +1,53 @@
{
"kind": "tm:security:firewall:address-list:address-liststate",
"name": "bar",
"partition": "Common",
"fullPath": "/Common/bar",
"generation": 135,
"selfLink": "https://localhost/mgmt/tm/security/firewall/address-list/~Common~bar?ver=12.1.2",
"addressLists": [
{
"name": "foo",
"partition": "Common",
"nameReference": {
"link": "https://localhost/mgmt/tm/security/firewall/address-list/~Common~foo?ver=12.1.2"
}
}
],
"addresses": [
{
"name": "1.1.1.1"
},
{
"name": "2.2.2.2-3.3.3.3"
},
{
"name": "5.5.5.5-6.6.6.6"
},
{
"name": "2700:bc00:1f10:101::6"
}
],
"fqdns": [
{
"name": "google.com"
}
],
"geo": [
{
"name": "AF:Baghlan"
},
{
"name": "AF:Helmand"
},
{
"name": "BS"
},
{
"name": "EU"
},
{
"name": "GE:Marneulis Raioni"
}
]
}

@ -0,0 +1,139 @@
# -*- 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.bigip_security_address_list import ApiParameters
from library.bigip_security_address_list import ModuleParameters
from library.bigip_security_address_list import ModuleManager
from library.bigip_security_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_security_address_list import Parameters
from ansible.modules.network.f5.bigip_security_address_list import ApiParameters
from ansible.modules.network.f5.bigip_security_address_list import ModuleParameters
from ansible.modules.network.f5.bigip_security_address_list import ModuleManager
from ansible.modules.network.f5.bigip_security_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'
Loading…
Cancel
Save