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:
commit
aebc1b03fd
4861 changed files with 812621 additions and 0 deletions
1062
plugins/modules/network/f5/bigip_asm_policy.py
Normal file
1062
plugins/modules/network/f5/bigip_asm_policy.py
Normal file
File diff suppressed because it is too large
Load diff
1
plugins/modules/network/f5/bigip_device_facts.py
Symbolic link
1
plugins/modules/network/f5/bigip_device_facts.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
bigip_device_info.py
|
||||
16267
plugins/modules/network/f5/bigip_device_info.py
Normal file
16267
plugins/modules/network/f5/bigip_device_info.py
Normal file
File diff suppressed because it is too large
Load diff
667
plugins/modules/network/f5/bigip_device_traffic_group.py
Normal file
667
plugins/modules/network/f5/bigip_device_traffic_group.py
Normal 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()
|
||||
1803
plugins/modules/network/f5/bigip_facts.py
Normal file
1803
plugins/modules/network/f5/bigip_facts.py
Normal file
File diff suppressed because it is too large
Load diff
979
plugins/modules/network/f5/bigip_firewall_address_list.py
Normal file
979
plugins/modules/network/f5/bigip_firewall_address_list.py
Normal 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()
|
||||
646
plugins/modules/network/f5/bigip_firewall_port_list.py
Normal file
646
plugins/modules/network/f5/bigip_firewall_port_list.py
Normal 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()
|
||||
986
plugins/modules/network/f5/bigip_gtm_facts.py
Normal file
986
plugins/modules/network/f5/bigip_gtm_facts.py
Normal 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()
|
||||
1
plugins/modules/network/f5/bigip_iapplx_package.py
Symbolic link
1
plugins/modules/network/f5/bigip_iapplx_package.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
bigip_lx_package.py
|
||||
481
plugins/modules/network/f5/bigip_lx_package.py
Normal file
481
plugins/modules/network/f5/bigip_lx_package.py
Normal 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()
|
||||
1
plugins/modules/network/f5/bigip_security_address_list.py
Symbolic link
1
plugins/modules/network/f5/bigip_security_address_list.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
bigip_firewall_address_list.py
|
||||
1
plugins/modules/network/f5/bigip_security_port_list.py
Symbolic link
1
plugins/modules/network/f5/bigip_security_port_list.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
bigip_firewall_port_list.py
|
||||
1
plugins/modules/network/f5/bigip_traffic_group.py
Symbolic link
1
plugins/modules/network/f5/bigip_traffic_group.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
bigip_device_traffic_group.py
|
||||
1
plugins/modules/network/f5/bigiq_device_facts.py
Symbolic link
1
plugins/modules/network/f5/bigiq_device_facts.py
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
bigiq_device_info.py
|
||||
2314
plugins/modules/network/f5/bigiq_device_info.py
Normal file
2314
plugins/modules/network/f5/bigiq_device_info.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue