1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-04-13 23:45:04 +00:00

Initial commit

This commit is contained in:
Ansible Core Team 2020-03-09 09:11:07 +00:00
commit aebc1b03fd
4861 changed files with 812621 additions and 0 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
bigip_device_info.py

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,667 @@
#!/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': 'certified'}
DOCUMENTATION = r'''
---
module: bigip_device_traffic_group
short_description: Manages traffic groups on BIG-IP
description:
- Supports managing traffic groups and their attributes on a BIG-IP.
options:
name:
description:
- The name of the traffic group.
type: str
required: True
mac_address:
description:
- Specifies the floating Media Access Control (MAC) address associated with the floating IP addresses
defined for a traffic group.
- Primarily, a MAC masquerade address minimizes ARP communications or dropped packets as a result of failover.
- A MAC masquerade address ensures that any traffic destined for a specific traffic group reaches an available
device after failover, which happens because along with the traffic group, the MAC masquerade address floats
to the available device.
- Without a MAC masquerade address, the sending host must learn the MAC address for a newly-active device,
either by sending an ARP request or by relying on the gratuitous ARP from the newly-active device.
- To unset the MAC address, specify an empty value (C("")) to this parameter.
type: str
ha_order:
description:
- Specifies order in which you would like to assign devices for failover.
- If you configure this setting, you must configure the setting on every traffic group in the device group.
- The values should be device names of the devices that belong to the failover group configured beforehand.
- The order in which the devices are placed as arguments to this parameter, determines their HA order
on the device, in other words changing the order of the same elements will cause a change on the unit.
- To disable an HA order failover method , specify an empty string value (C("")) to this parameter.
- Disabling HA order will revert the device back to using Load Aware method as it is the default,
unless C(ha_group) setting is also configured.
- Device names will be prepended by a partition by the module, so you can provide either the full path format
name C(/Common/bigip1) or just the name string C(bigip1).
type: list
ha_group:
description:
- Specifies a configured C(HA group) to be associated with the traffic group.
- Once you create an HA group on a device and associate the HA group with a traffic group,
you must create an HA group and associate it with that same traffic group on every device in the device group.
- To disable an HA group failover method , specify an empty string value (C("")) to this parameter.
- Disabling HA group will revert the device back to using C(Load Aware) method as it is the default,
unless C(ha_order) setting is also configured.
- The C(auto_failback) and C(auto_failback_time) are not compatible with C(ha_group).
type: str
ha_load_factor:
description:
- The value of the load the traffic-group presents the system relative to other traffic groups.
- This parameter only takes effect when C(Load Aware) failover method is in use.
- The correct value range is C(1 - 1000) inclusive.
type: int
auto_failback:
description:
- Specifies whether the traffic group fails back to the initial device specified in C(ha_order).
type: bool
auto_failback_time:
description:
- Specifies the number of seconds the system delays before failing back to the initial device
specified in C(ha_order).
- The correct value range is C(0 - 300) inclusive.
type: int
partition:
description:
- Device partition to manage resources on.
type: str
default: Common
state:
description:
- When C(present), ensures that the traffic group exists.
- When C(absent), ensures the traffic group is removed.
type: str
choices:
- present
- absent
default: present
extends_documentation_fragment:
- f5networks.f5_modules.f5
author:
- Tim Rupp (@caphrim007)
- Wojciech Wypior (@wojtek0806)
'''
EXAMPLES = r'''
- name: Create a traffic group
bigip_device_traffic_group:
name: foo1
state: present
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
- name: Create a traffic group with ha_group failover
bigip_device_traffic_group:
name: foo2
state: present
ha_group: foo_HA_grp
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
- name: Create a traffic group with ha_order failover
bigip_device_traffic_group:
name: foo3
state: present
ha_order:
- /Common/bigip1.lab.local
- /Common/bigip2.lab.local
auto_failback: yes
auto_failback_time: 40
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
- name: Change traffic group ha_order to ha_group
bigip_device_traffic_group:
name: foo3
state: present
ha_group: foo_HA_grp
ha_order: ""
auto_failback: no
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
- name: Remove traffic group
bigip_device_traffic_group:
name: foo
state: absent
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
'''
RETURN = r'''
mac_address:
description: The MAC masquerade address
returned: changed
type: str
sample: "02:01:d7:93:35:08"
ha_group:
description: The configured HA group associated with traffic group
returned: changed
type: str
sample: foo_HA_grp
ha_order:
description: Specifies the order in which the devices will failover
returned: changed
type: list
sample: ['/Common/bigip1', '/Common/bigip2']
ha_load_factor:
description: The value of the load the traffic-group presents the system relative to other traffic groups
returned: changed
type: int
sample: 20
auto_failback:
description: Specifies whether the traffic group fails back to the initial device specified in ha_order
returned: changed
type: bool
sample: yes
auto_failback_time:
description: Specifies the number of seconds the system delays before failing back
returned: changed
type: int
sample: 60
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
try:
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 f5_argument_spec
from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import transform_name
from library.module_utils.network.f5.common import flatten_boolean
except ImportError:
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.bigip import F5RestClient
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import F5ModuleError
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import AnsibleF5Parameters
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import f5_argument_spec
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import fq_name
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import transform_name
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import flatten_boolean
class Parameters(AnsibleF5Parameters):
api_map = {
'mac': 'mac_address',
'haGroup': 'ha_group',
'haOrder': 'ha_order',
'haLoadFactor': 'ha_load_factor',
'autoFailbackTime': 'auto_failback_time',
'autoFailbackEnabled': 'auto_failback',
}
api_attributes = [
'mac',
'haGroup',
'haOrder',
'haLoadFactor',
'autoFailbackTime',
'autoFailbackEnabled',
]
returnables = [
'mac_address',
'ha_group',
'ha_order',
'ha_load_factor',
'auto_failback_time',
'auto_failback',
]
updatables = [
'mac_address',
'ha_group',
'ha_order',
'ha_load_factor',
'auto_failback_time',
'auto_failback',
]
class ApiParameters(Parameters):
pass
class ModuleParameters(Parameters):
@property
def mac_address(self):
if self._values['mac_address'] is None:
return None
if self._values['mac_address'] == '':
return 'none'
return self._values['mac_address']
@property
def ha_group(self):
if self._values['ha_group'] is None:
return None
if self._values['ha_group'] == '':
return 'none'
if self.auto_failback == 'true':
raise F5ModuleError(
"The auto_failback cannot be enabled when ha_group is specified."
)
return self._values['ha_group']
@property
def ha_load_factor(self):
if self._values['ha_load_factor'] is None:
return None
value = self._values['ha_load_factor']
if value < 1 or value > 1000:
raise F5ModuleError(
"Invalid ha_load_factor value, correct range is 1 - 1000, specified value: {0}.".format(value))
return value
@property
def auto_failback_time(self):
if self._values['auto_failback_time'] is None:
return None
value = self._values['auto_failback_time']
if value < 0 or value > 300:
raise F5ModuleError(
"Invalid auto_failback_time value, correct range is 0 - 300, specified value: {0}.".format(value))
return value
@property
def auto_failback(self):
result = flatten_boolean(self._values['auto_failback'])
if result == 'yes':
return 'true'
if result == 'no':
return 'false'
return None
@property
def ha_order(self):
if self._values['ha_order'] is None:
return None
if len(self._values['ha_order']) == 1 and self._values['ha_order'][0] == '':
if self.auto_failback == 'true':
raise F5ModuleError(
'Cannot enable auto failback when HA order list is empty, at least one device must be specified.'
)
return 'none'
result = [fq_name(self.partition, value) for value in self._values['ha_order']]
return result
class Changes(Parameters):
def to_return(self):
result = {}
for returnable in self.returnables:
result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
return result
class UsableChanges(Changes):
pass
class ReportableChanges(Changes):
@property
def mac_address(self):
if self._values['mac_address'] is None:
return None
if self._values['mac_address'] == 'none':
return ''
return self._values['mac_address']
@property
def ha_group(self):
if self._values['ha_group'] is None:
return None
if self._values['ha_group'] == 'none':
return ''
return self._values['ha_group']
@property
def auto_failback(self):
result = self._values['auto_failback']
if result == 'true':
return 'yes'
if result == 'false':
return 'no'
return None
@property
def ha_order(self):
if self._values['ha_order'] is None:
return None
if self._values['ha_order'] == 'none':
return ''
return self._values['ha_order']
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 ha_group(self):
if self.want.ha_group is None:
return None
if self.have.ha_group is None and self.want.ha_group == 'none':
return None
if self.want.ha_group != self.have.ha_group:
if self.have.auto_failback == 'true' and self.want.auto_failback != 'false':
raise F5ModuleError(
"The auto_failback parameter on the device must disabled to use ha_group failover method."
)
return self.want.ha_group
@property
def ha_order(self):
# Device order is literally derived from the order in the array,
# hence lists with the same elements but in different order cannot be equal, so cmp_simple_list
# function will not work here.
if self.want.ha_order is None:
return None
if self.have.ha_order is None and self.want.ha_order == 'none':
return None
if self.want.ha_order != self.have.ha_order:
return self.want.ha_order
@property
def partition(self):
raise F5ModuleError(
"Partition cannot be changed for a traffic group. Only /Common is allowed."
)
class ModuleManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = F5RestClient(**self.module.params)
self.have = None
self.want = ModuleParameters(params=self.module.params)
self.changes = UsableChanges()
def _set_changed_options(self):
changed = {}
for key in Parameters.returnables:
if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key)
if changed:
self.changes = UsableChanges(params=changed)
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 _announce_deprecations(self, result):
warnings = result.pop('__warnings', [])
for warning in warnings:
self.module.deprecate(
msg=warning['msg'],
version=warning['version']
)
def exec_module(self):
changed = False
result = dict()
state = self.want.state
if state == "present":
changed = self.present()
elif state == "absent":
changed = self.absent()
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):
if self.exists():
return self.update()
else:
return self.create()
def absent(self):
if self.exists():
return self.remove()
return False
def should_update(self):
result = self._update_changed_options()
if result:
return True
return False
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._set_changed_options()
if self.want.partition.lower().strip('/') != 'common':
raise F5ModuleError(
"Traffic groups can only be created in the /Common partition"
)
if self.module.check_mode:
return True
self.create_on_device()
return True
def exists(self):
uri = "https://{0}:{1}/mgmt/tm/cm/traffic-group/{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 create_on_device(self):
params = self.changes.api_params()
params['name'] = self.want.name
params['partition'] = self.want.partition
uri = "https://{0}:{1}/mgmt/tm/cm/traffic-group/".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)
return response['selfLink']
def update_on_device(self):
params = self.changes.api_params()
uri = "https://{0}:{1}/mgmt/tm/cm/traffic-group/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
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 read_current_from_device(self):
uri = "https://{0}:{1}/mgmt/tm/cm/traffic-group/{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 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_from_device(self):
uri = "https://{0}:{1}/mgmt/tm/cm/traffic-group/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
response = self.client.api.delete(uri)
if response.status == 200:
return True
raise F5ModuleError(response.content)
class ArgumentSpec(object):
def __init__(self):
self.supports_check_mode = True
argument_spec = dict(
name=dict(required=True),
mac_address=dict(),
ha_order=dict(
type='list'
),
ha_group=dict(),
ha_load_factor=dict(
type='int'
),
auto_failback=dict(
type='bool',
),
auto_failback_time=dict(
type='int'
),
state=dict(
default='present',
choices=['absent', 'present']
),
partition=dict(
default='Common',
fallback=(env_fallback, ['F5_PARTITION'])
),
)
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,
)
try:
mm = ModuleManager(module=module)
results = mm.exec_module()
module.exit_json(**results)
except F5ModuleError as ex:
module.fail_json(msg=str(ex))
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,979 @@
#!/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': 'certified'}
DOCUMENTATION = r'''
---
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
and remove address list entries.
options:
name:
description:
- Specifies the name of the address list.
type: str
required: True
partition:
description:
- Device partition to manage resources on.
type: str
default: Common
description:
description:
- Description of the address list
type: str
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/.
type: str
required: True
choices:
- Any valid 2 character ISO country code.
- Any valid country name.
region:
description:
- Region name of the country to use.
type: str
type: list
addresses:
description:
- Individual addresses that you want to add to the list. These addresses differ
from ranges, and lists of lists such as what can be used in C(address_ranges)
and C(address_lists) respectively.
- This list can also include networks that have CIDR notation.
type: list
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).
type: list
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.
type: list
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).
type: list
state:
description:
- When C(present), ensures that the address list and entries exists.
- When C(absent), ensures the address list is removed.
type: str
choices:
- present
- absent
default: present
extends_documentation_fragment:
- f5networks.f5_modules.f5
author:
- Tim Rupp (@caphrim007)
- Wojciech Wypior (@wojtek0806)
'''
EXAMPLES = r'''
- name: Create an address list
bigip_firewall_address_list:
name: foo
addresses:
- 3.3.3.3
- 4.4.4.4
- 5.5.5.5
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
'''
RETURN = r'''
description:
description: The new description of the address list.
returned: changed
type: str
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: str
sample: US
region:
description: Region of the geo location.
returned: changed
type: str
sample: California
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
try:
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 fq_name
from library.module_utils.network.f5.common import f5_argument_spec
from library.module_utils.network.f5.common import transform_name
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
except ImportError:
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.bigip import F5RestClient
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import F5ModuleError
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import AnsibleF5Parameters
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import fq_name
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import f5_argument_spec
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import transform_name
from ansible_collections.ansible.netcommon.plugins.module_utils.compat.ipaddress import ip_address
from ansible_collections.ansible.netcommon.plugins.module_utils.compat.ipaddress import ip_interface
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.ipaddress import is_valid_ip
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.ipaddress import is_valid_ip_interface
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',
]
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
result = []
for x in self._values['addresses']:
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)
)
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 = 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 int(start) > int(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 = fq_name(self.partition, 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 = 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 int(start) > int(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 = F5RestClient(**self.module.params)
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
if state == "present":
changed = self.present()
elif state == "absent":
changed = self.absent()
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 absent(self):
if self.exists():
return self.remove()
return False
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 exists(self):
uri = "https://{0}:{1}/mgmt/tm/security/firewall/address-list/{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 create_on_device(self):
params = self.changes.api_params()
params['name'] = self.want.name
params['partition'] = self.want.partition
uri = "https://{0}:{1}/mgmt/tm/security/firewall/address-list/".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()
uri = "https://{0}:{1}/mgmt/tm/security/firewall/address-list/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
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 remove_from_device(self):
uri = "https://{0}:{1}/mgmt/tm/security/firewall/address-list/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
resp = self.client.api.delete(uri)
if resp.status == 200:
return True
raise F5ModuleError(resp.content)
def read_current_from_device(self):
uri = "https://{0}:{1}/mgmt/tm/security/firewall/address-list/{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 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):
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
)
try:
mm = ModuleManager(module=module)
results = mm.exec_module()
module.exit_json(**results)
except F5ModuleError as ex:
module.fail_json(msg=str(ex))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,646 @@
#!/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': 'certified'}
DOCUMENTATION = r'''
---
module: bigip_firewall_port_list
short_description: Manage port lists on BIG-IP AFM
description:
- Manages the AFM port lists on a BIG-IP. This module can be used to add
and remove port list entries.
options:
name:
description:
- Specifies the name of the port list.
type: str
required: True
partition:
description:
- Device partition to manage resources on.
type: str
default: Common
description:
description:
- Description of the port list
type: str
ports:
description:
- Simple list of port values to add to the list
type: list
port_ranges:
description:
- A list of port ranges where the range starts with a port number, is followed
by a dash (-) and then a second number.
- If the first number is greater than the second number, the numbers will be
reversed so-as to be properly formatted. ie, 90-78 would become 78-90.
type: list
port_lists:
description:
- Simple list of existing port lists to add to this list. Port 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.
type: list
state:
description:
- When C(present), ensures that the address list and entries exists.
- When C(absent), ensures the address list is removed.
type: str
choices:
- present
- absent
default: present
extends_documentation_fragment:
- f5networks.f5_modules.f5
author:
- Tim Rupp (@caphrim007)
- Wojciech Wypior (@wojtek0806)
'''
EXAMPLES = r'''
- name: Create a simple port list
bigip_firewall_port_list:
name: foo
ports:
- 80
- 443
state: present
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
- name: Override the above list of ports with a new list
bigip_firewall_port_list:
name: foo
ports:
- 3389
- 8080
- 25
state: present
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
- name: Create port list with series of ranges
bigip_firewall_port_list:
name: foo
port_ranges:
- 25-30
- 80-500
- 50-78
state: present
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
- name: Use multiple types of port arguments
bigip_firewall_port_list:
name: foo
port_ranges:
- 25-30
- 80-500
- 50-78
ports:
- 8080
- 443
state: present
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
- name: Remove port list
bigip_firewall_port_list:
name: foo
state: absent
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
- name: Create port list from a file with one port per line
bigip_firewall_port_list:
name: lot-of-ports
ports: "{{ lookup('file', 'my-large-port-list.txt').split('\n') }}"
state: present
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
'''
RETURN = r'''
description:
description: The new description of the port list.
returned: changed
type: str
sample: My port list
ports:
description: The new list of ports applied to the port list.
returned: changed
type: list
sample: [80, 443]
port_ranges:
description: The new list of port ranges applied to the port list.
returned: changed
type: list
sample: [80-100, 200-8080]
port_lists:
description: The new list of port list names applied to the port list.
returned: changed
type: list
sample: [/Common/list1, /Common/list2]
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback
try:
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 fq_name
from library.module_utils.network.f5.common import f5_argument_spec
from library.module_utils.network.f5.common import transform_name
from library.module_utils.network.f5.icontrol import module_provisioned
except ImportError:
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.bigip import F5RestClient
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import F5ModuleError
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import AnsibleF5Parameters
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import fq_name
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import f5_argument_spec
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import transform_name
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.icontrol import module_provisioned
class Parameters(AnsibleF5Parameters):
api_map = {
'portLists': 'port_lists',
}
api_attributes = [
'portLists', 'ports', 'description',
]
returnables = [
'ports', 'port_ranges', 'port_lists', 'description',
]
updatables = [
'description', 'ports', 'port_ranges', 'port_lists',
]
class ApiParameters(Parameters):
@property
def port_ranges(self):
if self._values['ports'] is None:
return None
result = []
for port_range in self._values['ports']:
if '-' not in port_range['name']:
continue
start, stop = port_range['name'].split('-')
start = int(start.strip())
stop = int(stop.strip())
if start > stop:
stop, start = start, stop
item = '{0}-{1}'.format(start, stop)
result.append(item)
return result
@property
def port_lists(self):
if self._values['port_lists'] is None:
return None
result = []
for x in self._values['port_lists']:
item = '/{0}/{1}'.format(x['partition'], x['name'])
result.append(item)
return result
@property
def ports(self):
if self._values['ports'] is None:
return None
result = [int(x['name']) for x in self._values['ports'] if '-' not in x['name']]
return result
class ModuleParameters(Parameters):
@property
def ports(self):
if self._values['ports'] is None:
return None
if any(x for x in self._values['ports'] if '-' in str(x)):
raise F5ModuleError(
"Ports must be whole numbers between 0 and 65,535"
)
if any(x for x in self._values['ports'] if 0 < int(x) > 65535):
raise F5ModuleError(
"Ports must be whole numbers between 0 and 65,535"
)
result = [int(x) for x in self._values['ports']]
return result
@property
def port_ranges(self):
if self._values['port_ranges'] is None:
return None
result = []
for port_range in self._values['port_ranges']:
if '-' not in port_range:
continue
start, stop = port_range.split('-')
start = int(start.strip())
stop = int(stop.strip())
if start > stop:
stop, start = start, stop
if 0 < start > 65535 or 0 < stop > 65535:
raise F5ModuleError(
"Ports must be whole numbers between 0 and 65,535"
)
item = '{0}-{1}'.format(start, stop)
result.append(item)
return result
@property
def port_lists(self):
if self._values['port_lists'] is None:
return None
result = []
for x in self._values['port_lists']:
item = fq_name(self.partition, x)
result.append(item)
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 ports(self):
result = []
for item in self._values['ports']:
if '-' in item['name']:
continue
result.append(item['name'])
return result
@property
def port_ranges(self):
result = []
for item in self._values['ports']:
if '-' not in item['name']:
continue
result.append(item['name'])
return result
class UsableChanges(Changes):
@property
def ports(self):
if self._values['ports'] is None and self._values['port_ranges'] is None:
return None
result = []
if self._values['ports']:
# The values of the 'key' index literally need to be string values.
# If they are not, on BIG-IP 12.1.0 they will raise this REST exception.
#
# {
# "code": 400,
# "message": "one or more configuration identifiers must be provided",
# "errorStack": [],
# "apiError": 26214401
# }
result += [dict(name=str(x)) for x in self._values['ports']]
if self._values['port_ranges']:
result += [dict(name=str(x)) for x in self._values['port_ranges']]
return result
@property
def port_lists(self):
if self._values['port_lists'] is None:
return None
result = []
for x in self._values['port_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 ports(self):
if self.want.ports is None:
return None
elif self.have.ports is None:
return self.want.ports
if sorted(self.want.ports) != sorted(self.have.ports):
return self.want.ports
@property
def port_lists(self):
if self.want.port_lists is None:
return None
elif self.have.port_lists is None:
return self.want.port_lists
if sorted(self.want.port_lists) != sorted(self.have.port_lists):
return self.want.port_lists
@property
def port_ranges(self):
if self.want.port_ranges is None:
return None
elif self.have.port_ranges is None:
return self.want.port_ranges
if sorted(self.want.port_ranges) != sorted(self.have.port_ranges):
return self.want.port_ranges
class ModuleManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = F5RestClient(**self.module.params)
self.want = ModuleParameters(params=self.module.params)
self.have = ApiParameters()
self.changes = UsableChanges()
def _set_changed_options(self):
changed = {}
for key in Parameters.returnables:
if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key)
if changed:
self.changes = UsableChanges(params=changed)
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):
if not module_provisioned(self.client, 'afm'):
raise F5ModuleError(
"AFM must be provisioned to use this module."
)
changed = False
result = dict()
state = self.want.state
if state == "present":
changed = self.present()
elif state == "absent":
changed = self.absent()
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 absent(self):
if self.exists():
return self.remove()
return False
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._set_changed_options()
if self.module.check_mode:
return True
self.create_on_device()
return True
def exists(self):
uri = "https://{0}:{1}/mgmt/tm/security/firewall/port-list/{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 update_on_device(self):
params = self.changes.api_params()
uri = "https://{0}:{1}/mgmt/tm/security/firewall/port-list/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
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 read_current_from_device(self):
uri = "https://{0}:{1}/mgmt/tm/security/firewall/port-list/{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 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 create_on_device(self):
params = self.changes.api_params()
params['name'] = self.want.name
params['partition'] = self.want.partition
uri = "https://{0}:{1}/mgmt/tm/security/firewall/port-list/".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)
return response['selfLink']
def remove_from_device(self):
uri = "https://{0}:{1}/mgmt/tm/security/firewall/port-list/{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
)
response = self.client.api.delete(uri)
if response.status == 200:
return True
raise F5ModuleError(response.content)
class ArgumentSpec(object):
def __init__(self):
self.supports_check_mode = True
argument_spec = dict(
name=dict(required=True),
description=dict(),
ports=dict(type='list'),
port_ranges=dict(type='list'),
port_lists=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
)
try:
mm = ModuleManager(module=module)
results = mm.exec_module()
module.exit_json(**results)
except F5ModuleError as ex:
module.fail_json(msg=str(ex))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,986 @@
#!/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': ['deprecated'],
'supported_by': 'certified'}
DOCUMENTATION = r'''
---
module: bigip_gtm_facts
short_description: Collect facts from F5 BIG-IP GTM devices
description:
- Collect facts from F5 BIG-IP GTM devices.
options:
include:
description:
- Fact category to collect.
required: True
choices:
- pool
- wide_ip
- server
filter:
description:
- Perform regex filter of response. Filtering is done on the name of
the resource. Valid filters are anything that can be provided to
Python's C(re) module.
deprecated:
removed_in: '2.11'
alternative: bigip_device_info
why: >
The bigip_gtm_facts module is an outlier as all facts are being collected
in the bigip_device_info module. Additionally, the M(bigip_device_info)
module is easier to maintain and use.
extends_documentation_fragment:
- f5networks.f5_modules.f5
notes:
- This module is deprecated. Use the C(bigip_device_info) module instead.
author:
- Tim Rupp (@caphrim007)
'''
EXAMPLES = r'''
- name: Get pool facts
bigip_gtm_facts:
server: lb.mydomain.com
user: admin
password: secret
include: pool
filter: my_pool
delegate_to: localhost
'''
RETURN = r'''
wide_ip:
description:
Contains the lb method for the wide ip and the pools that are within the wide ip.
returned: changed
type: list
sample:
wide_ip:
- enabled: True
failure_rcode: noerror
failure_rcode_response: disabled
failure_rcode_ttl: 0
full_path: /Common/foo.ok.com
last_resort_pool: ""
minimal_response: enabled
name: foo.ok.com
partition: Common
persist_cidr_ipv4: 32
persist_cidr_ipv6: 128
persistence: disabled
pool_lb_mode: round-robin
pools:
- name: d3qw
order: 0
partition: Common
ratio: 1
ttl_persistence: 3600
type: naptr
pool:
description: Contains the pool object status and enabled status.
returned: changed
type: list
sample:
pool:
- alternate_mode: round-robin
dynamic_ratio: disabled
enabled: True
fallback_mode: return-to-dns
full_path: /Common/d3qw
load_balancing_mode: round-robin
manual_resume: disabled
max_answers_returned: 1
members:
- disabled: True
flags: a
full_path: ok3.com
member_order: 0
name: ok3.com
order: 10
preference: 10
ratio: 1
service: 80
name: d3qw
partition: Common
qos_hit_ratio: 5
qos_hops: 0
qos_kilobytes_second: 3
qos_lcs: 30
qos_packet_rate: 1
qos_rtt: 50
qos_topology: 0
qos_vs_capacity: 0
qos_vs_score: 0
availability_state: offline
enabled_state: disabled
ttl: 30
type: naptr
verify_member_availability: disabled
server:
description:
Contains the virtual server enabled and availability status, and address.
returned: changed
type: list
sample:
server:
- addresses:
- device_name: /Common/qweqwe
name: 10.10.10.10
translation: none
datacenter: /Common/xfxgh
enabled: True
expose_route_domains: no
full_path: /Common/qweqwe
iq_allow_path: yes
iq_allow_service_check: yes
iq_allow_snmp: yes
limit_cpu_usage: 0
limit_cpu_usage_status: disabled
limit_max_bps: 0
limit_max_bps_status: disabled
limit_max_connections: 0
limit_max_connections_status: disabled
limit_max_pps: 0
limit_max_pps_status: disabled
limit_mem_avail: 0
limit_mem_avail_status: disabled
link_discovery: disabled
monitor: /Common/bigip
name: qweqwe
partition: Common
product: single-bigip
virtual_server_discovery: disabled
virtual_servers:
- destination: 10.10.10.10:0
enabled: True
full_path: jsdfhsd
limit_max_bps: 0
limit_max_bps_status: disabled
limit_max_connections: 0
limit_max_connections_status: disabled
limit_max_pps: 0
limit_max_pps_status: disabled
name: jsdfhsd
translation_address: none
translation_port: 0
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.six import iteritems
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
from distutils.version import LooseVersion
try:
from f5.bigip import ManagementRoot
from icontrol.exceptions import iControlUnexpectedHTTPError
from f5.utils.responses.handlers import Stats
HAS_F5SDK = True
except ImportError:
HAS_F5SDK = False
try:
from library.module_utils.network.f5.common import F5BaseClient
except ImportError:
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import F5BaseClient
try:
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
except ImportError:
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import F5ModuleError
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import AnsibleF5Parameters
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import f5_argument_spec
class F5Client(F5BaseClient):
def __init__(self, *args, **kwargs):
super(F5Client, self).__init__(*args, **kwargs)
self.provider = self.merge_provider_params()
@property
def api(self):
if self._client:
return self._client
try:
result = ManagementRoot(
self.provider['server'],
self.provider['user'],
self.provider['password'],
port=self.provider['server_port'],
verify=self.provider['validate_certs'],
token='tmos'
)
self._client = result
return self._client
except Exception as ex:
error = 'Unable to connect to {0} on port {1}. The reported error was "{2}".'.format(
self.provider['server'], self.provider['server_port'], str(ex)
)
raise F5ModuleError(error)
class BaseManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
self.kwargs = kwargs
self.types = dict(
a_s='a',
aaaas='aaaa',
cnames='cname',
mxs='mx',
naptrs='naptr',
srvs='srv'
)
def filter_matches_name(self, name):
if self.want.filter is None:
return True
matches = re.match(self.want.filter, str(name))
if matches:
return True
else:
return False
def version_is_less_than_12(self):
version = self.client.api.tmos_version
if LooseVersion(version) < LooseVersion('12.0.0'):
return True
else:
return False
def get_facts_from_collection(self, collection, collection_type=None):
results = []
for item in collection:
if not self.filter_matches_name(item.name):
continue
facts = self.format_facts(item, collection_type)
results.append(facts)
return results
def read_stats_from_device(self, resource):
stats = Stats(resource.stats.load())
return stats.stat
class UntypedManager(BaseManager):
def exec_module(self):
results = []
facts = self.read_facts()
for item in facts:
attrs = item.to_return()
filtered = [(k, v) for k, v in iteritems(attrs) if self.filter_matches_name(k)]
if filtered:
results.append(dict(filtered))
return results
class TypedManager(BaseManager):
def exec_module(self):
results = []
for collection, type in iteritems(self.types):
facts = self.read_facts(collection)
if not facts:
continue
for x in facts:
x.update({'type': type})
for item in facts:
attrs = item.to_return()
filtered = [(k, v) for k, v in iteritems(attrs) if self.filter_matches_name(k)]
if filtered:
results.append(dict(filtered))
return results
class Parameters(AnsibleF5Parameters):
@property
def include(self):
requested = self._values['include']
valid = ['pool', 'wide_ip', 'server', 'all']
if any(x for x in requested if x not in valid):
raise F5ModuleError(
"The valid 'include' choices are {0}".format(', '.join(valid))
)
if 'all' in requested:
return ['all']
else:
return requested
class BaseParameters(Parameters):
@property
def enabled(self):
if self._values['enabled'] is None:
return None
elif self._values['enabled'] in BOOLEANS_TRUE:
return True
else:
return False
@property
def disabled(self):
if self._values['disabled'] is None:
return None
elif self._values['disabled'] in BOOLEANS_TRUE:
return True
else:
return False
def _remove_internal_keywords(self, resource):
resource.pop('kind', None)
resource.pop('generation', None)
resource.pop('selfLink', None)
resource.pop('isSubcollection', None)
resource.pop('fullPath', None)
def to_return(self):
result = {}
for returnable in self.returnables:
result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
return result
class PoolParameters(BaseParameters):
api_map = {
'alternateMode': 'alternate_mode',
'dynamicRatio': 'dynamic_ratio',
'fallbackMode': 'fallback_mode',
'fullPath': 'full_path',
'loadBalancingMode': 'load_balancing_mode',
'manualResume': 'manual_resume',
'maxAnswersReturned': 'max_answers_returned',
'qosHitRatio': 'qos_hit_ratio',
'qosHops': 'qos_hops',
'qosKilobytesSecond': 'qos_kilobytes_second',
'qosLcs': 'qos_lcs',
'qosPacketRate': 'qos_packet_rate',
'qosRtt': 'qos_rtt',
'qosTopology': 'qos_topology',
'qosVsCapacity': 'qos_vs_capacity',
'qosVsScore': 'qos_vs_score',
'verifyMemberAvailability': 'verify_member_availability',
'membersReference': 'members'
}
returnables = [
'alternate_mode', 'dynamic_ratio', 'enabled', 'disabled', 'fallback_mode',
'load_balancing_mode', 'manual_resume', 'max_answers_returned', 'members',
'name', 'partition', 'qos_hit_ratio', 'qos_hops', 'qos_kilobytes_second',
'qos_lcs', 'qos_packet_rate', 'qos_rtt', 'qos_topology', 'qos_vs_capacity',
'qos_vs_score', 'ttl', 'type', 'full_path', 'availability_state',
'enabled_state', 'availability_status'
]
@property
def max_answers_returned(self):
if self._values['max_answers_returned'] is None:
return None
return int(self._values['max_answers_returned'])
@property
def members(self):
result = []
if self._values['members'] is None or 'items' not in self._values['members']:
return result
for item in self._values['members']['items']:
self._remove_internal_keywords(item)
if 'disabled' in item:
if item['disabled'] in BOOLEANS_TRUE:
item['disabled'] = True
else:
item['disabled'] = False
if 'enabled' in item:
if item['enabled'] in BOOLEANS_TRUE:
item['enabled'] = True
else:
item['enabled'] = False
if 'fullPath' in item:
item['full_path'] = item.pop('fullPath')
if 'memberOrder' in item:
item['member_order'] = int(item.pop('memberOrder'))
# Cast some attributes to integer
for x in ['order', 'preference', 'ratio', 'service']:
if x in item:
item[x] = int(item[x])
result.append(item)
return result
@property
def qos_hit_ratio(self):
if self._values['qos_hit_ratio'] is None:
return None
return int(self._values['qos_hit_ratio'])
@property
def qos_hops(self):
if self._values['qos_hops'] is None:
return None
return int(self._values['qos_hops'])
@property
def qos_kilobytes_second(self):
if self._values['qos_kilobytes_second'] is None:
return None
return int(self._values['qos_kilobytes_second'])
@property
def qos_lcs(self):
if self._values['qos_lcs'] is None:
return None
return int(self._values['qos_lcs'])
@property
def qos_packet_rate(self):
if self._values['qos_packet_rate'] is None:
return None
return int(self._values['qos_packet_rate'])
@property
def qos_rtt(self):
if self._values['qos_rtt'] is None:
return None
return int(self._values['qos_rtt'])
@property
def qos_topology(self):
if self._values['qos_topology'] is None:
return None
return int(self._values['qos_topology'])
@property
def qos_vs_capacity(self):
if self._values['qos_vs_capacity'] is None:
return None
return int(self._values['qos_vs_capacity'])
@property
def qos_vs_score(self):
if self._values['qos_vs_score'] is None:
return None
return int(self._values['qos_vs_score'])
@property
def availability_state(self):
if self._values['stats'] is None:
return None
try:
result = self._values['stats']['status_availabilityState']
return result['description']
except AttributeError:
return None
@property
def enabled_state(self):
if self._values['stats'] is None:
return None
try:
result = self._values['stats']['status_enabledState']
return result['description']
except AttributeError:
return None
@property
def availability_status(self):
# This fact is a combination of the availability_state and enabled_state
#
# The purpose of the fact is to give a higher-level view of the availability
# of the pool, that can be used in playbooks. If you need further detail,
# consider using the following facts together.
#
# - availability_state
# - enabled_state
#
if self.enabled_state == 'enabled':
if self.availability_state == 'offline':
return 'red'
elif self.availability_state == 'available':
return 'green'
elif self.availability_state == 'unknown':
return 'blue'
else:
return 'none'
else:
# disabled
return 'black'
class WideIpParameters(BaseParameters):
api_map = {
'fullPath': 'full_path',
'failureRcode': 'failure_return_code',
'failureRcodeResponse': 'failure_return_code_response',
'failureRcodeTtl': 'failure_return_code_ttl',
'lastResortPool': 'last_resort_pool',
'minimalResponse': 'minimal_response',
'persistCidrIpv4': 'persist_cidr_ipv4',
'persistCidrIpv6': 'persist_cidr_ipv6',
'poolLbMode': 'pool_lb_mode',
'ttlPersistence': 'ttl_persistence'
}
returnables = [
'full_path', 'description', 'enabled', 'disabled', 'failure_return_code',
'failure_return_code_response', 'failure_return_code_ttl', 'last_resort_pool',
'minimal_response', 'persist_cidr_ipv4', 'persist_cidr_ipv6', 'pool_lb_mode',
'ttl_persistence', 'pools'
]
@property
def pools(self):
result = []
if self._values['pools'] is None:
return []
for pool in self._values['pools']:
del pool['nameReference']
for x in ['order', 'ratio']:
if x in pool:
pool[x] = int(pool[x])
result.append(pool)
return result
@property
def failure_return_code_ttl(self):
if self._values['failure_return_code_ttl'] is None:
return None
return int(self._values['failure_return_code_ttl'])
@property
def persist_cidr_ipv4(self):
if self._values['persist_cidr_ipv4'] is None:
return None
return int(self._values['persist_cidr_ipv4'])
@property
def persist_cidr_ipv6(self):
if self._values['persist_cidr_ipv6'] is None:
return None
return int(self._values['persist_cidr_ipv6'])
@property
def ttl_persistence(self):
if self._values['ttl_persistence'] is None:
return None
return int(self._values['ttl_persistence'])
class ServerParameters(BaseParameters):
api_map = {
'fullPath': 'full_path',
'exposeRouteDomains': 'expose_route_domains',
'iqAllowPath': 'iq_allow_path',
'iqAllowServiceCheck': 'iq_allow_service_check',
'iqAllowSnmp': 'iq_allow_snmp',
'limitCpuUsage': 'limit_cpu_usage',
'limitCpuUsageStatus': 'limit_cpu_usage_status',
'limitMaxBps': 'limit_max_bps',
'limitMaxBpsStatus': 'limit_max_bps_status',
'limitMaxConnections': 'limit_max_connections',
'limitMaxConnectionsStatus': 'limit_max_connections_status',
'limitMaxPps': 'limit_max_pps',
'limitMaxPpsStatus': 'limit_max_pps_status',
'limitMemAvail': 'limit_mem_available',
'limitMemAvailStatus': 'limit_mem_available_status',
'linkDiscovery': 'link_discovery',
'proberFallback': 'prober_fallback',
'proberPreference': 'prober_preference',
'virtualServerDiscovery': 'virtual_server_discovery',
'devicesReference': 'devices',
'virtualServersReference': 'virtual_servers'
}
returnables = [
'datacenter', 'enabled', 'disabled', 'expose_route_domains', 'iq_allow_path',
'full_path', 'iq_allow_service_check', 'iq_allow_snmp', 'limit_cpu_usage',
'limit_cpu_usage_status', 'limit_max_bps', 'limit_max_bps_status',
'limit_max_connections', 'limit_max_connections_status', 'limit_max_pps',
'limit_max_pps_status', 'limit_mem_available', 'limit_mem_available_status',
'link_discovery', 'monitor', 'product', 'prober_fallback', 'prober_preference',
'virtual_server_discovery', 'addresses', 'devices', 'virtual_servers'
]
@property
def product(self):
if self._values['product'] is None:
return None
if self._values['product'] in ['single-bigip', 'redundant-bigip']:
return 'bigip'
return self._values['product']
@property
def devices(self):
result = []
if self._values['devices'] is None or 'items' not in self._values['devices']:
return result
for item in self._values['devices']['items']:
self._remove_internal_keywords(item)
if 'fullPath' in item:
item['full_path'] = item.pop('fullPath')
result.append(item)
return result
@property
def virtual_servers(self):
result = []
if self._values['virtual_servers'] is None or 'items' not in self._values['virtual_servers']:
return result
for item in self._values['virtual_servers']['items']:
self._remove_internal_keywords(item)
if 'disabled' in item:
if item['disabled'] in BOOLEANS_TRUE:
item['disabled'] = True
else:
item['disabled'] = False
if 'enabled' in item:
if item['enabled'] in BOOLEANS_TRUE:
item['enabled'] = True
else:
item['enabled'] = False
if 'fullPath' in item:
item['full_path'] = item.pop('fullPath')
if 'limitMaxBps' in item:
item['limit_max_bps'] = int(item.pop('limitMaxBps'))
if 'limitMaxBpsStatus' in item:
item['limit_max_bps_status'] = item.pop('limitMaxBpsStatus')
if 'limitMaxConnections' in item:
item['limit_max_connections'] = int(item.pop('limitMaxConnections'))
if 'limitMaxConnectionsStatus' in item:
item['limit_max_connections_status'] = item.pop('limitMaxConnectionsStatus')
if 'limitMaxPps' in item:
item['limit_max_pps'] = int(item.pop('limitMaxPps'))
if 'limitMaxPpsStatus' in item:
item['limit_max_pps_status'] = item.pop('limitMaxPpsStatus')
if 'translationAddress' in item:
item['translation_address'] = item.pop('translationAddress')
if 'translationPort' in item:
item['translation_port'] = int(item.pop('translationPort'))
result.append(item)
return result
@property
def limit_cpu_usage(self):
if self._values['limit_cpu_usage'] is None:
return None
return int(self._values['limit_cpu_usage'])
@property
def limit_max_bps(self):
if self._values['limit_max_bps'] is None:
return None
return int(self._values['limit_max_bps'])
@property
def limit_max_connections(self):
if self._values['limit_max_connections'] is None:
return None
return int(self._values['limit_max_connections'])
@property
def limit_max_pps(self):
if self._values['limit_max_pps'] is None:
return None
return int(self._values['limit_max_pps'])
@property
def limit_mem_available(self):
if self._values['limit_mem_available'] is None:
return None
return int(self._values['limit_mem_available'])
class PoolFactManager(BaseManager):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
super(PoolFactManager, self).__init__(**kwargs)
self.kwargs = kwargs
def exec_module(self):
if self.version_is_less_than_12():
manager = self.get_manager('untyped')
else:
manager = self.get_manager('typed')
facts = manager.exec_module()
result = dict(pool=facts)
return result
def get_manager(self, type):
if type == 'typed':
return TypedPoolFactManager(**self.kwargs)
elif type == 'untyped':
return UntypedPoolFactManager(**self.kwargs)
class TypedPoolFactManager(TypedManager):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
super(TypedPoolFactManager, self).__init__(**kwargs)
self.want = PoolParameters(params=self.module.params)
def read_facts(self, collection):
results = []
collection = self.read_collection_from_device(collection)
for resource in collection:
attrs = resource.attrs
attrs['stats'] = self.read_stats_from_device(resource)
params = PoolParameters(params=attrs)
results.append(params)
return results
def read_collection_from_device(self, collection_name):
pools = self.client.api.tm.gtm.pools
collection = getattr(pools, collection_name)
result = collection.get_collection(
requests_params=dict(
params='expandSubcollections=true'
)
)
return result
class UntypedPoolFactManager(UntypedManager):
def __init__(self, *args, **kwargs):
self.client = kwargs.get('client', None)
self.module = kwargs.get('module', None)
super(UntypedPoolFactManager, self).__init__(**kwargs)
self.want = PoolParameters(params=self.module.params)
def read_facts(self):
results = []
collection = self.read_collection_from_device()
for resource in collection:
attrs = resource.attrs
attrs['stats'] = self.read_stats_from_device(resource)
params = PoolParameters(params=attrs)
results.append(params)
return results
def read_collection_from_device(self):
result = self.client.api.tm.gtm.pools.get_collection(
requests_params=dict(
params='expandSubcollections=true'
)
)
return result
class WideIpFactManager(BaseManager):
def exec_module(self):
if self.version_is_less_than_12():
manager = self.get_manager('untyped')
else:
manager = self.get_manager('typed')
facts = manager.exec_module()
result = dict(wide_ip=facts)
return result
def get_manager(self, type):
if type == 'typed':
return TypedWideIpFactManager(**self.kwargs)
elif type == 'untyped':
return UntypedWideIpFactManager(**self.kwargs)
class TypedWideIpFactManager(TypedManager):
def __init__(self, *args, **kwargs):
self.client = kwargs.get('client', None)
self.module = kwargs.get('module', None)
super(TypedWideIpFactManager, self).__init__(**kwargs)
self.want = WideIpParameters(params=self.module.params)
def read_facts(self, collection):
results = []
collection = self.read_collection_from_device(collection)
for resource in collection:
attrs = resource.attrs
params = WideIpParameters(params=attrs)
results.append(params)
return results
def read_collection_from_device(self, collection_name):
wideips = self.client.api.tm.gtm.wideips
collection = getattr(wideips, collection_name)
result = collection.get_collection(
requests_params=dict(
params='expandSubcollections=true'
)
)
return result
class UntypedWideIpFactManager(UntypedManager):
def __init__(self, *args, **kwargs):
self.client = kwargs.get('client', None)
self.module = kwargs.get('module', None)
super(UntypedWideIpFactManager, self).__init__(**kwargs)
self.want = WideIpParameters(params=self.module.params)
def read_facts(self):
results = []
collection = self.read_collection_from_device()
for resource in collection:
attrs = resource.attrs
params = WideIpParameters(params=attrs)
results.append(params)
return results
def read_collection_from_device(self):
result = self.client.api.tm.gtm.wideips.get_collection(
requests_params=dict(
params='expandSubcollections=true'
)
)
return result
class ServerFactManager(UntypedManager):
def __init__(self, *args, **kwargs):
self.client = kwargs.get('client', None)
self.module = kwargs.get('module', None)
super(ServerFactManager, self).__init__(**kwargs)
self.want = ServerParameters(params=self.module.params)
def exec_module(self):
facts = super(ServerFactManager, self).exec_module()
result = dict(server=facts)
return result
def read_facts(self):
results = []
collection = self.read_collection_from_device()
for resource in collection:
attrs = resource.attrs
params = ServerParameters(params=attrs)
results.append(params)
return results
def read_collection_from_device(self):
result = self.client.api.tm.gtm.servers.get_collection(
requests_params=dict(
params='expandSubcollections=true'
)
)
return result
class ModuleManager(object):
def __init__(self, *args, **kwargs):
self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None)
self.kwargs = kwargs
self.want = Parameters(params=self.module.params)
def exec_module(self):
if not self.gtm_provisioned():
raise F5ModuleError(
"GTM must be provisioned to use this module."
)
if 'all' in self.want.include:
names = ['pool', 'wide_ip', 'server']
else:
names = self.want.include
managers = [self.get_manager(name) for name in names]
result = self.execute_managers(managers)
if result:
result['changed'] = True
else:
result['changed'] = False
self._announce_deprecations()
return result
def _announce_deprecations(self):
warnings = []
if self.want:
warnings += self.want._values.get('__warnings', [])
for warning in warnings:
self.module.deprecate(
msg=warning['msg'],
version=warning['version']
)
def execute_managers(self, managers):
results = dict()
for manager in managers:
result = manager.exec_module()
results.update(result)
return results
def get_manager(self, which):
if 'pool' == which:
return PoolFactManager(**self.kwargs)
if 'wide_ip' == which:
return WideIpFactManager(**self.kwargs)
if 'server' == which:
return ServerFactManager(**self.kwargs)
def gtm_provisioned(self):
resource = self.client.api.tm.sys.dbs.db.load(
name='provisioned.cpu.gtm'
)
if int(resource.value) == 0:
return False
return True
class ArgumentSpec(object):
def __init__(self):
self.supports_check_mode = False
argument_spec = dict(
include=dict(
type='list',
choices=[
'pool',
'wide_ip',
'server',
],
required=True
),
filter=dict()
)
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")
client = F5Client(**module.params)
try:
mm = ModuleManager(module=module, client=client)
results = mm.exec_module()
module.exit_json(**results)
except F5ModuleError as ex:
module.fail_json(msg=str(ex))
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
bigip_lx_package.py

View file

@ -0,0 +1,481 @@
#!/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': 'certified'}
DOCUMENTATION = r'''
---
module: bigip_lx_package
short_description: Manages Javascript LX packages on a BIG-IP
description:
- Manages Javascript LX packages on a BIG-IP. This module will allow
you to deploy LX packages to the BIG-IP and manage their lifecycle.
options:
package:
description:
- The LX package that you want to upload or remove. When C(state) is C(present),
and you intend to use this module in a C(role), it is recommended that you use
the C({{ role_path }}) variable. An example is provided in the C(EXAMPLES) section.
- When C(state) is C(absent), it is not necessary for the package to exist on the
Ansible controller. If the full path to the package is provided, the filename will
specifically be cherry picked from it to properly remove the package.
type: path
state:
description:
- Whether the LX package should exist or not.
type: str
default: present
choices:
- present
- absent
notes:
- Requires the rpm tool be installed on the host. This can be accomplished through
different ways on each platform. On Debian based systems with C(apt);
C(apt-get install rpm). On Mac with C(brew); C(brew install rpm).
This command is already present on RedHat based systems.
- Requires BIG-IP >= 12.1.0 because the required functionality is missing
on versions earlier than that.
- The module name C(bigip_iapplx_package) has been deprecated in favor of C(bigip_lx_package).
requirements:
- Requires BIG-IP >= 12.1.0
- The 'rpm' tool installed on the Ansible controller
extends_documentation_fragment:
- f5networks.f5_modules.f5
author:
- Tim Rupp (@caphrim007)
- Wojciech Wypior (@wojtek0806)
'''
EXAMPLES = r'''
- name: Install AS3
bigip_lx_package:
package: f5-appsvcs-3.5.0-3.noarch.rpm
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
- name: Add an LX package stored in a role
bigip_lx_package:
package: "{{ roles_path }}/files/MyApp-0.1.0-0001.noarch.rpm'"
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
- name: Remove an LX package
bigip_lx_package:
package: MyApp-0.1.0-0001.noarch.rpm
state: absent
provider:
password: secret
server: lb.mydomain.com
user: admin
delegate_to: localhost
'''
RETURN = r'''
# only common fields returned
'''
import os
import time
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import urlparse
from distutils.version import LooseVersion
try:
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 f5_argument_spec
from library.module_utils.network.f5.icontrol import tmos_version
from library.module_utils.network.f5.icontrol import upload_file
except ImportError:
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.bigip import F5RestClient
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import F5ModuleError
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import AnsibleF5Parameters
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.common import f5_argument_spec
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.icontrol import tmos_version
from ansible_collections.f5networks.f5_modules.plugins.module_utils.network.f5.icontrol import upload_file
class Parameters(AnsibleF5Parameters):
api_attributes = []
returnables = []
@property
def package(self):
if self._values['package'] is None:
return None
return self._values['package']
@property
def package_file(self):
if self._values['package'] is None:
return None
return os.path.basename(self._values['package'])
@property
def package_name(self):
"""Return a valid name for the package
BIG-IP determines the package name by the content of the RPM info.
It does not use the filename. Therefore, we do the same. This method
is only used though when the file actually exists on your Ansible
controller.
If the package does not exist, then we instead use the filename
portion of the 'package' argument that is provided.
Non-existence typically occurs when using 'state' = 'absent'
:return:
"""
cmd = ['rpm', '-qp', '--queryformat', '%{NAME}-%{VERSION}-%{RELEASE}.%{ARCH}', self.package]
rc, out, err = self._module.run_command(cmd)
if not out:
return str(self.package_file)
return out
@property
def package_root(self):
if self._values['package'] is None:
return None
base = os.path.basename(self._values['package'])
result = os.path.splitext(base)
return result[0]
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 = F5RestClient(**self.module.params)
self.want = ModuleParameters(module=self.module, params=self.module.params)
self.changes = UsableChanges()
def exec_module(self):
result = dict()
changed = False
state = self.want.state
version = tmos_version(self.client)
if LooseVersion(version) <= LooseVersion('12.0.0'):
raise F5ModuleError(
"This version of BIG-IP is not supported."
)
if state == "present":
changed = self.present()
elif state == "absent":
changed = self.absent()
changes = self.changes.to_return()
result.update(**changes)
result.update(dict(changed=changed))
return result
def present(self):
if self.exists():
return False
else:
return self.create()
def absent(self):
changed = False
if self.exists():
changed = self.remove()
return changed
def remove(self):
if self.module.check_mode:
return True
self.remove_from_device()
if self.exists():
raise F5ModuleError("Failed to delete the LX package.")
return True
def create(self):
if self.module.check_mode:
return True
if not os.path.exists(self.want.package):
if self.want.package.startswith('/'):
raise F5ModuleError(
"The specified LX package was not found at {0}.".format(self.want.package)
)
else:
raise F5ModuleError(
"The specified LX package was not found in {0}.".format(os.getcwd())
)
self.upload_to_device()
self.create_on_device()
self.enable_iapplx_on_device()
self.remove_package_file_from_device()
if self.exists():
return True
else:
raise F5ModuleError("Failed to install LX package.")
def exists(self):
exists = False
packages = self.get_installed_packages_on_device()
if os.path.exists(self.want.package):
exists = True
for package in packages:
if exists:
if self.want.package_name == package['packageName']:
return True
else:
if self.want.package_root == package['packageName']:
return True
return False
def get_installed_packages_on_device(self):
uri = "https://{0}:{1}/mgmt/shared/iapp/package-management-tasks".format(
self.client.provider['server'],
self.client.provider['server_port']
)
params = dict(operation='QUERY')
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)
path = urlparse(response["selfLink"]).path
task = self._wait_for_task(path)
if task['status'] == 'FINISHED':
return task['queryResponse']
raise F5ModuleError(
"Failed to find the installed packages on the device."
)
def _wait_for_task(self, path):
task = None
for x in range(0, 60):
task = self.check_task_on_device(path)
if task['status'] in ['FINISHED', 'FAILED']:
return task
time.sleep(1)
return task
def check_task_on_device(self, path):
uri = "https://{0}:{1}{2}".format(
self.client.provider['server'],
self.client.provider['server_port'],
path
)
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 response
def upload_to_device(self):
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, self.want.package)
except F5ModuleError:
raise F5ModuleError(
"Failed to upload the file."
)
def remove_package_file_from_device(self):
params = dict(
command="run",
utilCmdArgs="/var/config/rest/downloads/{0}".format(self.want.package_file)
)
uri = "https://{0}:{1}/mgmt/tm/util/unix-rm".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 create_on_device(self):
remote_path = "/var/config/rest/downloads/{0}".format(self.want.package_file)
params = dict(
operation='INSTALL', packageFilePath=remote_path
)
uri = "https://{0}:{1}/mgmt/shared/iapp/package-management-tasks".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)
path = urlparse(response["selfLink"]).path
task = self._wait_for_task(path)
if task['status'] == 'FINISHED':
return True
else:
raise F5ModuleError(task['errorMessage'])
def remove_from_device(self):
params = dict(
operation='UNINSTALL',
packageName=self.want.package_root
)
uri = "https://{0}:{1}/mgmt/shared/iapp/package-management-tasks".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)
path = urlparse(response["selfLink"]).path
task = self._wait_for_task(path)
if task['status'] == 'FINISHED':
return True
return False
def enable_iapplx_on_device(self):
params = dict(
command="run",
utilCmdArgs='-c "touch /var/config/rest/iapps/enable"'
)
uri = "https://{0}:{1}/mgmt/tm/util/bash".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)
class ArgumentSpec(object):
def __init__(self):
self.supports_check_mode = True
argument_spec = dict(
state=dict(
default='present',
choices=['present', 'absent']
),
package=dict(type='path')
)
self.argument_spec = {}
self.argument_spec.update(f5_argument_spec)
self.argument_spec.update(argument_spec)
self.required_if = [
['state', 'present', ['package']]
]
def main():
spec = ArgumentSpec()
module = AnsibleModule(
argument_spec=spec.argument_spec,
supports_check_mode=spec.supports_check_mode,
required_if=spec.required_if
)
try:
mm = ModuleManager(module=module)
results = mm.exec_module()
module.exit_json(**results)
except F5ModuleError as ex:
module.fail_json(msg=str(ex))
if __name__ == '__main__':
main()

View file

@ -0,0 +1 @@
bigip_firewall_address_list.py

View file

@ -0,0 +1 @@
bigip_firewall_port_list.py

View file

@ -0,0 +1 @@
bigip_device_traffic_group.py

View file

@ -0,0 +1 @@
bigiq_device_info.py

File diff suppressed because it is too large Load diff