summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/extras/network/f5
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/modules/extras/network/f5')
-rw-r--r--lib/ansible/modules/extras/network/f5/__init__.py0
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_device_dns.py397
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_device_ntp.py257
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_device_sshd.py344
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_facts.py1724
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_gtm_datacenter.py366
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_gtm_virtual_server.py235
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_gtm_wide_ip.py158
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_irule.py385
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_monitor_http.py443
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_monitor_tcp.py485
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_node.py463
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_pool.py561
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_pool_member.py505
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_routedomain.py523
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_selfip.py659
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_ssl_certificate.py516
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_sys_db.py221
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_virtual_server.py614
-rw-r--r--lib/ansible/modules/extras/network/f5/bigip_vlan.py445
20 files changed, 9301 insertions, 0 deletions
diff --git a/lib/ansible/modules/extras/network/f5/__init__.py b/lib/ansible/modules/extras/network/f5/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/__init__.py
diff --git a/lib/ansible/modules/extras/network/f5/bigip_device_dns.py b/lib/ansible/modules/extras/network/f5/bigip_device_dns.py
new file mode 100644
index 0000000000..c469fc4bff
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_device_dns.py
@@ -0,0 +1,397 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_device_dns
+short_description: Manage BIG-IP device DNS settings
+description:
+ - Manage BIG-IP device DNS settings
+version_added: "2.2"
+options:
+ cache:
+ description:
+ - Specifies whether the system caches DNS lookups or performs the
+ operation each time a lookup is needed. Please note that this applies
+ only to Access Policy Manager features, such as ACLs, web application
+ rewrites, and authentication.
+ required: false
+ default: disable
+ choices:
+ - enable
+ - disable
+ name_servers:
+ description:
+ - A list of name serverz that the system uses to validate DNS lookups
+ forwarders:
+ description:
+ - A list of BIND servers that the system can use to perform DNS lookups
+ search:
+ description:
+ - A list of domains that the system searches for local domain lookups,
+ to resolve local host names.
+ ip_version:
+ description:
+ - Specifies whether the DNS specifies IP addresses using IPv4 or IPv6.
+ required: false
+ choices:
+ - 4
+ - 6
+ state:
+ description:
+ - The state of the variable on the system. When C(present), guarantees
+ that an existing variable is set to C(value).
+ required: false
+ default: present
+ choices:
+ - absent
+ - present
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as pip
+ install requests
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Set the DNS settings on the BIG-IP
+ bigip_device_dns:
+ name_servers:
+ - 208.67.222.222
+ - 208.67.220.220
+ search:
+ - localdomain
+ - lab.local
+ state: present
+ password: "secret"
+ server: "lb.mydomain.com"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+cache:
+ description: The new value of the DNS caching
+ returned: changed
+ type: string
+ sample: "enabled"
+name_servers:
+ description: List of name servers that were added or removed
+ returned: changed
+ type: list
+ sample: "['192.0.2.10', '172.17.12.10']"
+forwarders:
+ description: List of forwarders that were added or removed
+ returned: changed
+ type: list
+ sample: "['192.0.2.10', '172.17.12.10']"
+search:
+ description: List of search domains that were added or removed
+ returned: changed
+ type: list
+ sample: "['192.0.2.10', '172.17.12.10']"
+ip_version:
+ description: IP version that was set that DNS will specify IP addresses in
+ returned: changed
+ type: int
+ sample: 4
+'''
+
+try:
+ from f5.bigip.contexts import TransactionContextManager
+ from f5.bigip import ManagementRoot
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+
+REQUIRED = ['name_servers', 'search', 'forwarders', 'ip_version', 'cache']
+CACHE = ['disable', 'enable']
+IP = [4, 6]
+
+
+class BigIpDeviceDns(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def flush(self):
+ result = dict()
+ changed = False
+ state = self.params['state']
+
+ if self.dhcp_enabled():
+ raise F5ModuleError(
+ "DHCP on the mgmt interface must be disabled to make use of " +
+ "this module"
+ )
+
+ if state == 'absent':
+ changed = self.absent()
+ else:
+ changed = self.present()
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+ def dhcp_enabled(self):
+ r = self.api.tm.sys.dbs.db.load(name='dhclient.mgmt')
+ if r.value == 'enable':
+ return True
+ else:
+ return False
+
+ def read(self):
+ result = dict()
+
+ cache = self.api.tm.sys.dbs.db.load(name='dns.cache')
+ proxy = self.api.tm.sys.dbs.db.load(name='dns.proxy.__iter__')
+ dns = self.api.tm.sys.dns.load()
+
+ result['cache'] = str(cache.value)
+ result['forwarders'] = str(proxy.value).split(' ')
+
+ if hasattr(dns, 'nameServers'):
+ result['name_servers'] = dns.nameServers
+ if hasattr(dns, 'search'):
+ result['search'] = dns.search
+ if hasattr(dns, 'include') and 'options inet6' in dns.include:
+ result['ip_version'] = 6
+ else:
+ result['ip_version'] = 4
+ return result
+
+ def present(self):
+ params = dict()
+ current = self.read()
+
+ # Temporary locations to hold the changed params
+ update = dict(
+ dns=None,
+ forwarders=None,
+ cache=None
+ )
+
+ nameservers = self.params['name_servers']
+ search_domains = self.params['search']
+ ip_version = self.params['ip_version']
+ forwarders = self.params['forwarders']
+ cache = self.params['cache']
+ check_mode = self.params['check_mode']
+
+ if nameservers:
+ if 'name_servers' in current:
+ if nameservers != current['name_servers']:
+ params['nameServers'] = nameservers
+ else:
+ params['nameServers'] = nameservers
+
+ if search_domains:
+ if 'search' in current:
+ if search_domains != current['search']:
+ params['search'] = search_domains
+ else:
+ params['search'] = search_domains
+
+ if ip_version:
+ if 'ip_version' in current:
+ if ip_version != int(current['ip_version']):
+ if ip_version == 6:
+ params['include'] = 'options inet6'
+ elif ip_version == 4:
+ params['include'] = ''
+ else:
+ if ip_version == 6:
+ params['include'] = 'options inet6'
+ elif ip_version == 4:
+ params['include'] = ''
+
+ if params:
+ self.cparams.update(camel_dict_to_snake_dict(params))
+
+ if 'include' in params:
+ del self.cparams['include']
+ if params['include'] == '':
+ self.cparams['ip_version'] = 4
+ else:
+ self.cparams['ip_version'] = 6
+
+ update['dns'] = params.copy()
+ params = dict()
+
+ if forwarders:
+ if 'forwarders' in current:
+ if forwarders != current['forwarders']:
+ params['forwarders'] = forwarders
+ else:
+ params['forwarders'] = forwarders
+
+ if params:
+ self.cparams.update(camel_dict_to_snake_dict(params))
+ update['forwarders'] = ' '.join(params['forwarders'])
+ params = dict()
+
+ if cache:
+ if 'cache' in current:
+ if cache != current['cache']:
+ params['cache'] = cache
+
+ if params:
+ self.cparams.update(camel_dict_to_snake_dict(params))
+ update['cache'] = params['cache']
+ params = dict()
+
+ if self.cparams:
+ changed = True
+ if check_mode:
+ return changed
+ else:
+ return False
+
+ tx = self.api.tm.transactions.transaction
+ with TransactionContextManager(tx) as api:
+ cache = api.tm.sys.dbs.db.load(name='dns.cache')
+ proxy = api.tm.sys.dbs.db.load(name='dns.proxy.__iter__')
+ dns = api.tm.sys.dns.load()
+
+ # Empty values can be supplied, but you cannot supply the
+ # None value, so we check for that specifically
+ if update['cache'] is not None:
+ cache.update(value=update['cache'])
+ if update['forwarders'] is not None:
+ proxy.update(value=update['forwarders'])
+ if update['dns'] is not None:
+ dns.update(**update['dns'])
+ return changed
+
+ def absent(self):
+ params = dict()
+ current = self.read()
+
+ # Temporary locations to hold the changed params
+ update = dict(
+ dns=None,
+ forwarders=None
+ )
+
+ nameservers = self.params['name_servers']
+ search_domains = self.params['search']
+ forwarders = self.params['forwarders']
+ check_mode = self.params['check_mode']
+
+ if forwarders and 'forwarders' in current:
+ set_current = set(current['forwarders'])
+ set_new = set(forwarders)
+
+ forwarders = set_current - set_new
+ if forwarders != set_current:
+ forwarders = list(forwarders)
+ params['forwarders'] = ' '.join(forwarders)
+
+ if params:
+ changed = True
+ self.cparams.update(camel_dict_to_snake_dict(params))
+ update['forwarders'] = params['forwarders']
+ params = dict()
+
+ if nameservers and 'name_servers' in current:
+ set_current = set(current['name_servers'])
+ set_new = set(nameservers)
+
+ nameservers = set_current - set_new
+ if nameservers != set_current:
+ params['nameServers'] = list(nameservers)
+
+ if search_domains and 'search' in current:
+ set_current = set(current['search'])
+ set_new = set(search_domains)
+
+ search_domains = set_current - set_new
+ if search_domains != set_current:
+ params['search'] = list(search_domains)
+
+ if params:
+ changed = True
+ self.cparams.update(camel_dict_to_snake_dict(params))
+ update['dns'] = params.copy()
+ params = dict()
+
+ if not self.cparams:
+ return False
+
+ if check_mode:
+ return changed
+
+ tx = self.api.tm.transactions.transaction
+ with TransactionContextManager(tx) as api:
+ proxy = api.tm.sys.dbs.db.load(name='dns.proxy.__iter__')
+ dns = api.tm.sys.dns.load()
+
+ if update['forwarders'] is not None:
+ proxy.update(value=update['forwarders'])
+ if update['dns'] is not None:
+ dns.update(**update['dns'])
+ return changed
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ cache=dict(required=False, choices=CACHE, default=None),
+ name_servers=dict(required=False, default=None, type='list'),
+ forwarders=dict(required=False, default=None, type='list'),
+ search=dict(required=False, default=None, type='list'),
+ ip_version=dict(required=False, default=None, choices=IP, type='int')
+ )
+ argument_spec.update(meta_args)
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=[REQUIRED],
+ supports_check_mode=True
+ )
+
+ try:
+ obj = BigIpDeviceDns(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_device_ntp.py b/lib/ansible/modules/extras/network/f5/bigip_device_ntp.py
new file mode 100644
index 0000000000..6dab16a3cb
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_device_ntp.py
@@ -0,0 +1,257 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_device_ntp
+short_description: Manage NTP servers on a BIG-IP
+description:
+ - Manage NTP servers on a BIG-IP
+version_added: "2.2"
+options:
+ ntp_servers:
+ description:
+ - A list of NTP servers to set on the device. At least one of C(ntp_servers)
+ or C(timezone) is required.
+ required: false
+ default: []
+ state:
+ description:
+ - The state of the NTP servers on the system. When C(present), guarantees
+ that the NTP servers are set on the system. When C(absent), removes the
+ specified NTP servers from the device configuration.
+ required: false
+ default: present
+ choices:
+ - absent
+ - present
+ timezone:
+ description:
+ - The timezone to set for NTP lookups. At least one of C(ntp_servers) or
+ C(timezone) is required.
+ default: UTC
+ required: false
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as pip
+ install f5-sdk.
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Set NTP server
+ bigip_device_ntp:
+ ntp_servers:
+ - "192.0.2.23"
+ password: "secret"
+ server: "lb.mydomain.com"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+
+- name: Set timezone
+ bigip_device_ntp:
+ password: "secret"
+ server: "lb.mydomain.com"
+ timezone: "America/Los_Angeles"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+ntp_servers:
+ description: The NTP servers that were set on the device
+ returned: changed
+ type: list
+ sample: ["192.0.2.23", "192.0.2.42"]
+timezone:
+ description: The timezone that was set on the device
+ returned: changed
+ type: string
+ sample: "true"
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+
+class BigIpDeviceNtp(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def flush(self):
+ result = dict()
+ changed = False
+ state = self.params['state']
+
+ try:
+ if state == "present":
+ changed = self.present()
+ elif state == "absent":
+ changed = self.absent()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ if 'servers' in self.cparams:
+ self.cparams['ntp_servers'] = self.cparams.pop('servers')
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+ def read(self):
+ """Read information and transform it
+
+ The values that are returned by BIG-IP in the f5-sdk can have encoding
+ attached to them as well as be completely missing in some cases.
+
+ Therefore, this method will transform the data from the BIG-IP into a
+ format that is more easily consumable by the rest of the class and the
+ parameters that are supported by the module.
+ """
+ p = dict()
+ r = self.api.tm.sys.ntp.load()
+
+ if hasattr(r, 'servers'):
+ # Deliberately using sets to supress duplicates
+ p['servers'] = set([str(x) for x in r.servers])
+ if hasattr(r, 'timezone'):
+ p['timezone'] = str(r.timezone)
+ return p
+
+ def present(self):
+ changed = False
+ params = dict()
+ current = self.read()
+
+ check_mode = self.params['check_mode']
+ ntp_servers = self.params['ntp_servers']
+ timezone = self.params['timezone']
+
+ # NTP servers can be set independently
+ if ntp_servers is not None:
+ if 'servers' in current:
+ items = set(ntp_servers)
+ if items != current['servers']:
+ params['servers'] = list(ntp_servers)
+ else:
+ params['servers'] = ntp_servers
+
+ # Timezone can be set independently
+ if timezone is not None:
+ if 'timezone' in current and current['timezone'] != timezone:
+ params['timezone'] = timezone
+
+ if params:
+ changed = True
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return changed
+ else:
+ return changed
+
+ r = self.api.tm.sys.ntp.load()
+ r.update(**params)
+ r.refresh()
+
+ return changed
+
+ def absent(self):
+ changed = False
+ params = dict()
+ current = self.read()
+
+ check_mode = self.params['check_mode']
+ ntp_servers = self.params['ntp_servers']
+
+ if not ntp_servers:
+ raise F5ModuleError(
+ "Absent can only be used when removing NTP servers"
+ )
+
+ if ntp_servers and 'servers' in current:
+ servers = current['servers']
+ new_servers = [x for x in servers if x not in ntp_servers]
+
+ if servers != new_servers:
+ params['servers'] = new_servers
+
+ if params:
+ changed = True
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return changed
+ else:
+ return changed
+
+ r = self.api.tm.sys.ntp.load()
+ r.update(**params)
+ r.refresh()
+ return changed
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ ntp_servers=dict(required=False, type='list', default=None),
+ timezone=dict(default=None, required=False)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=[
+ ['ntp_servers', 'timezone']
+ ],
+ supports_check_mode=True
+ )
+
+ try:
+ obj = BigIpDeviceNtp(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_device_sshd.py b/lib/ansible/modules/extras/network/f5/bigip_device_sshd.py
new file mode 100644
index 0000000000..e7a87a4e08
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_device_sshd.py
@@ -0,0 +1,344 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_device_sshd
+short_description: Manage the SSHD settings of a BIG-IP
+description:
+ - Manage the SSHD settings of a BIG-IP
+version_added: "2.2"
+options:
+ allow:
+ description:
+ - Specifies, if you have enabled SSH access, the IP address or address
+ range for other systems that can use SSH to communicate with this
+ system.
+ choices:
+ - all
+ - IP address, such as 172.27.1.10
+ - IP range, such as 172.27.*.* or 172.27.0.0/255.255.0.0
+ banner:
+ description:
+ - Whether to enable the banner or not.
+ required: false
+ choices:
+ - enabled
+ - disabled
+ banner_text:
+ description:
+ - Specifies the text to include on the pre-login banner that displays
+ when a user attempts to login to the system using SSH.
+ required: false
+ inactivity_timeout:
+ description:
+ - Specifies the number of seconds before inactivity causes an SSH
+ session to log out.
+ required: false
+ log_level:
+ description:
+ - Specifies the minimum SSHD message level to include in the system log.
+ choices:
+ - debug
+ - debug1
+ - debug2
+ - debug3
+ - error
+ - fatal
+ - info
+ - quiet
+ - verbose
+ login:
+ description:
+ - Specifies, when checked C(enabled), that the system accepts SSH
+ communications.
+ choices:
+ - enabled
+ - disabled
+ required: false
+ port:
+ description:
+ - Port that you want the SSH daemon to run on.
+ required: false
+notes:
+ - Requires the f5-sdk Python package on the host This is as easy as pip
+ install f5-sdk.
+ - Requires BIG-IP version 12.0.0 or greater
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Set the banner for the SSHD service from a string
+ bigip_device_sshd:
+ banner: "enabled"
+ banner_text: "banner text goes here"
+ password: "secret"
+ server: "lb.mydomain.com"
+ user: "admin"
+ delegate_to: localhost
+
+- name: Set the banner for the SSHD service from a file
+ bigip_device_sshd:
+ banner: "enabled"
+ banner_text: "{{ lookup('file', '/path/to/file') }}"
+ password: "secret"
+ server: "lb.mydomain.com"
+ user: "admin"
+ delegate_to: localhost
+
+- name: Set the SSHD service to run on port 2222
+ bigip_device_sshd:
+ password: "secret"
+ port: 2222
+ server: "lb.mydomain.com"
+ user: "admin"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+allow:
+ description: |
+ Specifies, if you have enabled SSH access, the IP address or address
+ range for other systems that can use SSH to communicate with this
+ system.
+ returned: changed
+ type: list
+ sample: "192.0.2.*"
+banner:
+ description: Whether the banner is enabled or not.
+ returned: changed
+ type: string
+ sample: "true"
+banner_text:
+ description: |
+ Specifies the text included on the pre-login banner that
+ displays when a user attempts to login to the system using SSH.
+ returned: changed and success
+ type: string
+ sample: "This is a corporate device. Connecting to it without..."
+inactivity_timeout:
+ description: |
+ The number of seconds before inactivity causes an SSH.
+ session to log out
+ returned: changed
+ type: int
+ sample: "10"
+log_level:
+ description: The minimum SSHD message level to include in the system log.
+ returned: changed
+ type: string
+ sample: "debug"
+login:
+ description: Specifies that the system accepts SSH communications or not.
+ return: changed
+ type: bool
+ sample: true
+port:
+ description: Port that you want the SSH daemon to run on.
+ return: changed
+ type: int
+ sample: 22
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+CHOICES = ['enabled', 'disabled']
+LEVELS = ['debug', 'debug1', 'debug2', 'debug3', 'error', 'fatal', 'info',
+ 'quiet', 'verbose']
+
+
+class BigIpDeviceSshd(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def update(self):
+ changed = False
+ current = self.read()
+ params = dict()
+
+ allow = self.params['allow']
+ banner = self.params['banner']
+ banner_text = self.params['banner_text']
+ timeout = self.params['inactivity_timeout']
+ log_level = self.params['log_level']
+ login = self.params['login']
+ port = self.params['port']
+ check_mode = self.params['check_mode']
+
+ if allow:
+ if 'allow' in current:
+ items = set(allow)
+ if items != current['allow']:
+ params['allow'] = list(items)
+ else:
+ params['allow'] = allow
+
+ if banner:
+ if 'banner' in current:
+ if banner != current['banner']:
+ params['banner'] = banner
+ else:
+ params['banner'] = banner
+
+ if banner_text:
+ if 'banner_text' in current:
+ if banner_text != current['banner_text']:
+ params['bannerText'] = banner_text
+ else:
+ params['bannerText'] = banner_text
+
+ if timeout:
+ if 'inactivity_timeout' in current:
+ if timeout != current['inactivity_timeout']:
+ params['inactivityTimeout'] = timeout
+ else:
+ params['inactivityTimeout'] = timeout
+
+ if log_level:
+ if 'log_level' in current:
+ if log_level != current['log_level']:
+ params['logLevel'] = log_level
+ else:
+ params['logLevel'] = log_level
+
+ if login:
+ if 'login' in current:
+ if login != current['login']:
+ params['login'] = login
+ else:
+ params['login'] = login
+
+ if port:
+ if 'port' in current:
+ if port != current['port']:
+ params['port'] = port
+ else:
+ params['port'] = port
+
+ if params:
+ changed = True
+ if check_mode:
+ return changed
+ self.cparams = camel_dict_to_snake_dict(params)
+ else:
+ return changed
+
+ r = self.api.tm.sys.sshd.load()
+ r.update(**params)
+ r.refresh()
+
+ return changed
+
+ def read(self):
+ """Read information and transform it
+
+ The values that are returned by BIG-IP in the f5-sdk can have encoding
+ attached to them as well as be completely missing in some cases.
+
+ Therefore, this method will transform the data from the BIG-IP into a
+ format that is more easily consumable by the rest of the class and the
+ parameters that are supported by the module.
+ """
+ p = dict()
+ r = self.api.tm.sys.sshd.load()
+
+ if hasattr(r, 'allow'):
+ # Deliberately using sets to supress duplicates
+ p['allow'] = set([str(x) for x in r.allow])
+ if hasattr(r, 'banner'):
+ p['banner'] = str(r.banner)
+ if hasattr(r, 'bannerText'):
+ p['banner_text'] = str(r.bannerText)
+ if hasattr(r, 'inactivityTimeout'):
+ p['inactivity_timeout'] = str(r.inactivityTimeout)
+ if hasattr(r, 'logLevel'):
+ p['log_level'] = str(r.logLevel)
+ if hasattr(r, 'login'):
+ p['login'] = str(r.login)
+ if hasattr(r, 'port'):
+ p['port'] = int(r.port)
+ return p
+
+ def flush(self):
+ result = dict()
+ changed = False
+
+ try:
+ changed = self.update()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ allow=dict(required=False, default=None, type='list'),
+ banner=dict(required=False, default=None, choices=CHOICES),
+ banner_text=dict(required=False, default=None),
+ inactivity_timeout=dict(required=False, default=None, type='int'),
+ log_level=dict(required=False, default=None, choices=LEVELS),
+ login=dict(required=False, default=None, choices=CHOICES),
+ port=dict(required=False, default=None, type='int'),
+ state=dict(default='present', choices=['present'])
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ try:
+ obj = BigIpDeviceSshd(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_facts.py b/lib/ansible/modules/extras/network/f5/bigip_facts.py
new file mode 100644
index 0000000000..dc6c6b7d1d
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_facts.py
@@ -0,0 +1,1724 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2013, Matt Hite <mhite@hotmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_facts
+short_description: Collect facts from F5 BIG-IP devices
+description:
+ - Collect facts from F5 BIG-IP devices via iControl SOAP API
+version_added: "1.6"
+author:
+ - Matt Hite (@mhite)
+ - Tim Rupp (@caphrim007)
+notes:
+ - Requires BIG-IP software version >= 11.4
+ - F5 developed module 'bigsuds' required (see http://devcentral.f5.com)
+ - Best run as a local_action in your playbook
+ - Tested with manager and above account privilege level
+ - C(provision) facts were added in 2.2
+requirements:
+ - bigsuds
+options:
+ session:
+ description:
+ - BIG-IP session support; may be useful to avoid concurrency
+ issues in certain circumstances.
+ required: false
+ default: true
+ choices: []
+ aliases: []
+ include:
+ description:
+ - Fact category or list of categories to collect
+ required: true
+ default: null
+ choices:
+ - address_class
+ - certificate
+ - client_ssl_profile
+ - device
+ - device_group
+ - interface
+ - key
+ - node
+ - pool
+ - provision
+ - rule
+ - self_ip
+ - software
+ - system_info
+ - traffic_group
+ - trunk
+ - virtual_address
+ - virtual_server
+ - vlan
+ aliases: []
+ filter:
+ description:
+ - Shell-style glob matching string used to filter fact keys. Not
+ applicable for software, provision, and system_info fact categories.
+ required: false
+ default: null
+ choices: []
+ aliases: []
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+- name: Collect BIG-IP facts
+ bigip_facts:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ include: "interface,vlan"
+ delegate_to: localhost
+'''
+
+try:
+ from suds import MethodNotFound, WebFault
+except ImportError:
+ bigsuds_found = False
+else:
+ bigsuds_found = True
+
+import fnmatch
+import re
+import traceback
+
+
+class F5(object):
+ """F5 iControl class.
+
+ F5 BIG-IP iControl API class.
+
+ Attributes:
+ api: iControl API instance.
+ """
+
+ def __init__(self, host, user, password, session=False, validate_certs=True, port=443):
+ self.api = bigip_api(host, user, password, validate_certs, port)
+ if session:
+ self.start_session()
+
+ def start_session(self):
+ self.api = self.api.with_session_id()
+
+ def get_api(self):
+ return self.api
+
+ def set_recursive_query_state(self, state):
+ self.api.System.Session.set_recursive_query_state(state)
+
+ def get_recursive_query_state(self):
+ return self.api.System.Session.get_recursive_query_state()
+
+ def enable_recursive_query_state(self):
+ self.set_recursive_query_state('STATE_ENABLED')
+
+ def disable_recursive_query_state(self):
+ self.set_recursive_query_state('STATE_DISABLED')
+
+ def set_active_folder(self, folder):
+ self.api.System.Session.set_active_folder(folder=folder)
+
+ def get_active_folder(self):
+ return self.api.System.Session.get_active_folder()
+
+
+class Interfaces(object):
+ """Interfaces class.
+
+ F5 BIG-IP interfaces class.
+
+ Attributes:
+ api: iControl API instance.
+ interfaces: A list of BIG-IP interface names.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.interfaces = api.Networking.Interfaces.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.interfaces = filter(re_filter.search, self.interfaces)
+
+ def get_list(self):
+ return self.interfaces
+
+ def get_active_media(self):
+ return self.api.Networking.Interfaces.get_active_media(self.interfaces)
+
+ def get_actual_flow_control(self):
+ return self.api.Networking.Interfaces.get_actual_flow_control(self.interfaces)
+
+ def get_bundle_state(self):
+ return self.api.Networking.Interfaces.get_bundle_state(self.interfaces)
+
+ def get_description(self):
+ return self.api.Networking.Interfaces.get_description(self.interfaces)
+
+ def get_dual_media_state(self):
+ return self.api.Networking.Interfaces.get_dual_media_state(self.interfaces)
+
+ def get_enabled_state(self):
+ return self.api.Networking.Interfaces.get_enabled_state(self.interfaces)
+
+ def get_if_index(self):
+ return self.api.Networking.Interfaces.get_if_index(self.interfaces)
+
+ def get_learning_mode(self):
+ return self.api.Networking.Interfaces.get_learning_mode(self.interfaces)
+
+ def get_lldp_admin_status(self):
+ return self.api.Networking.Interfaces.get_lldp_admin_status(self.interfaces)
+
+ def get_lldp_tlvmap(self):
+ return self.api.Networking.Interfaces.get_lldp_tlvmap(self.interfaces)
+
+ def get_mac_address(self):
+ return self.api.Networking.Interfaces.get_mac_address(self.interfaces)
+
+ def get_media(self):
+ return self.api.Networking.Interfaces.get_media(self.interfaces)
+
+ def get_media_option(self):
+ return self.api.Networking.Interfaces.get_media_option(self.interfaces)
+
+ def get_media_option_sfp(self):
+ return self.api.Networking.Interfaces.get_media_option_sfp(self.interfaces)
+
+ def get_media_sfp(self):
+ return self.api.Networking.Interfaces.get_media_sfp(self.interfaces)
+
+ def get_media_speed(self):
+ return self.api.Networking.Interfaces.get_media_speed(self.interfaces)
+
+ def get_media_status(self):
+ return self.api.Networking.Interfaces.get_media_status(self.interfaces)
+
+ def get_mtu(self):
+ return self.api.Networking.Interfaces.get_mtu(self.interfaces)
+
+ def get_phy_master_slave_mode(self):
+ return self.api.Networking.Interfaces.get_phy_master_slave_mode(self.interfaces)
+
+ def get_prefer_sfp_state(self):
+ return self.api.Networking.Interfaces.get_prefer_sfp_state(self.interfaces)
+
+ def get_flow_control(self):
+ return self.api.Networking.Interfaces.get_requested_flow_control(self.interfaces)
+
+ def get_sflow_poll_interval(self):
+ return self.api.Networking.Interfaces.get_sflow_poll_interval(self.interfaces)
+
+ def get_sflow_poll_interval_global(self):
+ return self.api.Networking.Interfaces.get_sflow_poll_interval_global(self.interfaces)
+
+ def get_sfp_media_state(self):
+ return self.api.Networking.Interfaces.get_sfp_media_state(self.interfaces)
+
+ def get_stp_active_edge_port_state(self):
+ return self.api.Networking.Interfaces.get_stp_active_edge_port_state(self.interfaces)
+
+ def get_stp_enabled_state(self):
+ return self.api.Networking.Interfaces.get_stp_enabled_state(self.interfaces)
+
+ def get_stp_link_type(self):
+ return self.api.Networking.Interfaces.get_stp_link_type(self.interfaces)
+
+ def get_stp_protocol_detection_reset_state(self):
+ return self.api.Networking.Interfaces.get_stp_protocol_detection_reset_state(self.interfaces)
+
+
+class SelfIPs(object):
+ """Self IPs class.
+
+ F5 BIG-IP Self IPs class.
+
+ Attributes:
+ api: iControl API instance.
+ self_ips: List of self IPs.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.self_ips = api.Networking.SelfIPV2.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.self_ips = filter(re_filter.search, self.self_ips)
+
+ def get_list(self):
+ return self.self_ips
+
+ def get_address(self):
+ return self.api.Networking.SelfIPV2.get_address(self.self_ips)
+
+ def get_allow_access_list(self):
+ return self.api.Networking.SelfIPV2.get_allow_access_list(self.self_ips)
+
+ def get_description(self):
+ return self.api.Networking.SelfIPV2.get_description(self.self_ips)
+
+ def get_enforced_firewall_policy(self):
+ return self.api.Networking.SelfIPV2.get_enforced_firewall_policy(self.self_ips)
+
+ def get_floating_state(self):
+ return self.api.Networking.SelfIPV2.get_floating_state(self.self_ips)
+
+ def get_fw_rule(self):
+ return self.api.Networking.SelfIPV2.get_fw_rule(self.self_ips)
+
+ def get_netmask(self):
+ return self.api.Networking.SelfIPV2.get_netmask(self.self_ips)
+
+ def get_staged_firewall_policy(self):
+ return self.api.Networking.SelfIPV2.get_staged_firewall_policy(self.self_ips)
+
+ def get_traffic_group(self):
+ return self.api.Networking.SelfIPV2.get_traffic_group(self.self_ips)
+
+ def get_vlan(self):
+ return self.api.Networking.SelfIPV2.get_vlan(self.self_ips)
+
+ def get_is_traffic_group_inherited(self):
+ return self.api.Networking.SelfIPV2.is_traffic_group_inherited(self.self_ips)
+
+
+class Trunks(object):
+ """Trunks class.
+
+ F5 BIG-IP trunks class.
+
+ Attributes:
+ api: iControl API instance.
+ trunks: List of trunks.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.trunks = api.Networking.Trunk.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.trunks = filter(re_filter.search, self.trunks)
+
+ def get_list(self):
+ return self.trunks
+
+ def get_active_lacp_state(self):
+ return self.api.Networking.Trunk.get_active_lacp_state(self.trunks)
+
+ def get_configured_member_count(self):
+ return self.api.Networking.Trunk.get_configured_member_count(self.trunks)
+
+ def get_description(self):
+ return self.api.Networking.Trunk.get_description(self.trunks)
+
+ def get_distribution_hash_option(self):
+ return self.api.Networking.Trunk.get_distribution_hash_option(self.trunks)
+
+ def get_interface(self):
+ return self.api.Networking.Trunk.get_interface(self.trunks)
+
+ def get_lacp_enabled_state(self):
+ return self.api.Networking.Trunk.get_lacp_enabled_state(self.trunks)
+
+ def get_lacp_timeout_option(self):
+ return self.api.Networking.Trunk.get_lacp_timeout_option(self.trunks)
+
+ def get_link_selection_policy(self):
+ return self.api.Networking.Trunk.get_link_selection_policy(self.trunks)
+
+ def get_media_speed(self):
+ return self.api.Networking.Trunk.get_media_speed(self.trunks)
+
+ def get_media_status(self):
+ return self.api.Networking.Trunk.get_media_status(self.trunks)
+
+ def get_operational_member_count(self):
+ return self.api.Networking.Trunk.get_operational_member_count(self.trunks)
+
+ def get_stp_enabled_state(self):
+ return self.api.Networking.Trunk.get_stp_enabled_state(self.trunks)
+
+ def get_stp_protocol_detection_reset_state(self):
+ return self.api.Networking.Trunk.get_stp_protocol_detection_reset_state(self.trunks)
+
+
+class Vlans(object):
+ """Vlans class.
+
+ F5 BIG-IP Vlans class.
+
+ Attributes:
+ api: iControl API instance.
+ vlans: List of VLANs.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.vlans = api.Networking.VLAN.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.vlans = filter(re_filter.search, self.vlans)
+
+ def get_list(self):
+ return self.vlans
+
+ def get_auto_lasthop(self):
+ return self.api.Networking.VLAN.get_auto_lasthop(self.vlans)
+
+ def get_cmp_hash_algorithm(self):
+ return self.api.Networking.VLAN.get_cmp_hash_algorithm(self.vlans)
+
+ def get_description(self):
+ return self.api.Networking.VLAN.get_description(self.vlans)
+
+ def get_dynamic_forwarding(self):
+ return self.api.Networking.VLAN.get_dynamic_forwarding(self.vlans)
+
+ def get_failsafe_action(self):
+ return self.api.Networking.VLAN.get_failsafe_action(self.vlans)
+
+ def get_failsafe_state(self):
+ return self.api.Networking.VLAN.get_failsafe_state(self.vlans)
+
+ def get_failsafe_timeout(self):
+ return self.api.Networking.VLAN.get_failsafe_timeout(self.vlans)
+
+ def get_if_index(self):
+ return self.api.Networking.VLAN.get_if_index(self.vlans)
+
+ def get_learning_mode(self):
+ return self.api.Networking.VLAN.get_learning_mode(self.vlans)
+
+ def get_mac_masquerade_address(self):
+ return self.api.Networking.VLAN.get_mac_masquerade_address(self.vlans)
+
+ def get_member(self):
+ return self.api.Networking.VLAN.get_member(self.vlans)
+
+ def get_mtu(self):
+ return self.api.Networking.VLAN.get_mtu(self.vlans)
+
+ def get_sflow_poll_interval(self):
+ return self.api.Networking.VLAN.get_sflow_poll_interval(self.vlans)
+
+ def get_sflow_poll_interval_global(self):
+ return self.api.Networking.VLAN.get_sflow_poll_interval_global(self.vlans)
+
+ def get_sflow_sampling_rate(self):
+ return self.api.Networking.VLAN.get_sflow_sampling_rate(self.vlans)
+
+ def get_sflow_sampling_rate_global(self):
+ return self.api.Networking.VLAN.get_sflow_sampling_rate_global(self.vlans)
+
+ def get_source_check_state(self):
+ return self.api.Networking.VLAN.get_source_check_state(self.vlans)
+
+ def get_true_mac_address(self):
+ return self.api.Networking.VLAN.get_true_mac_address(self.vlans)
+
+ def get_vlan_id(self):
+ return self.api.Networking.VLAN.get_vlan_id(self.vlans)
+
+
+class Software(object):
+ """Software class.
+
+ F5 BIG-IP software class.
+
+ Attributes:
+ api: iControl API instance.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def get_all_software_status(self):
+ return self.api.System.SoftwareManagement.get_all_software_status()
+
+
+class VirtualServers(object):
+ """Virtual servers class.
+
+ F5 BIG-IP virtual servers class.
+
+ Attributes:
+ api: iControl API instance.
+ virtual_servers: List of virtual servers.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.virtual_servers = api.LocalLB.VirtualServer.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.virtual_servers = filter(re_filter.search, self.virtual_servers)
+
+ def get_list(self):
+ return self.virtual_servers
+
+ def get_actual_hardware_acceleration(self):
+ return self.api.LocalLB.VirtualServer.get_actual_hardware_acceleration(self.virtual_servers)
+
+ def get_authentication_profile(self):
+ return self.api.LocalLB.VirtualServer.get_authentication_profile(self.virtual_servers)
+
+ def get_auto_lasthop(self):
+ return self.api.LocalLB.VirtualServer.get_auto_lasthop(self.virtual_servers)
+
+ def get_bw_controller_policy(self):
+ return self.api.LocalLB.VirtualServer.get_bw_controller_policy(self.virtual_servers)
+
+ def get_clone_pool(self):
+ return self.api.LocalLB.VirtualServer.get_clone_pool(self.virtual_servers)
+
+ def get_cmp_enable_mode(self):
+ return self.api.LocalLB.VirtualServer.get_cmp_enable_mode(self.virtual_servers)
+
+ def get_connection_limit(self):
+ return self.api.LocalLB.VirtualServer.get_connection_limit(self.virtual_servers)
+
+ def get_connection_mirror_state(self):
+ return self.api.LocalLB.VirtualServer.get_connection_mirror_state(self.virtual_servers)
+
+ def get_default_pool_name(self):
+ return self.api.LocalLB.VirtualServer.get_default_pool_name(self.virtual_servers)
+
+ def get_description(self):
+ return self.api.LocalLB.VirtualServer.get_description(self.virtual_servers)
+
+ def get_destination(self):
+ return self.api.LocalLB.VirtualServer.get_destination_v2(self.virtual_servers)
+
+ def get_enabled_state(self):
+ return self.api.LocalLB.VirtualServer.get_enabled_state(self.virtual_servers)
+
+ def get_enforced_firewall_policy(self):
+ return self.api.LocalLB.VirtualServer.get_enforced_firewall_policy(self.virtual_servers)
+
+ def get_fallback_persistence_profile(self):
+ return self.api.LocalLB.VirtualServer.get_fallback_persistence_profile(self.virtual_servers)
+
+ def get_fw_rule(self):
+ return self.api.LocalLB.VirtualServer.get_fw_rule(self.virtual_servers)
+
+ def get_gtm_score(self):
+ return self.api.LocalLB.VirtualServer.get_gtm_score(self.virtual_servers)
+
+ def get_last_hop_pool(self):
+ return self.api.LocalLB.VirtualServer.get_last_hop_pool(self.virtual_servers)
+
+ def get_nat64_state(self):
+ return self.api.LocalLB.VirtualServer.get_nat64_state(self.virtual_servers)
+
+ def get_object_status(self):
+ return self.api.LocalLB.VirtualServer.get_object_status(self.virtual_servers)
+
+ def get_persistence_profile(self):
+ return self.api.LocalLB.VirtualServer.get_persistence_profile(self.virtual_servers)
+
+ def get_profile(self):
+ return self.api.LocalLB.VirtualServer.get_profile(self.virtual_servers)
+
+ def get_protocol(self):
+ return self.api.LocalLB.VirtualServer.get_protocol(self.virtual_servers)
+
+ def get_rate_class(self):
+ return self.api.LocalLB.VirtualServer.get_rate_class(self.virtual_servers)
+
+ def get_rate_limit(self):
+ return self.api.LocalLB.VirtualServer.get_rate_limit(self.virtual_servers)
+
+ def get_rate_limit_destination_mask(self):
+ return self.api.LocalLB.VirtualServer.get_rate_limit_destination_mask(self.virtual_servers)
+
+ def get_rate_limit_mode(self):
+ return self.api.LocalLB.VirtualServer.get_rate_limit_mode(self.virtual_servers)
+
+ def get_rate_limit_source_mask(self):
+ return self.api.LocalLB.VirtualServer.get_rate_limit_source_mask(self.virtual_servers)
+
+ def get_related_rule(self):
+ return self.api.LocalLB.VirtualServer.get_related_rule(self.virtual_servers)
+
+ def get_rule(self):
+ return self.api.LocalLB.VirtualServer.get_rule(self.virtual_servers)
+
+ def get_security_log_profile(self):
+ return self.api.LocalLB.VirtualServer.get_security_log_profile(self.virtual_servers)
+
+ def get_snat_pool(self):
+ return self.api.LocalLB.VirtualServer.get_snat_pool(self.virtual_servers)
+
+ def get_snat_type(self):
+ return self.api.LocalLB.VirtualServer.get_snat_type(self.virtual_servers)
+
+ def get_source_address(self):
+ return self.api.LocalLB.VirtualServer.get_source_address(self.virtual_servers)
+
+ def get_source_address_translation_lsn_pool(self):
+ return self.api.LocalLB.VirtualServer.get_source_address_translation_lsn_pool(self.virtual_servers)
+
+ def get_source_address_translation_snat_pool(self):
+ return self.api.LocalLB.VirtualServer.get_source_address_translation_snat_pool(self.virtual_servers)
+
+ def get_source_address_translation_type(self):
+ return self.api.LocalLB.VirtualServer.get_source_address_translation_type(self.virtual_servers)
+
+ def get_source_port_behavior(self):
+ return self.api.LocalLB.VirtualServer.get_source_port_behavior(self.virtual_servers)
+
+ def get_staged_firewall_policy(self):
+ return self.api.LocalLB.VirtualServer.get_staged_firewall_policy(self.virtual_servers)
+
+ def get_translate_address_state(self):
+ return self.api.LocalLB.VirtualServer.get_translate_address_state(self.virtual_servers)
+
+ def get_translate_port_state(self):
+ return self.api.LocalLB.VirtualServer.get_translate_port_state(self.virtual_servers)
+
+ def get_type(self):
+ return self.api.LocalLB.VirtualServer.get_type(self.virtual_servers)
+
+ def get_vlan(self):
+ return self.api.LocalLB.VirtualServer.get_vlan(self.virtual_servers)
+
+ def get_wildmask(self):
+ return self.api.LocalLB.VirtualServer.get_wildmask(self.virtual_servers)
+
+
+class Pools(object):
+ """Pools class.
+
+ F5 BIG-IP pools class.
+
+ Attributes:
+ api: iControl API instance.
+ pool_names: List of pool names.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.pool_names = api.LocalLB.Pool.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.pool_names = filter(re_filter.search, self.pool_names)
+
+ def get_list(self):
+ return self.pool_names
+
+ def get_action_on_service_down(self):
+ return self.api.LocalLB.Pool.get_action_on_service_down(self.pool_names)
+
+ def get_active_member_count(self):
+ return self.api.LocalLB.Pool.get_active_member_count(self.pool_names)
+
+ def get_aggregate_dynamic_ratio(self):
+ return self.api.LocalLB.Pool.get_aggregate_dynamic_ratio(self.pool_names)
+
+ def get_allow_nat_state(self):
+ return self.api.LocalLB.Pool.get_allow_nat_state(self.pool_names)
+
+ def get_allow_snat_state(self):
+ return self.api.LocalLB.Pool.get_allow_snat_state(self.pool_names)
+
+ def get_client_ip_tos(self):
+ return self.api.LocalLB.Pool.get_client_ip_tos(self.pool_names)
+
+ def get_client_link_qos(self):
+ return self.api.LocalLB.Pool.get_client_link_qos(self.pool_names)
+
+ def get_description(self):
+ return self.api.LocalLB.Pool.get_description(self.pool_names)
+
+ def get_gateway_failsafe_device(self):
+ return self.api.LocalLB.Pool.get_gateway_failsafe_device(self.pool_names)
+
+ def get_ignore_persisted_weight_state(self):
+ return self.api.LocalLB.Pool.get_ignore_persisted_weight_state(self.pool_names)
+
+ def get_lb_method(self):
+ return self.api.LocalLB.Pool.get_lb_method(self.pool_names)
+
+ def get_member(self):
+ return self.api.LocalLB.Pool.get_member_v2(self.pool_names)
+
+ def get_minimum_active_member(self):
+ return self.api.LocalLB.Pool.get_minimum_active_member(self.pool_names)
+
+ def get_minimum_up_member(self):
+ return self.api.LocalLB.Pool.get_minimum_up_member(self.pool_names)
+
+ def get_minimum_up_member_action(self):
+ return self.api.LocalLB.Pool.get_minimum_up_member_action(self.pool_names)
+
+ def get_minimum_up_member_enabled_state(self):
+ return self.api.LocalLB.Pool.get_minimum_up_member_enabled_state(self.pool_names)
+
+ def get_monitor_association(self):
+ return self.api.LocalLB.Pool.get_monitor_association(self.pool_names)
+
+ def get_monitor_instance(self):
+ return self.api.LocalLB.Pool.get_monitor_instance(self.pool_names)
+
+ def get_object_status(self):
+ return self.api.LocalLB.Pool.get_object_status(self.pool_names)
+
+ def get_profile(self):
+ return self.api.LocalLB.Pool.get_profile(self.pool_names)
+
+ def get_queue_depth_limit(self):
+ return self.api.LocalLB.Pool.get_queue_depth_limit(self.pool_names)
+
+ def get_queue_on_connection_limit_state(self):
+ return self.api.LocalLB.Pool.get_queue_on_connection_limit_state(self.pool_names)
+
+ def get_queue_time_limit(self):
+ return self.api.LocalLB.Pool.get_queue_time_limit(self.pool_names)
+
+ def get_reselect_tries(self):
+ return self.api.LocalLB.Pool.get_reselect_tries(self.pool_names)
+
+ def get_server_ip_tos(self):
+ return self.api.LocalLB.Pool.get_server_ip_tos(self.pool_names)
+
+ def get_server_link_qos(self):
+ return self.api.LocalLB.Pool.get_server_link_qos(self.pool_names)
+
+ def get_simple_timeout(self):
+ return self.api.LocalLB.Pool.get_simple_timeout(self.pool_names)
+
+ def get_slow_ramp_time(self):
+ return self.api.LocalLB.Pool.get_slow_ramp_time(self.pool_names)
+
+
+class Devices(object):
+ """Devices class.
+
+ F5 BIG-IP devices class.
+
+ Attributes:
+ api: iControl API instance.
+ devices: List of devices.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.devices = api.Management.Device.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.devices = filter(re_filter.search, self.devices)
+
+ def get_list(self):
+ return self.devices
+
+ def get_active_modules(self):
+ return self.api.Management.Device.get_active_modules(self.devices)
+
+ def get_base_mac_address(self):
+ return self.api.Management.Device.get_base_mac_address(self.devices)
+
+ def get_blade_addresses(self):
+ return self.api.Management.Device.get_blade_addresses(self.devices)
+
+ def get_build(self):
+ return self.api.Management.Device.get_build(self.devices)
+
+ def get_chassis_id(self):
+ return self.api.Management.Device.get_chassis_id(self.devices)
+
+ def get_chassis_type(self):
+ return self.api.Management.Device.get_chassis_type(self.devices)
+
+ def get_comment(self):
+ return self.api.Management.Device.get_comment(self.devices)
+
+ def get_configsync_address(self):
+ return self.api.Management.Device.get_configsync_address(self.devices)
+
+ def get_contact(self):
+ return self.api.Management.Device.get_contact(self.devices)
+
+ def get_description(self):
+ return self.api.Management.Device.get_description(self.devices)
+
+ def get_edition(self):
+ return self.api.Management.Device.get_edition(self.devices)
+
+ def get_failover_state(self):
+ return self.api.Management.Device.get_failover_state(self.devices)
+
+ def get_local_device(self):
+ return self.api.Management.Device.get_local_device()
+
+ def get_hostname(self):
+ return self.api.Management.Device.get_hostname(self.devices)
+
+ def get_inactive_modules(self):
+ return self.api.Management.Device.get_inactive_modules(self.devices)
+
+ def get_location(self):
+ return self.api.Management.Device.get_location(self.devices)
+
+ def get_management_address(self):
+ return self.api.Management.Device.get_management_address(self.devices)
+
+ def get_marketing_name(self):
+ return self.api.Management.Device.get_marketing_name(self.devices)
+
+ def get_multicast_address(self):
+ return self.api.Management.Device.get_multicast_address(self.devices)
+
+ def get_optional_modules(self):
+ return self.api.Management.Device.get_optional_modules(self.devices)
+
+ def get_platform_id(self):
+ return self.api.Management.Device.get_platform_id(self.devices)
+
+ def get_primary_mirror_address(self):
+ return self.api.Management.Device.get_primary_mirror_address(self.devices)
+
+ def get_product(self):
+ return self.api.Management.Device.get_product(self.devices)
+
+ def get_secondary_mirror_address(self):
+ return self.api.Management.Device.get_secondary_mirror_address(self.devices)
+
+ def get_software_version(self):
+ return self.api.Management.Device.get_software_version(self.devices)
+
+ def get_timelimited_modules(self):
+ return self.api.Management.Device.get_timelimited_modules(self.devices)
+
+ def get_timezone(self):
+ return self.api.Management.Device.get_timezone(self.devices)
+
+ def get_unicast_addresses(self):
+ return self.api.Management.Device.get_unicast_addresses(self.devices)
+
+
+class DeviceGroups(object):
+ """Device groups class.
+
+ F5 BIG-IP device groups class.
+
+ Attributes:
+ api: iControl API instance.
+ device_groups: List of device groups.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.device_groups = api.Management.DeviceGroup.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.device_groups = filter(re_filter.search, self.device_groups)
+
+ def get_list(self):
+ return self.device_groups
+
+ def get_all_preferred_active(self):
+ return self.api.Management.DeviceGroup.get_all_preferred_active(self.device_groups)
+
+ def get_autosync_enabled_state(self):
+ return self.api.Management.DeviceGroup.get_autosync_enabled_state(self.device_groups)
+
+ def get_description(self):
+ return self.api.Management.DeviceGroup.get_description(self.device_groups)
+
+ def get_device(self):
+ return self.api.Management.DeviceGroup.get_device(self.device_groups)
+
+ def get_full_load_on_sync_state(self):
+ return self.api.Management.DeviceGroup.get_full_load_on_sync_state(self.device_groups)
+
+ def get_incremental_config_sync_size_maximum(self):
+ return self.api.Management.DeviceGroup.get_incremental_config_sync_size_maximum(self.device_groups)
+
+ def get_network_failover_enabled_state(self):
+ return self.api.Management.DeviceGroup.get_network_failover_enabled_state(self.device_groups)
+
+ def get_sync_status(self):
+ return self.api.Management.DeviceGroup.get_sync_status(self.device_groups)
+
+ def get_type(self):
+ return self.api.Management.DeviceGroup.get_type(self.device_groups)
+
+
+class TrafficGroups(object):
+ """Traffic groups class.
+
+ F5 BIG-IP traffic groups class.
+
+ Attributes:
+ api: iControl API instance.
+ traffic_groups: List of traffic groups.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.traffic_groups = api.Management.TrafficGroup.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.traffic_groups = filter(re_filter.search, self.traffic_groups)
+
+ def get_list(self):
+ return self.traffic_groups
+
+ def get_auto_failback_enabled_state(self):
+ return self.api.Management.TrafficGroup.get_auto_failback_enabled_state(self.traffic_groups)
+
+ def get_auto_failback_time(self):
+ return self.api.Management.TrafficGroup.get_auto_failback_time(self.traffic_groups)
+
+ def get_default_device(self):
+ return self.api.Management.TrafficGroup.get_default_device(self.traffic_groups)
+
+ def get_description(self):
+ return self.api.Management.TrafficGroup.get_description(self.traffic_groups)
+
+ def get_ha_load_factor(self):
+ return self.api.Management.TrafficGroup.get_ha_load_factor(self.traffic_groups)
+
+ def get_ha_order(self):
+ return self.api.Management.TrafficGroup.get_ha_order(self.traffic_groups)
+
+ def get_is_floating(self):
+ return self.api.Management.TrafficGroup.get_is_floating(self.traffic_groups)
+
+ def get_mac_masquerade_address(self):
+ return self.api.Management.TrafficGroup.get_mac_masquerade_address(self.traffic_groups)
+
+ def get_unit_id(self):
+ return self.api.Management.TrafficGroup.get_unit_id(self.traffic_groups)
+
+
+class Rules(object):
+ """Rules class.
+
+ F5 BIG-IP iRules class.
+
+ Attributes:
+ api: iControl API instance.
+ rules: List of iRules.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.rules = api.LocalLB.Rule.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.traffic_groups = filter(re_filter.search, self.rules)
+
+ def get_list(self):
+ return self.rules
+
+ def get_description(self):
+ return self.api.LocalLB.Rule.get_description(rule_names=self.rules)
+
+ def get_ignore_vertification(self):
+ return self.api.LocalLB.Rule.get_ignore_vertification(rule_names=self.rules)
+
+ def get_verification_status(self):
+ return self.api.LocalLB.Rule.get_verification_status_v2(rule_names=self.rules)
+
+ def get_definition(self):
+ return [x['rule_definition'] for x in self.api.LocalLB.Rule.query_rule(rule_names=self.rules)]
+
+
+class Nodes(object):
+ """Nodes class.
+
+ F5 BIG-IP nodes class.
+
+ Attributes:
+ api: iControl API instance.
+ nodes: List of nodes.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.nodes = api.LocalLB.NodeAddressV2.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.nodes = filter(re_filter.search, self.nodes)
+
+ def get_list(self):
+ return self.nodes
+
+ def get_address(self):
+ return self.api.LocalLB.NodeAddressV2.get_address(nodes=self.nodes)
+
+ def get_connection_limit(self):
+ return self.api.LocalLB.NodeAddressV2.get_connection_limit(nodes=self.nodes)
+
+ def get_description(self):
+ return self.api.LocalLB.NodeAddressV2.get_description(nodes=self.nodes)
+
+ def get_dynamic_ratio(self):
+ return self.api.LocalLB.NodeAddressV2.get_dynamic_ratio_v2(nodes=self.nodes)
+
+ def get_monitor_instance(self):
+ return self.api.LocalLB.NodeAddressV2.get_monitor_instance(nodes=self.nodes)
+
+ def get_monitor_rule(self):
+ return self.api.LocalLB.NodeAddressV2.get_monitor_rule(nodes=self.nodes)
+
+ def get_monitor_status(self):
+ return self.api.LocalLB.NodeAddressV2.get_monitor_status(nodes=self.nodes)
+
+ def get_object_status(self):
+ return self.api.LocalLB.NodeAddressV2.get_object_status(nodes=self.nodes)
+
+ def get_rate_limit(self):
+ return self.api.LocalLB.NodeAddressV2.get_rate_limit(nodes=self.nodes)
+
+ def get_ratio(self):
+ return self.api.LocalLB.NodeAddressV2.get_ratio(nodes=self.nodes)
+
+ def get_session_status(self):
+ return self.api.LocalLB.NodeAddressV2.get_session_status(nodes=self.nodes)
+
+
+class VirtualAddresses(object):
+ """Virtual addresses class.
+
+ F5 BIG-IP virtual addresses class.
+
+ Attributes:
+ api: iControl API instance.
+ virtual_addresses: List of virtual addresses.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.virtual_addresses = api.LocalLB.VirtualAddressV2.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.virtual_addresses = filter(re_filter.search, self.virtual_addresses)
+
+ def get_list(self):
+ return self.virtual_addresses
+
+ def get_address(self):
+ return self.api.LocalLB.VirtualAddressV2.get_address(self.virtual_addresses)
+
+ def get_arp_state(self):
+ return self.api.LocalLB.VirtualAddressV2.get_arp_state(self.virtual_addresses)
+
+ def get_auto_delete_state(self):
+ return self.api.LocalLB.VirtualAddressV2.get_auto_delete_state(self.virtual_addresses)
+
+ def get_connection_limit(self):
+ return self.api.LocalLB.VirtualAddressV2.get_connection_limit(self.virtual_addresses)
+
+ def get_description(self):
+ return self.api.LocalLB.VirtualAddressV2.get_description(self.virtual_addresses)
+
+ def get_enabled_state(self):
+ return self.api.LocalLB.VirtualAddressV2.get_enabled_state(self.virtual_addresses)
+
+ def get_icmp_echo_state(self):
+ return self.api.LocalLB.VirtualAddressV2.get_icmp_echo_state(self.virtual_addresses)
+
+ def get_is_floating_state(self):
+ return self.api.LocalLB.VirtualAddressV2.get_is_floating_state(self.virtual_addresses)
+
+ def get_netmask(self):
+ return self.api.LocalLB.VirtualAddressV2.get_netmask(self.virtual_addresses)
+
+ def get_object_status(self):
+ return self.api.LocalLB.VirtualAddressV2.get_object_status(self.virtual_addresses)
+
+ def get_route_advertisement_state(self):
+ return self.api.LocalLB.VirtualAddressV2.get_route_advertisement_state(self.virtual_addresses)
+
+ def get_traffic_group(self):
+ return self.api.LocalLB.VirtualAddressV2.get_traffic_group(self.virtual_addresses)
+
+
+class AddressClasses(object):
+ """Address group/class class.
+
+ F5 BIG-IP address group/class class.
+
+ Attributes:
+ api: iControl API instance.
+ address_classes: List of address classes.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.address_classes = api.LocalLB.Class.get_address_class_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.address_classes = filter(re_filter.search, self.address_classes)
+
+ def get_list(self):
+ return self.address_classes
+
+ def get_address_class(self):
+ key = self.api.LocalLB.Class.get_address_class(self.address_classes)
+ value = self.api.LocalLB.Class.get_address_class_member_data_value(key)
+ result = list(map(zip, [x['members'] for x in key], value))
+ return result
+
+ def get_description(self):
+ return self.api.LocalLB.Class.get_description(self.address_classes)
+
+
+class Certificates(object):
+ """Certificates class.
+
+ F5 BIG-IP certificates class.
+
+ Attributes:
+ api: iControl API instance.
+ certificates: List of certificate identifiers.
+ certificate_list: List of certificate information structures.
+ """
+
+ def __init__(self, api, regex=None, mode="MANAGEMENT_MODE_DEFAULT"):
+ self.api = api
+ self.certificate_list = api.Management.KeyCertificate.get_certificate_list(mode=mode)
+ self.certificates = [x['certificate']['cert_info']['id'] for x in self.certificate_list]
+ if regex:
+ re_filter = re.compile(regex)
+ self.certificates = filter(re_filter.search, self.certificates)
+ self.certificate_list = [x for x in self.certificate_list if x['certificate']['cert_info']['id'] in self.certificates]
+
+ def get_list(self):
+ return self.certificates
+
+ def get_certificate_list(self):
+ return self.certificate_list
+
+
+class Keys(object):
+ """Keys class.
+
+ F5 BIG-IP keys class.
+
+ Attributes:
+ api: iControl API instance.
+ keys: List of key identifiers.
+ key_list: List of key information structures.
+ """
+
+ def __init__(self, api, regex=None, mode="MANAGEMENT_MODE_DEFAULT"):
+ self.api = api
+ self.key_list = api.Management.KeyCertificate.get_key_list(mode=mode)
+ self.keys = [x['key_info']['id'] for x in self.key_list]
+ if regex:
+ re_filter = re.compile(regex)
+ self.keys = filter(re_filter.search, self.keys)
+ self.key_list = [x for x in self.key_list if x['key_info']['id'] in self.keys]
+
+ def get_list(self):
+ return self.keys
+
+ def get_key_list(self):
+ return self.key_list
+
+
+class ProfileClientSSL(object):
+ """Client SSL profiles class.
+
+ F5 BIG-IP client SSL profiles class.
+
+ Attributes:
+ api: iControl API instance.
+ profiles: List of client SSL profiles.
+ """
+
+ def __init__(self, api, regex=None):
+ self.api = api
+ self.profiles = api.LocalLB.ProfileClientSSL.get_list()
+ if regex:
+ re_filter = re.compile(regex)
+ self.profiles = filter(re_filter.search, self.profiles)
+
+ def get_list(self):
+ return self.profiles
+
+ def get_alert_timeout(self):
+ return self.api.LocalLB.ProfileClientSSL.get_alert_timeout(self.profiles)
+
+ def get_allow_nonssl_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_allow_nonssl_state(self.profiles)
+
+ def get_authenticate_depth(self):
+ return self.api.LocalLB.ProfileClientSSL.get_authenticate_depth(self.profiles)
+
+ def get_authenticate_once_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_authenticate_once_state(self.profiles)
+
+ def get_ca_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_ca_file_v2(self.profiles)
+
+ def get_cache_size(self):
+ return self.api.LocalLB.ProfileClientSSL.get_cache_size(self.profiles)
+
+ def get_cache_timeout(self):
+ return self.api.LocalLB.ProfileClientSSL.get_cache_timeout(self.profiles)
+
+ def get_certificate_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_certificate_file_v2(self.profiles)
+
+ def get_chain_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_chain_file_v2(self.profiles)
+
+ def get_cipher_list(self):
+ return self.api.LocalLB.ProfileClientSSL.get_cipher_list(self.profiles)
+
+ def get_client_certificate_ca_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_client_certificate_ca_file_v2(self.profiles)
+
+ def get_crl_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_crl_file_v2(self.profiles)
+
+ def get_default_profile(self):
+ return self.api.LocalLB.ProfileClientSSL.get_default_profile(self.profiles)
+
+ def get_description(self):
+ return self.api.LocalLB.ProfileClientSSL.get_description(self.profiles)
+
+ def get_forward_proxy_ca_certificate_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_ca_certificate_file(self.profiles)
+
+ def get_forward_proxy_ca_key_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_ca_key_file(self.profiles)
+
+ def get_forward_proxy_ca_passphrase(self):
+ return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_ca_passphrase(self.profiles)
+
+ def get_forward_proxy_certificate_extension_include(self):
+ return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_certificate_extension_include(self.profiles)
+
+ def get_forward_proxy_certificate_lifespan(self):
+ return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_certificate_lifespan(self.profiles)
+
+ def get_forward_proxy_enabled_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_enabled_state(self.profiles)
+
+ def get_forward_proxy_lookup_by_ipaddr_port_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_forward_proxy_lookup_by_ipaddr_port_state(self.profiles)
+
+ def get_handshake_timeout(self):
+ return self.api.LocalLB.ProfileClientSSL.get_handshake_timeout(self.profiles)
+
+ def get_key_file(self):
+ return self.api.LocalLB.ProfileClientSSL.get_key_file_v2(self.profiles)
+
+ def get_modssl_emulation_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_modssl_emulation_state(self.profiles)
+
+ def get_passphrase(self):
+ return self.api.LocalLB.ProfileClientSSL.get_passphrase(self.profiles)
+
+ def get_peer_certification_mode(self):
+ return self.api.LocalLB.ProfileClientSSL.get_peer_certification_mode(self.profiles)
+
+ def get_profile_mode(self):
+ return self.api.LocalLB.ProfileClientSSL.get_profile_mode(self.profiles)
+
+ def get_renegotiation_maximum_record_delay(self):
+ return self.api.LocalLB.ProfileClientSSL.get_renegotiation_maximum_record_delay(self.profiles)
+
+ def get_renegotiation_period(self):
+ return self.api.LocalLB.ProfileClientSSL.get_renegotiation_period(self.profiles)
+
+ def get_renegotiation_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_renegotiation_state(self.profiles)
+
+ def get_renegotiation_throughput(self):
+ return self.api.LocalLB.ProfileClientSSL.get_renegotiation_throughput(self.profiles)
+
+ def get_retain_certificate_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_retain_certificate_state(self.profiles)
+
+ def get_secure_renegotiation_mode(self):
+ return self.api.LocalLB.ProfileClientSSL.get_secure_renegotiation_mode(self.profiles)
+
+ def get_server_name(self):
+ return self.api.LocalLB.ProfileClientSSL.get_server_name(self.profiles)
+
+ def get_session_ticket_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_session_ticket_state(self.profiles)
+
+ def get_sni_default_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_sni_default_state(self.profiles)
+
+ def get_sni_require_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_sni_require_state(self.profiles)
+
+ def get_ssl_option(self):
+ return self.api.LocalLB.ProfileClientSSL.get_ssl_option(self.profiles)
+
+ def get_strict_resume_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_strict_resume_state(self.profiles)
+
+ def get_unclean_shutdown_state(self):
+ return self.api.LocalLB.ProfileClientSSL.get_unclean_shutdown_state(self.profiles)
+
+ def get_is_base_profile(self):
+ return self.api.LocalLB.ProfileClientSSL.is_base_profile(self.profiles)
+
+ def get_is_system_profile(self):
+ return self.api.LocalLB.ProfileClientSSL.is_system_profile(self.profiles)
+
+
+class SystemInfo(object):
+ """System information class.
+
+ F5 BIG-IP system information class.
+
+ Attributes:
+ api: iControl API instance.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def get_base_mac_address(self):
+ return self.api.System.SystemInfo.get_base_mac_address()
+
+ def get_blade_temperature(self):
+ return self.api.System.SystemInfo.get_blade_temperature()
+
+ def get_chassis_slot_information(self):
+ return self.api.System.SystemInfo.get_chassis_slot_information()
+
+ def get_globally_unique_identifier(self):
+ return self.api.System.SystemInfo.get_globally_unique_identifier()
+
+ def get_group_id(self):
+ return self.api.System.SystemInfo.get_group_id()
+
+ def get_hardware_information(self):
+ return self.api.System.SystemInfo.get_hardware_information()
+
+ def get_marketing_name(self):
+ return self.api.System.SystemInfo.get_marketing_name()
+
+ def get_product_information(self):
+ return self.api.System.SystemInfo.get_product_information()
+
+ def get_pva_version(self):
+ return self.api.System.SystemInfo.get_pva_version()
+
+ def get_system_id(self):
+ return self.api.System.SystemInfo.get_system_id()
+
+ def get_system_information(self):
+ return self.api.System.SystemInfo.get_system_information()
+
+ def get_time(self):
+ return self.api.System.SystemInfo.get_time()
+
+ def get_time_zone(self):
+ return self.api.System.SystemInfo.get_time_zone()
+
+ def get_uptime(self):
+ return self.api.System.SystemInfo.get_uptime()
+
+
+class ProvisionInfo(object):
+ """Provision information class.
+
+ F5 BIG-IP provision information class.
+
+ Attributes:
+ api: iControl API instance.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def get_list(self):
+ result = []
+ list = self.api.Management.Provision.get_list()
+ for item in list:
+ item = item.lower().replace('tmos_module_', '')
+ result.append(item)
+ return result
+
+ def get_provisioned_list(self):
+ result = []
+ list = self.api.Management.Provision.get_provisioned_list()
+ for item in list:
+ item = item.lower().replace('tmos_module_', '')
+ result.append(item)
+ return result
+
+
+def generate_dict(api_obj, fields):
+ result_dict = {}
+ lists = []
+ supported_fields = []
+ if api_obj.get_list():
+ for field in fields:
+ try:
+ api_response = getattr(api_obj, "get_" + field)()
+ except (MethodNotFound, WebFault):
+ pass
+ else:
+ lists.append(api_response)
+ supported_fields.append(field)
+ for i, j in enumerate(api_obj.get_list()):
+ temp = {}
+ temp.update([(item[0], item[1][i]) for item in zip(supported_fields, lists)])
+ result_dict[j] = temp
+ return result_dict
+
+
+def generate_simple_dict(api_obj, fields):
+ result_dict = {}
+ for field in fields:
+ try:
+ api_response = getattr(api_obj, "get_" + field)()
+ except (MethodNotFound, WebFault):
+ pass
+ else:
+ result_dict[field] = api_response
+ return result_dict
+
+
+def generate_interface_dict(f5, regex):
+ interfaces = Interfaces(f5.get_api(), regex)
+ fields = ['active_media', 'actual_flow_control', 'bundle_state',
+ 'description', 'dual_media_state', 'enabled_state', 'if_index',
+ 'learning_mode', 'lldp_admin_status', 'lldp_tlvmap',
+ 'mac_address', 'media', 'media_option', 'media_option_sfp',
+ 'media_sfp', 'media_speed', 'media_status', 'mtu',
+ 'phy_master_slave_mode', 'prefer_sfp_state', 'flow_control',
+ 'sflow_poll_interval', 'sflow_poll_interval_global',
+ 'sfp_media_state', 'stp_active_edge_port_state',
+ 'stp_enabled_state', 'stp_link_type',
+ 'stp_protocol_detection_reset_state']
+ return generate_dict(interfaces, fields)
+
+
+def generate_self_ip_dict(f5, regex):
+ self_ips = SelfIPs(f5.get_api(), regex)
+ fields = ['address', 'allow_access_list', 'description',
+ 'enforced_firewall_policy', 'floating_state', 'fw_rule',
+ 'netmask', 'staged_firewall_policy', 'traffic_group',
+ 'vlan', 'is_traffic_group_inherited']
+ return generate_dict(self_ips, fields)
+
+
+def generate_trunk_dict(f5, regex):
+ trunks = Trunks(f5.get_api(), regex)
+ fields = ['active_lacp_state', 'configured_member_count', 'description',
+ 'distribution_hash_option', 'interface', 'lacp_enabled_state',
+ 'lacp_timeout_option', 'link_selection_policy', 'media_speed',
+ 'media_status', 'operational_member_count', 'stp_enabled_state',
+ 'stp_protocol_detection_reset_state']
+ return generate_dict(trunks, fields)
+
+
+def generate_vlan_dict(f5, regex):
+ vlans = Vlans(f5.get_api(), regex)
+ fields = ['auto_lasthop', 'cmp_hash_algorithm', 'description',
+ 'dynamic_forwarding', 'failsafe_action', 'failsafe_state',
+ 'failsafe_timeout', 'if_index', 'learning_mode',
+ 'mac_masquerade_address', 'member', 'mtu',
+ 'sflow_poll_interval', 'sflow_poll_interval_global',
+ 'sflow_sampling_rate', 'sflow_sampling_rate_global',
+ 'source_check_state', 'true_mac_address', 'vlan_id']
+ return generate_dict(vlans, fields)
+
+
+def generate_vs_dict(f5, regex):
+ virtual_servers = VirtualServers(f5.get_api(), regex)
+ fields = ['actual_hardware_acceleration', 'authentication_profile',
+ 'auto_lasthop', 'bw_controller_policy', 'clone_pool',
+ 'cmp_enable_mode', 'connection_limit', 'connection_mirror_state',
+ 'default_pool_name', 'description', 'destination',
+ 'enabled_state', 'enforced_firewall_policy',
+ 'fallback_persistence_profile', 'fw_rule', 'gtm_score',
+ 'last_hop_pool', 'nat64_state', 'object_status',
+ 'persistence_profile', 'profile', 'protocol',
+ 'rate_class', 'rate_limit', 'rate_limit_destination_mask',
+ 'rate_limit_mode', 'rate_limit_source_mask', 'related_rule',
+ 'rule', 'security_log_profile', 'snat_pool', 'snat_type',
+ 'source_address', 'source_address_translation_lsn_pool',
+ 'source_address_translation_snat_pool',
+ 'source_address_translation_type', 'source_port_behavior',
+ 'staged_firewall_policy', 'translate_address_state',
+ 'translate_port_state', 'type', 'vlan', 'wildmask']
+ return generate_dict(virtual_servers, fields)
+
+
+def generate_pool_dict(f5, regex):
+ pools = Pools(f5.get_api(), regex)
+ fields = ['action_on_service_down', 'active_member_count',
+ 'aggregate_dynamic_ratio', 'allow_nat_state',
+ 'allow_snat_state', 'client_ip_tos', 'client_link_qos',
+ 'description', 'gateway_failsafe_device',
+ 'ignore_persisted_weight_state', 'lb_method', 'member',
+ 'minimum_active_member', 'minimum_up_member',
+ 'minimum_up_member_action', 'minimum_up_member_enabled_state',
+ 'monitor_association', 'monitor_instance', 'object_status',
+ 'profile', 'queue_depth_limit',
+ 'queue_on_connection_limit_state', 'queue_time_limit',
+ 'reselect_tries', 'server_ip_tos', 'server_link_qos',
+ 'simple_timeout', 'slow_ramp_time']
+ return generate_dict(pools, fields)
+
+
+def generate_device_dict(f5, regex):
+ devices = Devices(f5.get_api(), regex)
+ fields = ['active_modules', 'base_mac_address', 'blade_addresses',
+ 'build', 'chassis_id', 'chassis_type', 'comment',
+ 'configsync_address', 'contact', 'description', 'edition',
+ 'failover_state', 'hostname', 'inactive_modules', 'location',
+ 'management_address', 'marketing_name', 'multicast_address',
+ 'optional_modules', 'platform_id', 'primary_mirror_address',
+ 'product', 'secondary_mirror_address', 'software_version',
+ 'timelimited_modules', 'timezone', 'unicast_addresses']
+ return generate_dict(devices, fields)
+
+
+def generate_device_group_dict(f5, regex):
+ device_groups = DeviceGroups(f5.get_api(), regex)
+ fields = ['all_preferred_active', 'autosync_enabled_state', 'description',
+ 'device', 'full_load_on_sync_state',
+ 'incremental_config_sync_size_maximum',
+ 'network_failover_enabled_state', 'sync_status', 'type']
+ return generate_dict(device_groups, fields)
+
+
+def generate_traffic_group_dict(f5, regex):
+ traffic_groups = TrafficGroups(f5.get_api(), regex)
+ fields = ['auto_failback_enabled_state', 'auto_failback_time',
+ 'default_device', 'description', 'ha_load_factor',
+ 'ha_order', 'is_floating', 'mac_masquerade_address',
+ 'unit_id']
+ return generate_dict(traffic_groups, fields)
+
+
+def generate_rule_dict(f5, regex):
+ rules = Rules(f5.get_api(), regex)
+ fields = ['definition', 'description', 'ignore_vertification',
+ 'verification_status']
+ return generate_dict(rules, fields)
+
+
+def generate_node_dict(f5, regex):
+ nodes = Nodes(f5.get_api(), regex)
+ fields = ['address', 'connection_limit', 'description', 'dynamic_ratio',
+ 'monitor_instance', 'monitor_rule', 'monitor_status',
+ 'object_status', 'rate_limit', 'ratio', 'session_status']
+ return generate_dict(nodes, fields)
+
+
+def generate_virtual_address_dict(f5, regex):
+ virtual_addresses = VirtualAddresses(f5.get_api(), regex)
+ fields = ['address', 'arp_state', 'auto_delete_state', 'connection_limit',
+ 'description', 'enabled_state', 'icmp_echo_state',
+ 'is_floating_state', 'netmask', 'object_status',
+ 'route_advertisement_state', 'traffic_group']
+ return generate_dict(virtual_addresses, fields)
+
+
+def generate_address_class_dict(f5, regex):
+ address_classes = AddressClasses(f5.get_api(), regex)
+ fields = ['address_class', 'description']
+ return generate_dict(address_classes, fields)
+
+
+def generate_certificate_dict(f5, regex):
+ certificates = Certificates(f5.get_api(), regex)
+ return dict(zip(certificates.get_list(), certificates.get_certificate_list()))
+
+
+def generate_key_dict(f5, regex):
+ keys = Keys(f5.get_api(), regex)
+ return dict(zip(keys.get_list(), keys.get_key_list()))
+
+
+def generate_client_ssl_profile_dict(f5, regex):
+ profiles = ProfileClientSSL(f5.get_api(), regex)
+ fields = ['alert_timeout', 'allow_nonssl_state', 'authenticate_depth',
+ 'authenticate_once_state', 'ca_file', 'cache_size',
+ 'cache_timeout', 'certificate_file', 'chain_file',
+ 'cipher_list', 'client_certificate_ca_file', 'crl_file',
+ 'default_profile', 'description',
+ 'forward_proxy_ca_certificate_file', 'forward_proxy_ca_key_file',
+ 'forward_proxy_ca_passphrase',
+ 'forward_proxy_certificate_extension_include',
+ 'forward_proxy_certificate_lifespan',
+ 'forward_proxy_enabled_state',
+ 'forward_proxy_lookup_by_ipaddr_port_state', 'handshake_timeout',
+ 'key_file', 'modssl_emulation_state', 'passphrase',
+ 'peer_certification_mode', 'profile_mode',
+ 'renegotiation_maximum_record_delay', 'renegotiation_period',
+ 'renegotiation_state', 'renegotiation_throughput',
+ 'retain_certificate_state', 'secure_renegotiation_mode',
+ 'server_name', 'session_ticket_state', 'sni_default_state',
+ 'sni_require_state', 'ssl_option', 'strict_resume_state',
+ 'unclean_shutdown_state', 'is_base_profile', 'is_system_profile']
+ return generate_dict(profiles, fields)
+
+
+def generate_system_info_dict(f5):
+ system_info = SystemInfo(f5.get_api())
+ fields = ['base_mac_address',
+ 'blade_temperature', 'chassis_slot_information',
+ 'globally_unique_identifier', 'group_id',
+ 'hardware_information',
+ 'marketing_name',
+ 'product_information', 'pva_version', 'system_id',
+ 'system_information', 'time',
+ 'time_zone', 'uptime']
+ return generate_simple_dict(system_info, fields)
+
+
+def generate_software_list(f5):
+ software = Software(f5.get_api())
+ software_list = software.get_all_software_status()
+ return software_list
+
+
+def generate_provision_dict(f5):
+ provisioned = ProvisionInfo(f5.get_api())
+ fields = ['list', 'provisioned_list']
+ return generate_simple_dict(provisioned, fields)
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ session=dict(type='bool', default=False),
+ include=dict(type='list', required=True),
+ filter=dict(type='str', required=False),
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec
+ )
+
+ if not bigsuds_found:
+ module.fail_json(msg="the python suds and bigsuds modules are required")
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ validate_certs = module.params['validate_certs']
+ session = module.params['session']
+ fact_filter = module.params['filter']
+
+ if validate_certs:
+ import ssl
+ if not hasattr(ssl, 'SSLContext'):
+ module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task')
+
+ if fact_filter:
+ regex = fnmatch.translate(fact_filter)
+ else:
+ regex = None
+ include = [x.lower() for x in module.params['include']]
+ valid_includes = ('address_class', 'certificate', 'client_ssl_profile',
+ 'device', 'device_group', 'interface', 'key', 'node',
+ 'pool', 'provision', 'rule', 'self_ip', 'software',
+ 'system_info', 'traffic_group', 'trunk',
+ 'virtual_address', 'virtual_server', 'vlan')
+ include_test = map(lambda x: x in valid_includes, include)
+ if not all(include_test):
+ module.fail_json(msg="value of include must be one or more of: %s, got: %s" % (",".join(valid_includes), ",".join(include)))
+
+ try:
+ facts = {}
+
+ if len(include) > 0:
+ f5 = F5(server, user, password, session, validate_certs, server_port)
+ saved_active_folder = f5.get_active_folder()
+ saved_recursive_query_state = f5.get_recursive_query_state()
+ if saved_active_folder != "/":
+ f5.set_active_folder("/")
+ if saved_recursive_query_state != "STATE_ENABLED":
+ f5.enable_recursive_query_state()
+
+ if 'interface' in include:
+ facts['interface'] = generate_interface_dict(f5, regex)
+ if 'self_ip' in include:
+ facts['self_ip'] = generate_self_ip_dict(f5, regex)
+ if 'trunk' in include:
+ facts['trunk'] = generate_trunk_dict(f5, regex)
+ if 'vlan' in include:
+ facts['vlan'] = generate_vlan_dict(f5, regex)
+ if 'virtual_server' in include:
+ facts['virtual_server'] = generate_vs_dict(f5, regex)
+ if 'pool' in include:
+ facts['pool'] = generate_pool_dict(f5, regex)
+ if 'provision' in include:
+ facts['provision'] = generate_provision_dict(f5)
+ if 'device' in include:
+ facts['device'] = generate_device_dict(f5, regex)
+ if 'device_group' in include:
+ facts['device_group'] = generate_device_group_dict(f5, regex)
+ if 'traffic_group' in include:
+ facts['traffic_group'] = generate_traffic_group_dict(f5, regex)
+ if 'rule' in include:
+ facts['rule'] = generate_rule_dict(f5, regex)
+ if 'node' in include:
+ facts['node'] = generate_node_dict(f5, regex)
+ if 'virtual_address' in include:
+ facts['virtual_address'] = generate_virtual_address_dict(f5, regex)
+ if 'address_class' in include:
+ facts['address_class'] = generate_address_class_dict(f5, regex)
+ if 'software' in include:
+ facts['software'] = generate_software_list(f5)
+ if 'certificate' in include:
+ facts['certificate'] = generate_certificate_dict(f5, regex)
+ if 'key' in include:
+ facts['key'] = generate_key_dict(f5, regex)
+ if 'client_ssl_profile' in include:
+ facts['client_ssl_profile'] = generate_client_ssl_profile_dict(f5, regex)
+ if 'system_info' in include:
+ facts['system_info'] = generate_system_info_dict(f5)
+
+ # restore saved state
+ if saved_active_folder and saved_active_folder != "/":
+ f5.set_active_folder(saved_active_folder)
+ if saved_recursive_query_state and \
+ saved_recursive_query_state != "STATE_ENABLED":
+ f5.set_recursive_query_state(saved_recursive_query_state)
+
+ result = {'ansible_facts': facts}
+
+ except Exception as e:
+ module.fail_json(msg="received exception: %s\ntraceback: %s" % (e, traceback.format_exc()))
+
+ module.exit_json(**result)
+
+# include magic from lib/ansible/module_common.py
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_gtm_datacenter.py b/lib/ansible/modules/extras/network/f5/bigip_gtm_datacenter.py
new file mode 100644
index 0000000000..90882b6f64
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_gtm_datacenter.py
@@ -0,0 +1,366 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_gtm_datacenter
+short_description: Manage Datacenter configuration in BIG-IP
+description:
+ - Manage BIG-IP data center configuration. A data center defines the location
+ where the physical network components reside, such as the server and link
+ objects that share the same subnet on the network. This module is able to
+ manipulate the data center definitions in a BIG-IP
+version_added: "2.2"
+options:
+ contact:
+ description:
+ - The name of the contact for the data center.
+ description:
+ description:
+ - The description of the data center.
+ enabled:
+ description:
+ - Whether the data center should be enabled. At least one of C(state) and
+ C(enabled) are required.
+ choices:
+ - yes
+ - no
+ location:
+ description:
+ - The location of the data center.
+ name:
+ description:
+ - The name of the data center.
+ required: true
+ state:
+ description:
+ - The state of the datacenter on the BIG-IP. When C(present), guarantees
+ that the data center exists. When C(absent) removes the data center
+ from the BIG-IP. C(enabled) will enable the data center and C(disabled)
+ will ensure the data center is disabled. At least one of state and
+ enabled are required.
+ choices:
+ - present
+ - absent
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as
+ pip install f5-sdk.
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Create data center "New York"
+ bigip_gtm_datacenter:
+ server: "big-ip"
+ name: "New York"
+ location: "222 West 23rd"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+contact:
+ description: The contact that was set on the datacenter
+ returned: changed
+ type: string
+ sample: "admin@root.local"
+description:
+ description: The description that was set for the datacenter
+ returned: changed
+ type: string
+ sample: "Datacenter in NYC"
+enabled:
+ description: Whether the datacenter is enabled or not
+ returned: changed
+ type: bool
+ sample: true
+location:
+ description: The location that is set for the datacenter
+ returned: changed
+ type: string
+ sample: "222 West 23rd"
+name:
+ description: Name of the datacenter being manipulated
+ returned: changed
+ type: string
+ sample: "foo"
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+
+class BigIpGtmDatacenter(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def create(self):
+ params = dict()
+
+ check_mode = self.params['check_mode']
+ contact = self.params['contact']
+ description = self.params['description']
+ location = self.params['location']
+ name = self.params['name']
+ partition = self.params['partition']
+ enabled = self.params['enabled']
+
+ # Specifically check for None because a person could supply empty
+ # values which would technically still be valid
+ if contact is not None:
+ params['contact'] = contact
+
+ if description is not None:
+ params['description'] = description
+
+ if location is not None:
+ params['location'] = location
+
+ if enabled is not None:
+ params['enabled'] = True
+ else:
+ params['disabled'] = False
+
+ params['name'] = name
+ params['partition'] = partition
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return True
+
+ d = self.api.tm.gtm.datacenters.datacenter
+ d.create(**params)
+
+ if not self.exists():
+ raise F5ModuleError("Failed to create the datacenter")
+ return True
+
+ def read(self):
+ """Read information and transform it
+
+ The values that are returned by BIG-IP in the f5-sdk can have encoding
+ attached to them as well as be completely missing in some cases.
+
+ Therefore, this method will transform the data from the BIG-IP into a
+ format that is more easily consumable by the rest of the class and the
+ parameters that are supported by the module.
+ """
+ p = dict()
+ name = self.params['name']
+ partition = self.params['partition']
+ r = self.api.tm.gtm.datacenters.datacenter.load(
+ name=name,
+ partition=partition
+ )
+
+ if hasattr(r, 'servers'):
+ # Deliberately using sets to supress duplicates
+ p['servers'] = set([str(x) for x in r.servers])
+ if hasattr(r, 'contact'):
+ p['contact'] = str(r.contact)
+ if hasattr(r, 'location'):
+ p['location'] = str(r.location)
+ if hasattr(r, 'description'):
+ p['description'] = str(r.description)
+ if r.enabled:
+ p['enabled'] = True
+ else:
+ p['enabled'] = False
+ p['name'] = name
+ return p
+
+ def update(self):
+ changed = False
+ params = dict()
+ current = self.read()
+
+ check_mode = self.params['check_mode']
+ contact = self.params['contact']
+ description = self.params['description']
+ location = self.params['location']
+ name = self.params['name']
+ partition = self.params['partition']
+ enabled = self.params['enabled']
+
+ if contact is not None:
+ if 'contact' in current:
+ if contact != current['contact']:
+ params['contact'] = contact
+ else:
+ params['contact'] = contact
+
+ if description is not None:
+ if 'description' in current:
+ if description != current['description']:
+ params['description'] = description
+ else:
+ params['description'] = description
+
+ if location is not None:
+ if 'location' in current:
+ if location != current['location']:
+ params['location'] = location
+ else:
+ params['location'] = location
+
+ if enabled is not None:
+ if current['enabled'] != enabled:
+ if enabled is True:
+ params['enabled'] = True
+ params['disabled'] = False
+ else:
+ params['disabled'] = True
+ params['enabled'] = False
+
+ if params:
+ changed = True
+ if check_mode:
+ return changed
+ self.cparams = camel_dict_to_snake_dict(params)
+ else:
+ return changed
+
+ r = self.api.tm.gtm.datacenters.datacenter.load(
+ name=name,
+ partition=partition
+ )
+ r.update(**params)
+ r.refresh()
+
+ return True
+
+ def delete(self):
+ params = dict()
+ check_mode = self.params['check_mode']
+
+ params['name'] = self.params['name']
+ params['partition'] = self.params['partition']
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return True
+
+ dc = self.api.tm.gtm.datacenters.datacenter.load(**params)
+ dc.delete()
+
+ if self.exists():
+ raise F5ModuleError("Failed to delete the datacenter")
+ return True
+
+ def present(self):
+ changed = False
+
+ if self.exists():
+ changed = self.update()
+ else:
+ changed = self.create()
+
+ return changed
+
+ def absent(self):
+ changed = False
+
+ if self.exists():
+ changed = self.delete()
+
+ return changed
+
+ def exists(self):
+ name = self.params['name']
+ partition = self.params['partition']
+
+ return self.api.tm.gtm.datacenters.datacenter.exists(
+ name=name,
+ partition=partition
+ )
+
+ def flush(self):
+ result = dict()
+ state = self.params['state']
+ enabled = self.params['enabled']
+
+ if state is None and enabled is None:
+ module.fail_json(msg="Neither 'state' nor 'enabled' set")
+
+ try:
+ if state == "present":
+ changed = self.present()
+
+ # Ensure that this field is not returned to the user since it
+ # is not a valid parameter to the module.
+ if 'disabled' in self.cparams:
+ del self.cparams['disabled']
+ elif state == "absent":
+ changed = self.absent()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ contact=dict(required=False, default=None),
+ description=dict(required=False, default=None),
+ enabled=dict(required=False, type='bool', default=None, choices=BOOLEANS),
+ location=dict(required=False, default=None),
+ name=dict(required=True)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ try:
+ obj = BigIpGtmDatacenter(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_gtm_virtual_server.py b/lib/ansible/modules/extras/network/f5/bigip_gtm_virtual_server.py
new file mode 100644
index 0000000000..079709c1b3
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_gtm_virtual_server.py
@@ -0,0 +1,235 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2015, Michael Perzel
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_gtm_virtual_server
+short_description: "Manages F5 BIG-IP GTM virtual servers"
+description:
+ - "Manages F5 BIG-IP GTM virtual servers"
+version_added: "2.2"
+author:
+ - Michael Perzel (@perzizzle)
+ - Tim Rupp (@caphrim007)
+notes:
+ - "Requires BIG-IP software version >= 11.4"
+ - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
+ - "Best run as a local_action in your playbook"
+ - "Tested with manager and above account privilege level"
+
+requirements:
+ - bigsuds
+options:
+ state:
+ description:
+ - Virtual server state
+ required: false
+ default: present
+ choices: ['present', 'absent','enabled','disabled']
+ virtual_server_name:
+ description:
+ - Virtual server name
+ required: True
+ virtual_server_server:
+ description:
+ - Virtual server server
+ required: true
+ host:
+ description:
+ - Virtual server host
+ required: false
+ default: None
+ aliases: ['address']
+ port:
+ description:
+ - Virtual server port
+ required: false
+ default: None
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+ - name: Enable virtual server
+ local_action: >
+ bigip_gtm_virtual_server
+ server=192.0.2.1
+ user=admin
+ password=mysecret
+ virtual_server_name=myname
+ virtual_server_server=myserver
+ state=enabled
+'''
+
+RETURN = '''# '''
+
+try:
+ import bigsuds
+except ImportError:
+ bigsuds_found = False
+else:
+ bigsuds_found = True
+
+
+def server_exists(api, server):
+ # hack to determine if virtual server exists
+ result = False
+ try:
+ api.GlobalLB.Server.get_object_status([server])
+ result = True
+ except bigsuds.OperationFailed, e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def virtual_server_exists(api, name, server):
+ # hack to determine if virtual server exists
+ result = False
+ try:
+ virtual_server_id = {'name': name, 'server': server}
+ api.GlobalLB.VirtualServerV2.get_object_status([virtual_server_id])
+ result = True
+ except bigsuds.OperationFailed, e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def add_virtual_server(api, virtual_server_name, virtual_server_server, address, port):
+ addresses = {'address': address, 'port': port}
+ virtual_server_id = {'name': virtual_server_name, 'server': virtual_server_server}
+ api.GlobalLB.VirtualServerV2.create([virtual_server_id], [addresses])
+
+
+def remove_virtual_server(api, virtual_server_name, virtual_server_server):
+ virtual_server_id = {'name': virtual_server_name, 'server': virtual_server_server}
+ api.GlobalLB.VirtualServerV2.delete_virtual_server([virtual_server_id])
+
+
+def get_virtual_server_state(api, name, server):
+ virtual_server_id = {'name': name, 'server': server}
+ state = api.GlobalLB.VirtualServerV2.get_enabled_state([virtual_server_id])
+ state = state[0].split('STATE_')[1].lower()
+ return state
+
+
+def set_virtual_server_state(api, name, server, state):
+ virtual_server_id = {'name': name, 'server': server}
+ state = "STATE_%s" % state.strip().upper()
+ api.GlobalLB.VirtualServerV2.set_enabled_state([virtual_server_id], [state])
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ state=dict(type='str', default='present', choices=['present', 'absent', 'enabled', 'disabled']),
+ host=dict(type='str', default=None, aliases=['address']),
+ port=dict(type='int', default=None),
+ virtual_server_name=dict(type='str', required=True),
+ virtual_server_server=dict(type='str', required=True)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ if not bigsuds_found:
+ module.fail_json(msg="the python bigsuds module is required")
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ validate_certs = module.params['validate_certs']
+ user = module.params['user']
+ password = module.params['password']
+ virtual_server_name = module.params['virtual_server_name']
+ virtual_server_server = module.params['virtual_server_server']
+ state = module.params['state']
+ address = module.params['host']
+ port = module.params['port']
+
+ result = {'changed': False} # default
+
+ try:
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+
+ if state == 'absent':
+ if virtual_server_exists(api, virtual_server_name, virtual_server_server):
+ if not module.check_mode:
+ remove_virtual_server(api, virtual_server_name, virtual_server_server)
+ result = {'changed': True}
+ else:
+ # check-mode return value
+ result = {'changed': True}
+ elif state == 'present':
+ if virtual_server_name and virtual_server_server and address and port:
+ if not virtual_server_exists(api, virtual_server_name, virtual_server_server):
+ if not module.check_mode:
+ if server_exists(api, virtual_server_server):
+ add_virtual_server(api, virtual_server_name, virtual_server_server, address, port)
+ result = {'changed': True}
+ else:
+ module.fail_json(msg="server does not exist")
+ else:
+ # check-mode return value
+ result = {'changed': True}
+ else:
+ # virtual server exists -- potentially modify attributes --future feature
+ result = {'changed': False}
+ else:
+ module.fail_json(msg="Address and port are required to create virtual server")
+ elif state == 'enabled':
+ if not virtual_server_exists(api, virtual_server_name, virtual_server_server):
+ module.fail_json(msg="virtual server does not exist")
+ if state != get_virtual_server_state(api, virtual_server_name, virtual_server_server):
+ if not module.check_mode:
+ set_virtual_server_state(api, virtual_server_name, virtual_server_server, state)
+ result = {'changed': True}
+ else:
+ result = {'changed': True}
+ elif state == 'disabled':
+ if not virtual_server_exists(api, virtual_server_name, virtual_server_server):
+ module.fail_json(msg="virtual server does not exist")
+ if state != get_virtual_server_state(api, virtual_server_name, virtual_server_server):
+ if not module.check_mode:
+ set_virtual_server_state(api, virtual_server_name, virtual_server_server, state)
+ result = {'changed': True}
+ else:
+ result = {'changed': True}
+
+ except Exception, e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_gtm_wide_ip.py b/lib/ansible/modules/extras/network/f5/bigip_gtm_wide_ip.py
new file mode 100644
index 0000000000..19292783bc
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_gtm_wide_ip.py
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2015, Michael Perzel
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_gtm_wide_ip
+short_description: "Manages F5 BIG-IP GTM wide ip"
+description:
+ - "Manages F5 BIG-IP GTM wide ip"
+version_added: "2.0"
+author:
+ - Michael Perzel (@perzizzle)
+ - Tim Rupp (@caphrim007)
+notes:
+ - "Requires BIG-IP software version >= 11.4"
+ - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
+ - "Best run as a local_action in your playbook"
+ - "Tested with manager and above account privilege level"
+
+requirements:
+ - bigsuds
+options:
+ lb_method:
+ description:
+ - LB method of wide ip
+ required: true
+ choices: ['return_to_dns', 'null', 'round_robin',
+ 'ratio', 'topology', 'static_persist', 'global_availability',
+ 'vs_capacity', 'least_conn', 'lowest_rtt', 'lowest_hops',
+ 'packet_rate', 'cpu', 'hit_ratio', 'qos', 'bps',
+ 'drop_packet', 'explicit_ip', 'connection_rate', 'vs_score']
+ wide_ip:
+ description:
+ - Wide IP name
+ required: true
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+ - name: Set lb method
+ local_action: >
+ bigip_gtm_wide_ip
+ server=192.0.2.1
+ user=admin
+ password=mysecret
+ lb_method=round_robin
+ wide_ip=my-wide-ip.example.com
+'''
+
+try:
+ import bigsuds
+except ImportError:
+ bigsuds_found = False
+else:
+ bigsuds_found = True
+
+def get_wide_ip_lb_method(api, wide_ip):
+ lb_method = api.GlobalLB.WideIP.get_lb_method(wide_ips=[wide_ip])[0]
+ lb_method = lb_method.strip().replace('LB_METHOD_', '').lower()
+ return lb_method
+
+def get_wide_ip_pools(api, wide_ip):
+ try:
+ return api.GlobalLB.WideIP.get_wideip_pool([wide_ip])
+ except Exception, e:
+ print e
+
+def wide_ip_exists(api, wide_ip):
+ # hack to determine if wide_ip exists
+ result = False
+ try:
+ api.GlobalLB.WideIP.get_object_status(wide_ips=[wide_ip])
+ result = True
+ except bigsuds.OperationFailed, e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+def set_wide_ip_lb_method(api, wide_ip, lb_method):
+ lb_method = "LB_METHOD_%s" % lb_method.strip().upper()
+ api.GlobalLB.WideIP.set_lb_method(wide_ips=[wide_ip], lb_methods=[lb_method])
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ lb_method_choices = ['return_to_dns', 'null', 'round_robin',
+ 'ratio', 'topology', 'static_persist', 'global_availability',
+ 'vs_capacity', 'least_conn', 'lowest_rtt', 'lowest_hops',
+ 'packet_rate', 'cpu', 'hit_ratio', 'qos', 'bps',
+ 'drop_packet', 'explicit_ip', 'connection_rate', 'vs_score']
+ meta_args = dict(
+ lb_method = dict(type='str', required=True, choices=lb_method_choices),
+ wide_ip = dict(type='str', required=True)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ if not bigsuds_found:
+ module.fail_json(msg="the python bigsuds module is required")
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ wide_ip = module.params['wide_ip']
+ lb_method = module.params['lb_method']
+ validate_certs = module.params['validate_certs']
+
+ result = {'changed': False} # default
+
+ try:
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+
+ if not wide_ip_exists(api, wide_ip):
+ module.fail_json(msg="wide ip %s does not exist" % wide_ip)
+
+ if lb_method is not None and lb_method != get_wide_ip_lb_method(api, wide_ip):
+ if not module.check_mode:
+ set_wide_ip_lb_method(api, wide_ip, lb_method)
+ result = {'changed': True}
+ else:
+ result = {'changed': True}
+
+ except Exception, e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_irule.py b/lib/ansible/modules/extras/network/f5/bigip_irule.py
new file mode 100644
index 0000000000..5e99ec34fa
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_irule.py
@@ -0,0 +1,385 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_irule
+short_description: Manage iRules across different modules on a BIG-IP.
+description:
+ - Manage iRules across different modules on a BIG-IP.
+version_added: "2.2"
+options:
+ content:
+ description:
+ - When used instead of 'src', sets the contents of an iRule directly to
+ the specified value. This is for simple values, but can be used with
+ lookup plugins for anything complex or with formatting. Either one
+ of C(src) or C(content) must be provided.
+ module:
+ description:
+ - The BIG-IP module to add the iRule to.
+ required: true
+ choices:
+ - ltm
+ - gtm
+ partition:
+ description:
+ - The partition to create the iRule on.
+ required: false
+ default: Common
+ name:
+ description:
+ - The name of the iRule.
+ required: true
+ src:
+ description:
+ - The iRule file to interpret and upload to the BIG-IP. Either one
+ of C(src) or C(content) must be provided.
+ required: true
+ state:
+ description:
+ - Whether the iRule should exist or not.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as
+ pip install f5-sdk.
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Add the iRule contained in templated irule.tcl to the LTM module
+ bigip_irule:
+ content: "{{ lookup('template', 'irule-template.tcl') }}"
+ module: "ltm"
+ name: "MyiRule"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "present"
+ user: "admin"
+ delegate_to: localhost
+
+- name: Add the iRule contained in static file irule.tcl to the LTM module
+ bigip_irule:
+ module: "ltm"
+ name: "MyiRule"
+ password: "secret"
+ server: "lb.mydomain.com"
+ src: "irule-static.tcl"
+ state: "present"
+ user: "admin"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+module:
+ description: The module that the iRule was added to
+ returned: changed and success
+ type: string
+ sample: "gtm"
+src:
+ description: The filename that included the iRule source
+ returned: changed and success, when provided
+ type: string
+ sample: "/opt/src/irules/example1.tcl"
+name:
+ description: The name of the iRule that was managed
+ returned: changed and success
+ type: string
+ sample: "my-irule"
+content:
+ description: The content of the iRule that was managed
+ returned: changed and success
+ type: string
+ sample: "when LB_FAILED { set wipHost [LB::server addr] }"
+partition:
+ description: The partition in which the iRule was managed
+ returned: changed and success
+ type: string
+ sample: "Common"
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+MODULES = ['gtm', 'ltm']
+
+
+class BigIpiRule(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ if kwargs['state'] != 'absent':
+ if not kwargs['content'] and not kwargs['src']:
+ raise F5ModuleError(
+ "Either 'content' or 'src' must be provided"
+ )
+
+ source = kwargs['src']
+ if source:
+ with open(source) as f:
+ kwargs['content'] = f.read()
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def flush(self):
+ result = dict()
+ state = self.params['state']
+
+ try:
+ if state == "present":
+ changed = self.present()
+ elif state == "absent":
+ changed = self.absent()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+ def read(self):
+ """Read information and transform it
+
+ The values that are returned by BIG-IP in the f5-sdk can have encoding
+ attached to them as well as be completely missing in some cases.
+
+ Therefore, this method will transform the data from the BIG-IP into a
+ format that is more easily consumable by the rest of the class and the
+ parameters that are supported by the module.
+ """
+ p = dict()
+ name = self.params['name']
+ partition = self.params['partition']
+ module = self.params['module']
+
+ if module == 'ltm':
+ r = self.api.tm.ltm.rules.rule.load(
+ name=name,
+ partition=partition
+ )
+ elif module == 'gtm':
+ r = self.api.tm.gtm.rules.rule.load(
+ name=name,
+ partition=partition
+ )
+
+ if hasattr(r, 'apiAnonymous'):
+ p['content'] = str(r.apiAnonymous)
+ p['name'] = name
+ return p
+
+ def delete(self):
+ params = dict()
+ check_mode = self.params['check_mode']
+ module = self.params['module']
+
+ params['name'] = self.params['name']
+ params['partition'] = self.params['partition']
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return True
+
+ if module == 'ltm':
+ r = self.api.tm.ltm.rules.rule.load(**params)
+ r.delete()
+ elif module == 'gtm':
+ r = self.api.tm.gtm.rules.rule.load(**params)
+ r.delete()
+
+ if self.exists():
+ raise F5ModuleError("Failed to delete the iRule")
+ return True
+
+ def exists(self):
+ name = self.params['name']
+ partition = self.params['partition']
+ module = self.params['module']
+
+ if module == 'ltm':
+ return self.api.tm.ltm.rules.rule.exists(
+ name=name,
+ partition=partition
+ )
+ elif module == 'gtm':
+ return self.api.tm.gtm.rules.rule.exists(
+ name=name,
+ partition=partition
+ )
+
+ def present(self):
+ changed = False
+
+ if self.exists():
+ changed = self.update()
+ else:
+ changed = self.create()
+
+ return changed
+
+ def update(self):
+ params = dict()
+ current = self.read()
+ changed = False
+
+ check_mode = self.params['check_mode']
+ content = self.params['content']
+ name = self.params['name']
+ partition = self.params['partition']
+ module = self.params['module']
+
+ if content is not None:
+ if 'content' in current:
+ if content != current['content']:
+ params['apiAnonymous'] = content
+ else:
+ params['apiAnonymous'] = content
+
+ if params:
+ changed = True
+ params['name'] = name
+ params['partition'] = partition
+ self.cparams = camel_dict_to_snake_dict(params)
+ if 'api_anonymous' in self.cparams:
+ self.cparams['content'] = self.cparams.pop('api_anonymous')
+ if self.params['src']:
+ self.cparams['src'] = self.params['src']
+
+ if check_mode:
+ return changed
+ else:
+ return changed
+
+ if module == 'ltm':
+ d = self.api.tm.ltm.rules.rule.load(
+ name=name,
+ partition=partition
+ )
+ d.update(**params)
+ d.refresh()
+ elif module == 'gtm':
+ d = self.api.tm.gtm.rules.rule.load(
+ name=name,
+ partition=partition
+ )
+ d.update(**params)
+ d.refresh()
+
+ return True
+
+ def create(self):
+ params = dict()
+
+ check_mode = self.params['check_mode']
+ content = self.params['content']
+ name = self.params['name']
+ partition = self.params['partition']
+ module = self.params['module']
+
+ if check_mode:
+ return True
+
+ if content is not None:
+ params['apiAnonymous'] = content
+
+ params['name'] = name
+ params['partition'] = partition
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if 'api_anonymous' in self.cparams:
+ self.cparams['content'] = self.cparams.pop('api_anonymous')
+ if self.params['src']:
+ self.cparams['src'] = self.params['src']
+
+ if check_mode:
+ return True
+
+ if module == 'ltm':
+ d = self.api.tm.ltm.rules.rule
+ d.create(**params)
+ elif module == 'gtm':
+ d = self.api.tm.gtm.rules.rule
+ d.create(**params)
+
+ if not self.exists():
+ raise F5ModuleError("Failed to create the iRule")
+ return True
+
+ def absent(self):
+ changed = False
+
+ if self.exists():
+ changed = self.delete()
+
+ return changed
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ content=dict(required=False, default=None),
+ src=dict(required=False, default=None),
+ name=dict(required=True),
+ module=dict(required=True, choices=MODULES)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ mutually_exclusive=[
+ ['content', 'src']
+ ]
+ )
+
+ try:
+ obj = BigIpiRule(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_monitor_http.py b/lib/ansible/modules/extras/network/f5/bigip_monitor_http.py
new file mode 100644
index 0000000000..3c303c3ce5
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_monitor_http.py
@@ -0,0 +1,443 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2013, serge van Ginderachter <serge@vanginderachter.be>
+# based on Matt Hite's bigip_pool module
+# (c) 2013, Matt Hite <mhite@hotmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_monitor_http
+short_description: "Manages F5 BIG-IP LTM http monitors"
+description:
+ - Manages F5 BIG-IP LTM monitors via iControl SOAP API
+version_added: "1.4"
+author:
+ - Serge van Ginderachter (@srvg)
+ - Tim Rupp (@caphrim007)
+notes:
+ - "Requires BIG-IP software version >= 11"
+ - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
+ - "Best run as a local_action in your playbook"
+ - "Monitor API documentation: https://devcentral.f5.com/wiki/iControl.LocalLB__Monitor.ashx"
+requirements:
+ - bigsuds
+options:
+ state:
+ description:
+ - Monitor state
+ required: false
+ default: 'present'
+ choices:
+ - present
+ - absent
+ name:
+ description:
+ - Monitor name
+ required: true
+ default: null
+ aliases:
+ - monitor
+ partition:
+ description:
+ - Partition for the monitor
+ required: false
+ default: 'Common'
+ parent:
+ description:
+ - The parent template of this monitor template
+ required: false
+ default: 'http'
+ parent_partition:
+ description:
+ - Partition for the parent monitor
+ required: false
+ default: 'Common'
+ send:
+ description:
+ - The send string for the monitor call
+ required: true
+ default: none
+ receive:
+ description:
+ - The receive string for the monitor call
+ required: true
+ default: none
+ receive_disable:
+ description:
+ - The receive disable string for the monitor call
+ required: true
+ default: none
+ ip:
+ description:
+ - IP address part of the ipport definition. The default API setting
+ is "0.0.0.0".
+ required: false
+ default: none
+ port:
+ description:
+ - Port address part of the ip/port definition. The default API
+ setting is 0.
+ required: false
+ default: none
+ interval:
+ description:
+ - The interval specifying how frequently the monitor instance
+ of this template will run. By default, this interval is used for up and
+ down states. The default API setting is 5.
+ required: false
+ default: none
+ timeout:
+ description:
+ - The number of seconds in which the node or service must respond to
+ the monitor request. If the target responds within the set time
+ period, it is considered up. If the target does not respond within
+ the set time period, it is considered down. You can change this
+ number to any number you want, however, it should be 3 times the
+ interval number of seconds plus 1 second. The default API setting
+ is 16.
+ required: false
+ default: none
+ time_until_up:
+ description:
+ - Specifies the amount of time in seconds after the first successful
+ response before a node will be marked up. A value of 0 will cause a
+ node to be marked up immediately after a valid response is received
+ from the node. The default API setting is 0.
+ required: false
+ default: none
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+- name: BIGIP F5 | Create HTTP Monitor
+ bigip_monitor_http:
+ state: "present"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ name: "my_http_monitor"
+ send: "http string to send"
+ receive: "http string to receive"
+ delegate_to: localhost
+
+- name: BIGIP F5 | Remove HTTP Monitor
+ bigip_monitor_http:
+ state: "absent"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ name: "my_http_monitor"
+ delegate_to: localhost
+'''
+
+TEMPLATE_TYPE = 'TTYPE_HTTP'
+DEFAULT_PARENT_TYPE = 'http'
+
+
+def check_monitor_exists(module, api, monitor, parent):
+ # hack to determine if monitor exists
+ result = False
+ try:
+ ttype = api.LocalLB.Monitor.get_template_type(template_names=[monitor])[0]
+ parent2 = api.LocalLB.Monitor.get_parent_template(template_names=[monitor])[0]
+ if ttype == TEMPLATE_TYPE and parent == parent2:
+ result = True
+ else:
+ module.fail_json(msg='Monitor already exists, but has a different type (%s) or parent(%s)' % (ttype, parent))
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def create_monitor(api, monitor, template_attributes):
+ try:
+ api.LocalLB.Monitor.create_template(
+ templates=[{
+ 'template_name': monitor,
+ 'template_type': TEMPLATE_TYPE
+ }],
+ template_attributes=[template_attributes]
+ )
+ except bigsuds.OperationFailed as e:
+ if "already exists" in str(e):
+ return False
+ else:
+ # genuine exception
+ raise
+ return True
+
+
+def delete_monitor(api, monitor):
+ try:
+ api.LocalLB.Monitor.delete_template(template_names=[monitor])
+ except bigsuds.OperationFailed as e:
+ # maybe it was deleted since we checked
+ if "was not found" in str(e):
+ return False
+ else:
+ # genuine exception
+ raise
+ return True
+
+
+def check_string_property(api, monitor, str_property):
+ try:
+ template_prop = api.LocalLB.Monitor.get_template_string_property(
+ [monitor], [str_property['type']]
+ )[0]
+ return str_property == template_prop
+ except bigsuds.OperationFailed as e:
+ # happens in check mode if not created yet
+ if "was not found" in str(e):
+ return True
+ else:
+ # genuine exception
+ raise
+
+
+def set_string_property(api, monitor, str_property):
+ api.LocalLB.Monitor.set_template_string_property(
+ template_names=[monitor],
+ values=[str_property]
+ )
+
+
+def check_integer_property(api, monitor, int_property):
+ try:
+ template_prop = api.LocalLB.Monitor.get_template_integer_property(
+ [monitor], [int_property['type']]
+ )[0]
+ return int_property == template_prop
+ except bigsuds.OperationFailed as e:
+ # happens in check mode if not created yet
+ if "was not found" in str(e):
+ return True
+ else:
+ # genuine exception
+ raise
+
+
+def set_integer_property(api, monitor, int_property):
+ api.LocalLB.Monitor.set_template_integer_property(
+ template_names=[monitor],
+ values=[int_property]
+ )
+
+
+def update_monitor_properties(api, module, monitor, template_string_properties, template_integer_properties):
+ changed = False
+ for str_property in template_string_properties:
+ if str_property['value'] is not None and not check_string_property(api, monitor, str_property):
+ if not module.check_mode:
+ set_string_property(api, monitor, str_property)
+ changed = True
+ for int_property in template_integer_properties:
+ if int_property['value'] is not None and not check_integer_property(api, monitor, int_property):
+ if not module.check_mode:
+ set_integer_property(api, monitor, int_property)
+ changed = True
+
+ return changed
+
+
+def get_ipport(api, monitor):
+ return api.LocalLB.Monitor.get_template_destination(template_names=[monitor])[0]
+
+
+def set_ipport(api, monitor, ipport):
+ try:
+ api.LocalLB.Monitor.set_template_destination(
+ template_names=[monitor], destinations=[ipport]
+ )
+ return True, ""
+ except bigsuds.OperationFailed as e:
+ if "Cannot modify the address type of monitor" in str(e):
+ return False, "Cannot modify the address type of monitor if already assigned to a pool."
+ else:
+ # genuine exception
+ raise
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ name=dict(required=True),
+ parent=dict(default=DEFAULT_PARENT_TYPE),
+ parent_partition=dict(default='Common'),
+ send=dict(required=False),
+ receive=dict(required=False),
+ receive_disable=dict(required=False),
+ ip=dict(required=False),
+ port=dict(required=False, type='int'),
+ interval=dict(required=False, type='int'),
+ timeout=dict(required=False, type='int'),
+ time_until_up=dict(required=False, type='int', default=0)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ state = module.params['state']
+ partition = module.params['partition']
+ validate_certs = module.params['validate_certs']
+
+ parent_partition = module.params['parent_partition']
+ name = module.params['name']
+ parent = fq_name(parent_partition, module.params['parent'])
+ monitor = fq_name(partition, name)
+ send = module.params['send']
+ receive = module.params['receive']
+ receive_disable = module.params['receive_disable']
+ ip = module.params['ip']
+ port = module.params['port']
+ interval = module.params['interval']
+ timeout = module.params['timeout']
+ time_until_up = module.params['time_until_up']
+
+ # end monitor specific stuff
+
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+ monitor_exists = check_monitor_exists(module, api, monitor, parent)
+
+ # ipport is a special setting
+ if monitor_exists:
+ cur_ipport = get_ipport(api, monitor)
+ if ip is None:
+ ip = cur_ipport['ipport']['address']
+ if port is None:
+ port = cur_ipport['ipport']['port']
+ else:
+ if interval is None:
+ interval = 5
+ if timeout is None:
+ timeout = 16
+ if ip is None:
+ ip = '0.0.0.0'
+ if port is None:
+ port = 0
+ if send is None:
+ send = ''
+ if receive is None:
+ receive = ''
+ if receive_disable is None:
+ receive_disable = ''
+
+ # define and set address type
+ if ip == '0.0.0.0' and port == 0:
+ address_type = 'ATYPE_STAR_ADDRESS_STAR_PORT'
+ elif ip == '0.0.0.0' and port != 0:
+ address_type = 'ATYPE_STAR_ADDRESS_EXPLICIT_PORT'
+ elif ip != '0.0.0.0' and port != 0:
+ address_type = 'ATYPE_EXPLICIT_ADDRESS_EXPLICIT_PORT'
+ else:
+ address_type = 'ATYPE_UNSET'
+
+ ipport = {'address_type': address_type,
+ 'ipport': {'address': ip,
+ 'port': port}}
+
+ template_attributes = {'parent_template': parent,
+ 'interval': interval,
+ 'timeout': timeout,
+ 'dest_ipport': ipport,
+ 'is_read_only': False,
+ 'is_directly_usable': True}
+
+ # monitor specific stuff
+ template_string_properties = [{'type': 'STYPE_SEND',
+ 'value': send},
+ {'type': 'STYPE_RECEIVE',
+ 'value': receive},
+ {'type': 'STYPE_RECEIVE_DRAIN',
+ 'value': receive_disable}]
+
+ template_integer_properties = [
+ {
+ 'type': 'ITYPE_INTERVAL',
+ 'value': interval
+ },
+ {
+ 'type': 'ITYPE_TIMEOUT',
+ 'value': timeout
+ },
+ {
+ 'type': 'ITYPE_TIME_UNTIL_UP',
+ 'value': time_until_up
+ }
+ ]
+
+ # main logic, monitor generic
+
+ try:
+ result = {'changed': False} # default
+
+ if state == 'absent':
+ if monitor_exists:
+ if not module.check_mode:
+ # possible race condition if same task
+ # on other node deleted it first
+ result['changed'] |= delete_monitor(api, monitor)
+ else:
+ result['changed'] |= True
+ else:
+ # check for monitor itself
+ if not monitor_exists:
+ if not module.check_mode:
+ # again, check changed status here b/c race conditions
+ # if other task already created it
+ result['changed'] |= create_monitor(api, monitor, template_attributes)
+ else:
+ result['changed'] |= True
+
+ # check for monitor parameters
+ # whether it already existed, or was just created, now update
+ # the update functions need to check for check mode but
+ # cannot update settings if it doesn't exist which happens in check mode
+ result['changed'] |= update_monitor_properties(api, module, monitor,
+ template_string_properties,
+ template_integer_properties)
+
+ # we just have to update the ipport if monitor already exists and it's different
+ if monitor_exists and cur_ipport != ipport:
+ set_ipport(api, monitor, ipport)
+ result['changed'] |= True
+ # else: monitor doesn't exist (check mode) or ipport is already ok
+ except Exception as e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_monitor_tcp.py b/lib/ansible/modules/extras/network/f5/bigip_monitor_tcp.py
new file mode 100644
index 0000000000..45756b1ba2
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_monitor_tcp.py
@@ -0,0 +1,485 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2013, serge van Ginderachter <serge@vanginderachter.be>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_monitor_tcp
+short_description: "Manages F5 BIG-IP LTM tcp monitors"
+description:
+ - "Manages F5 BIG-IP LTM tcp monitors via iControl SOAP API"
+version_added: "1.4"
+author:
+ - Serge van Ginderachter (@srvg)
+ - Tim Rupp (@caphrim007)
+notes:
+ - "Requires BIG-IP software version >= 11"
+ - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
+ - "Best run as a local_action in your playbook"
+ - "Monitor API documentation: https://devcentral.f5.com/wiki/iControl.LocalLB__Monitor.ashx"
+requirements:
+ - bigsuds
+options:
+ state:
+ description:
+ - Monitor state
+ required: false
+ default: 'present'
+ choices:
+ - present
+ - absent
+ name:
+ description:
+ - Monitor name
+ required: true
+ default: null
+ aliases:
+ - monitor
+ partition:
+ description:
+ - Partition for the monitor
+ required: false
+ default: 'Common'
+ type:
+ description:
+ - The template type of this monitor template
+ required: false
+ default: 'tcp'
+ choices:
+ - TTYPE_TCP
+ - TTYPE_TCP_ECHO
+ - TTYPE_TCP_HALF_OPEN
+ parent:
+ description:
+ - The parent template of this monitor template
+ required: false
+ default: 'tcp'
+ choices:
+ - tcp
+ - tcp_echo
+ - tcp_half_open
+ parent_partition:
+ description:
+ - Partition for the parent monitor
+ required: false
+ default: 'Common'
+ send:
+ description:
+ - The send string for the monitor call
+ required: true
+ default: none
+ receive:
+ description:
+ - The receive string for the monitor call
+ required: true
+ default: none
+ ip:
+ description:
+ - IP address part of the ipport definition. The default API setting
+ is "0.0.0.0".
+ required: false
+ default: none
+ port:
+ description:
+ - Port address part op the ipport definition. The default API
+ setting is 0.
+ required: false
+ default: none
+ interval:
+ description:
+ - The interval specifying how frequently the monitor instance
+ of this template will run. By default, this interval is used for up and
+ down states. The default API setting is 5.
+ required: false
+ default: none
+ timeout:
+ description:
+ - The number of seconds in which the node or service must respond to
+ the monitor request. If the target responds within the set time
+ period, it is considered up. If the target does not respond within
+ the set time period, it is considered down. You can change this
+ number to any number you want, however, it should be 3 times the
+ interval number of seconds plus 1 second. The default API setting
+ is 16.
+ required: false
+ default: none
+ time_until_up:
+ description:
+ - Specifies the amount of time in seconds after the first successful
+ response before a node will be marked up. A value of 0 will cause a
+ node to be marked up immediately after a valid response is received
+ from the node. The default API setting is 0.
+ required: false
+ default: none
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+- name: Create TCP Monitor
+ bigip_monitor_tcp:
+ state: "present"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ name: "my_tcp_monitor"
+ type: "tcp"
+ send: "tcp string to send"
+ receive: "tcp string to receive"
+ delegate_to: localhost
+
+- name: Create TCP half open Monitor
+ bigip_monitor_tcp:
+ state: "present"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ name: "my_tcp_monitor"
+ type: "tcp"
+ send: "tcp string to send"
+ receive: "http string to receive"
+ delegate_to: localhost
+
+- name: Remove TCP Monitor
+ bigip_monitor_tcp:
+ state: "absent"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ name: "my_tcp_monitor"
+'''
+
+TEMPLATE_TYPE = DEFAULT_TEMPLATE_TYPE = 'TTYPE_TCP'
+TEMPLATE_TYPE_CHOICES = ['tcp', 'tcp_echo', 'tcp_half_open']
+DEFAULT_PARENT = DEFAULT_TEMPLATE_TYPE_CHOICE = DEFAULT_TEMPLATE_TYPE.replace('TTYPE_', '').lower()
+
+
+def check_monitor_exists(module, api, monitor, parent):
+ # hack to determine if monitor exists
+ result = False
+ try:
+ ttype = api.LocalLB.Monitor.get_template_type(template_names=[monitor])[0]
+ parent2 = api.LocalLB.Monitor.get_parent_template(template_names=[monitor])[0]
+ if ttype == TEMPLATE_TYPE and parent == parent2:
+ result = True
+ else:
+ module.fail_json(msg='Monitor already exists, but has a different type (%s) or parent(%s)' % (ttype, parent))
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def create_monitor(api, monitor, template_attributes):
+ try:
+ api.LocalLB.Monitor.create_template(
+ templates=[{
+ 'template_name': monitor,
+ 'template_type': TEMPLATE_TYPE
+ }],
+ template_attributes=[template_attributes]
+ )
+ except bigsuds.OperationFailed as e:
+ if "already exists" in str(e):
+ return False
+ else:
+ # genuine exception
+ raise
+ return True
+
+
+def delete_monitor(api, monitor):
+ try:
+ api.LocalLB.Monitor.delete_template(template_names=[monitor])
+ except bigsuds.OperationFailed as e:
+ # maybe it was deleted since we checked
+ if "was not found" in str(e):
+ return False
+ else:
+ # genuine exception
+ raise
+ return True
+
+
+def check_string_property(api, monitor, str_property):
+ try:
+ template_prop = api.LocalLB.Monitor.get_template_string_property(
+ [monitor], [str_property['type']]
+ )[0]
+ return str_property == template_prop
+ except bigsuds.OperationFailed as e:
+ # happens in check mode if not created yet
+ if "was not found" in str(e):
+ return True
+ else:
+ # genuine exception
+ raise
+
+
+def set_string_property(api, monitor, str_property):
+ api.LocalLB.Monitor.set_template_string_property(
+ template_names=[monitor],
+ values=[str_property]
+ )
+
+
+def check_integer_property(api, monitor, int_property):
+ try:
+ return int_property == api.LocalLB.Monitor.get_template_integer_property(
+ [monitor], [int_property['type']]
+ )[0]
+ except bigsuds.OperationFailed as e:
+ # happens in check mode if not created yet
+ if "was not found" in str(e):
+ return True
+ else:
+ # genuine exception
+ raise
+
+
+def set_integer_property(api, monitor, int_property):
+ api.LocalLB.Monitor.set_template_integer_property(
+ template_names=[monitor],
+ values=[int_property]
+ )
+
+
+def update_monitor_properties(api, module, monitor, template_string_properties, template_integer_properties):
+ changed = False
+ for str_property in template_string_properties:
+ if str_property['value'] is not None and not check_string_property(api, monitor, str_property):
+ if not module.check_mode:
+ set_string_property(api, monitor, str_property)
+ changed = True
+
+ for int_property in template_integer_properties:
+ if int_property['value'] is not None and not check_integer_property(api, monitor, int_property):
+ if not module.check_mode:
+ set_integer_property(api, monitor, int_property)
+ changed = True
+
+ return changed
+
+
+def get_ipport(api, monitor):
+ return api.LocalLB.Monitor.get_template_destination(template_names=[monitor])[0]
+
+
+def set_ipport(api, monitor, ipport):
+ try:
+ api.LocalLB.Monitor.set_template_destination(
+ template_names=[monitor], destinations=[ipport]
+ )
+ return True, ""
+
+ except bigsuds.OperationFailed as e:
+ if "Cannot modify the address type of monitor" in str(e):
+ return False, "Cannot modify the address type of monitor if already assigned to a pool."
+ else:
+ # genuine exception
+ raise
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ name=dict(required=True),
+ type=dict(default=DEFAULT_TEMPLATE_TYPE_CHOICE, choices=TEMPLATE_TYPE_CHOICES),
+ parent=dict(default=DEFAULT_PARENT),
+ parent_partition=dict(default='Common'),
+ send=dict(required=False),
+ receive=dict(required=False),
+ ip=dict(required=False),
+ port=dict(required=False, type='int'),
+ interval=dict(required=False, type='int'),
+ timeout=dict(required=False, type='int'),
+ time_until_up=dict(required=False, type='int', default=0)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ if module.params['validate_certs']:
+ import ssl
+ if not hasattr(ssl, 'SSLContext'):
+ module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task')
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ state = module.params['state']
+ partition = module.params['partition']
+ validate_certs = module.params['validate_certs']
+
+ parent_partition = module.params['parent_partition']
+ name = module.params['name']
+ type = 'TTYPE_' + module.params['type'].upper()
+ parent = fq_name(parent_partition, module.params['parent'])
+ monitor = fq_name(partition, name)
+ send = module.params['send']
+ receive = module.params['receive']
+ ip = module.params['ip']
+ port = module.params['port']
+ interval = module.params['interval']
+ timeout = module.params['timeout']
+ time_until_up = module.params['time_until_up']
+
+ # tcp monitor has multiple types, so overrule
+ global TEMPLATE_TYPE
+ TEMPLATE_TYPE = type
+
+ # end monitor specific stuff
+
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+ monitor_exists = check_monitor_exists(module, api, monitor, parent)
+
+ # ipport is a special setting
+ if monitor_exists:
+ # make sure to not update current settings if not asked
+ cur_ipport = get_ipport(api, monitor)
+ if ip is None:
+ ip = cur_ipport['ipport']['address']
+ if port is None:
+ port = cur_ipport['ipport']['port']
+ else:
+ # use API defaults if not defined to create it
+ if interval is None:
+ interval = 5
+ if timeout is None:
+ timeout = 16
+ if ip is None:
+ ip = '0.0.0.0'
+ if port is None:
+ port = 0
+ if send is None:
+ send = ''
+ if receive is None:
+ receive = ''
+
+ # define and set address type
+ if ip == '0.0.0.0' and port == 0:
+ address_type = 'ATYPE_STAR_ADDRESS_STAR_PORT'
+ elif ip == '0.0.0.0' and port != 0:
+ address_type = 'ATYPE_STAR_ADDRESS_EXPLICIT_PORT'
+ elif ip != '0.0.0.0' and port != 0:
+ address_type = 'ATYPE_EXPLICIT_ADDRESS_EXPLICIT_PORT'
+ else:
+ address_type = 'ATYPE_UNSET'
+
+ ipport = {
+ 'address_type': address_type,
+ 'ipport': {
+ 'address': ip,
+ 'port': port
+ }
+ }
+
+ template_attributes = {
+ 'parent_template': parent,
+ 'interval': interval,
+ 'timeout': timeout,
+ 'dest_ipport': ipport,
+ 'is_read_only': False,
+ 'is_directly_usable': True
+ }
+
+ # monitor specific stuff
+ if type == 'TTYPE_TCP':
+ template_string_properties = [
+ {
+ 'type': 'STYPE_SEND',
+ 'value': send
+ },
+ {
+ 'type': 'STYPE_RECEIVE',
+ 'value': receive
+ }
+ ]
+ else:
+ template_string_properties = []
+
+ template_integer_properties = [
+ {
+ 'type': 'ITYPE_INTERVAL',
+ 'value': interval
+ },
+ {
+ 'type': 'ITYPE_TIMEOUT',
+ 'value': timeout
+ },
+ {
+ 'type': 'ITYPE_TIME_UNTIL_UP',
+ 'value': time_until_up
+ }
+ ]
+
+ # main logic, monitor generic
+
+ try:
+ result = {'changed': False} # default
+
+ if state == 'absent':
+ if monitor_exists:
+ if not module.check_mode:
+ # possible race condition if same task
+ # on other node deleted it first
+ result['changed'] |= delete_monitor(api, monitor)
+ else:
+ result['changed'] |= True
+ else:
+ # check for monitor itself
+ if not monitor_exists:
+ if not module.check_mode:
+ # again, check changed status here b/c race conditions
+ # if other task already created it
+ result['changed'] |= create_monitor(api, monitor, template_attributes)
+ else:
+ result['changed'] |= True
+
+ # check for monitor parameters
+ # whether it already existed, or was just created, now update
+ # the update functions need to check for check mode but
+ # cannot update settings if it doesn't exist which happens in check mode
+ result['changed'] |= update_monitor_properties(api, module, monitor,
+ template_string_properties,
+ template_integer_properties)
+
+ # we just have to update the ipport if monitor already exists and it's different
+ if monitor_exists and cur_ipport != ipport:
+ set_ipport(api, monitor, ipport)
+ result['changed'] |= True
+ # else: monitor doesn't exist (check mode) or ipport is already ok
+ except Exception as e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_node.py b/lib/ansible/modules/extras/network/f5/bigip_node.py
new file mode 100644
index 0000000000..53a0b1973f
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_node.py
@@ -0,0 +1,463 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2013, Matt Hite <mhite@hotmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_node
+short_description: "Manages F5 BIG-IP LTM nodes"
+description:
+ - "Manages F5 BIG-IP LTM nodes via iControl SOAP API"
+version_added: "1.4"
+author:
+ - Matt Hite (@mhite)
+ - Tim Rupp (@caphrim007)
+notes:
+ - "Requires BIG-IP software version >= 11"
+ - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
+ - "Best run as a local_action in your playbook"
+requirements:
+ - bigsuds
+options:
+ state:
+ description:
+ - Pool member state
+ required: true
+ default: present
+ choices: ['present', 'absent']
+ aliases: []
+ session_state:
+ description:
+ - Set new session availability status for node
+ version_added: "1.9"
+ required: false
+ default: null
+ choices: ['enabled', 'disabled']
+ aliases: []
+ monitor_state:
+ description:
+ - Set monitor availability status for node
+ version_added: "1.9"
+ required: false
+ default: null
+ choices: ['enabled', 'disabled']
+ aliases: []
+ partition:
+ description:
+ - Partition
+ required: false
+ default: 'Common'
+ choices: []
+ aliases: []
+ name:
+ description:
+ - "Node name"
+ required: false
+ default: null
+ choices: []
+ monitor_type:
+ description:
+ - Monitor rule type when monitors > 1
+ version_added: "2.2"
+ required: False
+ default: null
+ choices: ['and_list', 'm_of_n']
+ aliases: []
+ quorum:
+ description:
+ - Monitor quorum value when monitor_type is m_of_n
+ version_added: "2.2"
+ required: False
+ default: null
+ choices: []
+ aliases: []
+ monitors:
+ description:
+ - Monitor template name list. Always use the full path to the monitor.
+ version_added: "2.2"
+ required: False
+ default: null
+ choices: []
+ aliases: []
+ host:
+ description:
+ - "Node IP. Required when state=present and node does not exist. Error when state=absent."
+ required: true
+ default: null
+ choices: []
+ aliases: ['address', 'ip']
+ description:
+ description:
+ - "Node description."
+ required: false
+ default: null
+ choices: []
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+- name: Add node
+ bigip_node:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ partition: "Common"
+ host: "10.20.30.40"
+ name: "10.20.30.40"
+
+# Note that the BIG-IP automatically names the node using the
+# IP address specified in previous play's host parameter.
+# Future plays referencing this node no longer use the host
+# parameter but instead use the name parameter.
+# Alternatively, you could have specified a name with the
+# name parameter when state=present.
+
+- name: Add node with a single 'ping' monitor
+ bigip_node:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ partition: "Common"
+ host: "10.20.30.40"
+ name: "mytestserver"
+ monitors:
+ - /Common/icmp
+ delegate_to: localhost
+
+- name: Modify node description
+ bigip_node:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ partition: "Common"
+ name: "10.20.30.40"
+ description: "Our best server yet"
+ delegate_to: localhost
+
+- name: Delete node
+ bigip_node:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "absent"
+ partition: "Common"
+ name: "10.20.30.40"
+
+# The BIG-IP GUI doesn't map directly to the API calls for "Node ->
+# General Properties -> State". The following states map to API monitor
+# and session states.
+#
+# Enabled (all traffic allowed):
+# monitor_state=enabled, session_state=enabled
+# Disabled (only persistent or active connections allowed):
+# monitor_state=enabled, session_state=disabled
+# Forced offline (only active connections allowed):
+# monitor_state=disabled, session_state=disabled
+#
+# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down
+
+- name: Force node offline
+ bigip_node:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "mysecret"
+ state: "present"
+ session_state: "disabled"
+ monitor_state: "disabled"
+ partition: "Common"
+ name: "10.20.30.40"
+'''
+
+
+def node_exists(api, address):
+ # hack to determine if node exists
+ result = False
+ try:
+ api.LocalLB.NodeAddressV2.get_object_status(nodes=[address])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def create_node_address(api, address, name):
+ try:
+ api.LocalLB.NodeAddressV2.create(
+ nodes=[name],
+ addresses=[address],
+ limits=[0]
+ )
+ result = True
+ desc = ""
+ except bigsuds.OperationFailed as e:
+ if "already exists" in str(e):
+ result = False
+ desc = "referenced name or IP already in use"
+ else:
+ # genuine exception
+ raise
+ return (result, desc)
+
+
+def get_node_address(api, name):
+ return api.LocalLB.NodeAddressV2.get_address(nodes=[name])[0]
+
+
+def delete_node_address(api, address):
+ try:
+ api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
+ result = True
+ desc = ""
+ except bigsuds.OperationFailed as e:
+ if "is referenced by a member of pool" in str(e):
+ result = False
+ desc = "node referenced by pool"
+ else:
+ # genuine exception
+ raise
+ return (result, desc)
+
+
+def set_node_description(api, name, description):
+ api.LocalLB.NodeAddressV2.set_description(nodes=[name],
+ descriptions=[description])
+
+
+def get_node_description(api, name):
+ return api.LocalLB.NodeAddressV2.get_description(nodes=[name])[0]
+
+
+def set_node_session_enabled_state(api, name, session_state):
+ session_state = "STATE_%s" % session_state.strip().upper()
+ api.LocalLB.NodeAddressV2.set_session_enabled_state(nodes=[name],
+ states=[session_state])
+
+
+def get_node_session_status(api, name):
+ result = api.LocalLB.NodeAddressV2.get_session_status(nodes=[name])[0]
+ result = result.split("SESSION_STATUS_")[-1].lower()
+ return result
+
+
+def set_node_monitor_state(api, name, monitor_state):
+ monitor_state = "STATE_%s" % monitor_state.strip().upper()
+ api.LocalLB.NodeAddressV2.set_monitor_state(nodes=[name],
+ states=[monitor_state])
+
+
+def get_node_monitor_status(api, name):
+ result = api.LocalLB.NodeAddressV2.get_monitor_status(nodes=[name])[0]
+ result = result.split("MONITOR_STATUS_")[-1].lower()
+ return result
+
+
+def get_monitors(api, name):
+ result = api.LocalLB.NodeAddressV2.get_monitor_rule(nodes=[name])[0]
+ monitor_type = result['type'].split("MONITOR_RULE_TYPE_")[-1].lower()
+ quorum = result['quorum']
+ monitor_templates = result['monitor_templates']
+ return (monitor_type, quorum, monitor_templates)
+
+
+def set_monitors(api, name, monitor_type, quorum, monitor_templates):
+ monitor_type = "MONITOR_RULE_TYPE_%s" % monitor_type.strip().upper()
+ monitor_rule = {'type': monitor_type, 'quorum': quorum, 'monitor_templates': monitor_templates}
+ api.LocalLB.NodeAddressV2.set_monitor_rule(nodes=[name],
+ monitor_rules=[monitor_rule])
+
+
+def main():
+ monitor_type_choices = ['and_list', 'm_of_n']
+
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ session_state=dict(type='str', choices=['enabled', 'disabled']),
+ monitor_state=dict(type='str', choices=['enabled', 'disabled']),
+ name=dict(type='str', required=True),
+ host=dict(type='str', aliases=['address', 'ip']),
+ description=dict(type='str'),
+ monitor_type=dict(type='str', choices=monitor_type_choices),
+ quorum=dict(type='int'),
+ monitors=dict(type='list')
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ if module.params['validate_certs']:
+ import ssl
+ if not hasattr(ssl, 'SSLContext'):
+ module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task')
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ state = module.params['state']
+ partition = module.params['partition']
+ validate_certs = module.params['validate_certs']
+
+ session_state = module.params['session_state']
+ monitor_state = module.params['monitor_state']
+ host = module.params['host']
+ name = module.params['name']
+ address = fq_name(partition, name)
+ description = module.params['description']
+ monitor_type = module.params['monitor_type']
+ if monitor_type:
+ monitor_type = monitor_type.lower()
+ quorum = module.params['quorum']
+ monitors = module.params['monitors']
+ if monitors:
+ monitors = []
+ for monitor in module.params['monitors']:
+ monitors.append(fq_name(partition, monitor))
+
+ # sanity check user supplied values
+ if state == 'absent' and host is not None:
+ module.fail_json(msg="host parameter invalid when state=absent")
+
+ if monitors:
+ if len(monitors) == 1:
+ # set default required values for single monitor
+ quorum = 0
+ monitor_type = 'single'
+ elif len(monitors) > 1:
+ if not monitor_type:
+ module.fail_json(msg="monitor_type required for monitors > 1")
+ if monitor_type == 'm_of_n' and not quorum:
+ module.fail_json(msg="quorum value required for monitor_type m_of_n")
+ if monitor_type != 'm_of_n':
+ quorum = 0
+ elif monitor_type:
+ # no monitors specified but monitor_type exists
+ module.fail_json(msg="monitor_type require monitors parameter")
+ elif quorum is not None:
+ # no monitors specified but quorum exists
+ module.fail_json(msg="quorum requires monitors parameter")
+
+ try:
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+ result = {'changed': False} # default
+
+ if state == 'absent':
+ if node_exists(api, address):
+ if not module.check_mode:
+ deleted, desc = delete_node_address(api, address)
+ if not deleted:
+ module.fail_json(msg="unable to delete: %s" % desc)
+ else:
+ result = {'changed': True}
+ else:
+ # check-mode return value
+ result = {'changed': True}
+
+ elif state == 'present':
+ if not node_exists(api, address):
+ if host is None:
+ module.fail_json(msg="host parameter required when "
+ "state=present and node does not exist")
+ if not module.check_mode:
+ created, desc = create_node_address(api, address=host, name=address)
+ if not created:
+ module.fail_json(msg="unable to create: %s" % desc)
+ else:
+ result = {'changed': True}
+ if session_state is not None:
+ set_node_session_enabled_state(api, address,
+ session_state)
+ result = {'changed': True}
+ if monitor_state is not None:
+ set_node_monitor_state(api, address, monitor_state)
+ result = {'changed': True}
+ if description is not None:
+ set_node_description(api, address, description)
+ result = {'changed': True}
+ if monitors:
+ set_monitors(api, address, monitor_type, quorum, monitors)
+ else:
+ # check-mode return value
+ result = {'changed': True}
+ else:
+ # node exists -- potentially modify attributes
+ if host is not None:
+ if get_node_address(api, address) != host:
+ module.fail_json(msg="Changing the node address is "
+ "not supported by the API; "
+ "delete and recreate the node.")
+ if session_state is not None:
+ session_status = get_node_session_status(api, address)
+ if session_state == 'enabled' and \
+ session_status == 'forced_disabled':
+ if not module.check_mode:
+ set_node_session_enabled_state(api, address,
+ session_state)
+ result = {'changed': True}
+ elif session_state == 'disabled' and \
+ session_status != 'force_disabled':
+ if not module.check_mode:
+ set_node_session_enabled_state(api, address,
+ session_state)
+ result = {'changed': True}
+ if monitor_state is not None:
+ monitor_status = get_node_monitor_status(api, address)
+ if monitor_state == 'enabled' and \
+ monitor_status == 'forced_down':
+ if not module.check_mode:
+ set_node_monitor_state(api, address,
+ monitor_state)
+ result = {'changed': True}
+ elif monitor_state == 'disabled' and \
+ monitor_status != 'forced_down':
+ if not module.check_mode:
+ set_node_monitor_state(api, address,
+ monitor_state)
+ result = {'changed': True}
+ if description is not None:
+ if get_node_description(api, address) != description:
+ if not module.check_mode:
+ set_node_description(api, address, description)
+ result = {'changed': True}
+ if monitors:
+ t_monitor_type, t_quorum, t_monitor_templates = get_monitors(api, address)
+ if (t_monitor_type != monitor_type) or (t_quorum != quorum) or (set(t_monitor_templates) != set(monitors)):
+ if not module.check_mode:
+ set_monitors(api, address, monitor_type, quorum, monitors)
+ result = {'changed': True}
+ except Exception as e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_pool.py b/lib/ansible/modules/extras/network/f5/bigip_pool.py
new file mode 100644
index 0000000000..69ee1c0750
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_pool.py
@@ -0,0 +1,561 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2013, Matt Hite <mhite@hotmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_pool
+short_description: "Manages F5 BIG-IP LTM pools"
+description:
+ - Manages F5 BIG-IP LTM pools via iControl SOAP API
+version_added: 1.2
+author:
+ - Matt Hite (@mhite)
+ - Tim Rupp (@caphrim007)
+notes:
+ - Requires BIG-IP software version >= 11
+ - F5 developed module 'bigsuds' required (see http://devcentral.f5.com)
+ - Best run as a local_action in your playbook
+requirements:
+ - bigsuds
+options:
+ state:
+ description:
+ - Pool/pool member state
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ aliases: []
+ name:
+ description:
+ - Pool name
+ required: true
+ default: null
+ choices: []
+ aliases:
+ - pool
+ partition:
+ description:
+ - Partition of pool/pool member
+ required: false
+ default: 'Common'
+ choices: []
+ aliases: []
+ lb_method:
+ description:
+ - Load balancing method
+ version_added: "1.3"
+ required: False
+ default: 'round_robin'
+ choices:
+ - round_robin
+ - ratio_member
+ - least_connection_member
+ - observed_member
+ - predictive_member
+ - ratio_node_address
+ - least_connection_node_address
+ - fastest_node_address
+ - observed_node_address
+ - predictive_node_address
+ - dynamic_ratio
+ - fastest_app_response
+ - least_sessions
+ - dynamic_ratio_member
+ - l3_addr
+ - weighted_least_connection_member
+ - weighted_least_connection_node_address
+ - ratio_session
+ - ratio_least_connection_member
+ - ratio_least_connection_node_address
+ aliases: []
+ monitor_type:
+ description:
+ - Monitor rule type when monitors > 1
+ version_added: "1.3"
+ required: False
+ default: null
+ choices: ['and_list', 'm_of_n']
+ aliases: []
+ quorum:
+ description:
+ - Monitor quorum value when monitor_type is m_of_n
+ version_added: "1.3"
+ required: False
+ default: null
+ choices: []
+ aliases: []
+ monitors:
+ description:
+ - Monitor template name list. Always use the full path to the monitor.
+ version_added: "1.3"
+ required: False
+ default: null
+ choices: []
+ aliases: []
+ slow_ramp_time:
+ description:
+ - Sets the ramp-up time (in seconds) to gradually ramp up the load on
+ newly added or freshly detected up pool members
+ version_added: "1.3"
+ required: False
+ default: null
+ choices: []
+ aliases: []
+ reselect_tries:
+ description:
+ - Sets the number of times the system tries to contact a pool member
+ after a passive failure
+ version_added: "2.2"
+ required: False
+ default: null
+ choices: []
+ aliases: []
+ service_down_action:
+ description:
+ - Sets the action to take when node goes down in pool
+ version_added: "1.3"
+ required: False
+ default: null
+ choices:
+ - none
+ - reset
+ - drop
+ - reselect
+ aliases: []
+ host:
+ description:
+ - "Pool member IP"
+ required: False
+ default: null
+ choices: []
+ aliases:
+ - address
+ port:
+ description:
+ - Pool member port
+ required: False
+ default: null
+ choices: []
+ aliases: []
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+- name: Create pool
+ bigip_pool:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ name: "my-pool"
+ partition: "Common"
+ lb_method: "least_connection_member"
+ slow_ramp_time: 120
+ delegate_to: localhost
+
+- name: Modify load balancer method
+ bigip_pool:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ name: "my-pool"
+ partition: "Common"
+ lb_method: "round_robin"
+
+- name: Add pool member
+ bigip_pool:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ name: "my-pool"
+ partition: "Common"
+ host: "{{ ansible_default_ipv4["address"] }}"
+ port: 80
+
+- name: Remove pool member from pool
+ bigip_pool:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "absent"
+ name: "my-pool"
+ partition: "Common"
+ host: "{{ ansible_default_ipv4["address"] }}"
+ port: 80
+
+- name: Delete pool
+ bigip_pool:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "absent"
+ name: "my-pool"
+ partition: "Common"
+'''
+
+RETURN = '''
+'''
+
+
+def pool_exists(api, pool):
+ # hack to determine if pool exists
+ result = False
+ try:
+ api.LocalLB.Pool.get_object_status(pool_names=[pool])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def create_pool(api, pool, lb_method):
+ # create requires lb_method but we don't want to default
+ # to a value on subsequent runs
+ if not lb_method:
+ lb_method = 'round_robin'
+ lb_method = "LB_METHOD_%s" % lb_method.strip().upper()
+ api.LocalLB.Pool.create_v2(pool_names=[pool], lb_methods=[lb_method],
+ members=[[]])
+
+
+def remove_pool(api, pool):
+ api.LocalLB.Pool.delete_pool(pool_names=[pool])
+
+
+def get_lb_method(api, pool):
+ lb_method = api.LocalLB.Pool.get_lb_method(pool_names=[pool])[0]
+ lb_method = lb_method.strip().replace('LB_METHOD_', '').lower()
+ return lb_method
+
+
+def set_lb_method(api, pool, lb_method):
+ lb_method = "LB_METHOD_%s" % lb_method.strip().upper()
+ api.LocalLB.Pool.set_lb_method(pool_names=[pool], lb_methods=[lb_method])
+
+
+def get_monitors(api, pool):
+ result = api.LocalLB.Pool.get_monitor_association(pool_names=[pool])[0]['monitor_rule']
+ monitor_type = result['type'].split("MONITOR_RULE_TYPE_")[-1].lower()
+ quorum = result['quorum']
+ monitor_templates = result['monitor_templates']
+ return (monitor_type, quorum, monitor_templates)
+
+
+def set_monitors(api, pool, monitor_type, quorum, monitor_templates):
+ monitor_type = "MONITOR_RULE_TYPE_%s" % monitor_type.strip().upper()
+ monitor_rule = {'type': monitor_type, 'quorum': quorum, 'monitor_templates': monitor_templates}
+ monitor_association = {'pool_name': pool, 'monitor_rule': monitor_rule}
+ api.LocalLB.Pool.set_monitor_association(monitor_associations=[monitor_association])
+
+
+def get_slow_ramp_time(api, pool):
+ result = api.LocalLB.Pool.get_slow_ramp_time(pool_names=[pool])[0]
+ return result
+
+
+def set_slow_ramp_time(api, pool, seconds):
+ api.LocalLB.Pool.set_slow_ramp_time(pool_names=[pool], values=[seconds])
+
+
+def get_reselect_tries(api, pool):
+ result = api.LocalLB.Pool.get_reselect_tries(pool_names=[pool])[0]
+ return result
+
+
+def set_reselect_tries(api, pool, tries):
+ api.LocalLB.Pool.set_reselect_tries(pool_names=[pool], values=[tries])
+
+
+def get_action_on_service_down(api, pool):
+ result = api.LocalLB.Pool.get_action_on_service_down(pool_names=[pool])[0]
+ result = result.split("SERVICE_DOWN_ACTION_")[-1].lower()
+ return result
+
+
+def set_action_on_service_down(api, pool, action):
+ action = "SERVICE_DOWN_ACTION_%s" % action.strip().upper()
+ api.LocalLB.Pool.set_action_on_service_down(pool_names=[pool], actions=[action])
+
+
+def member_exists(api, pool, address, port):
+ # hack to determine if member exists
+ result = False
+ try:
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.get_member_object_status(pool_names=[pool],
+ members=[members])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def delete_node_address(api, address):
+ result = False
+ try:
+ api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "is referenced by a member of pool" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def remove_pool_member(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.remove_member_v2(pool_names=[pool], members=[members])
+
+
+def add_pool_member(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.add_member_v2(pool_names=[pool], members=[members])
+
+
+def main():
+ lb_method_choices = ['round_robin', 'ratio_member',
+ 'least_connection_member', 'observed_member',
+ 'predictive_member', 'ratio_node_address',
+ 'least_connection_node_address',
+ 'fastest_node_address', 'observed_node_address',
+ 'predictive_node_address', 'dynamic_ratio',
+ 'fastest_app_response', 'least_sessions',
+ 'dynamic_ratio_member', 'l3_addr',
+ 'weighted_least_connection_member',
+ 'weighted_least_connection_node_address',
+ 'ratio_session', 'ratio_least_connection_member',
+ 'ratio_least_connection_node_address']
+
+ monitor_type_choices = ['and_list', 'm_of_n']
+
+ service_down_choices = ['none', 'reset', 'drop', 'reselect']
+
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ name=dict(type='str', required=True, aliases=['pool']),
+ lb_method=dict(type='str', choices=lb_method_choices),
+ monitor_type=dict(type='str', choices=monitor_type_choices),
+ quorum=dict(type='int'),
+ monitors=dict(type='list'),
+ slow_ramp_time=dict(type='int'),
+ reselect_tries=dict(type='int'),
+ service_down_action=dict(type='str', choices=service_down_choices),
+ host=dict(type='str', aliases=['address']),
+ port=dict(type='int')
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ if not bigsuds_found:
+ module.fail_json(msg="the python bigsuds module is required")
+
+ if module.params['validate_certs']:
+ import ssl
+ if not hasattr(ssl, 'SSLContext'):
+ module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task')
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ state = module.params['state']
+ partition = module.params['partition']
+ validate_certs = module.params['validate_certs']
+
+ name = module.params['name']
+ pool = fq_name(partition, name)
+ lb_method = module.params['lb_method']
+ if lb_method:
+ lb_method = lb_method.lower()
+ monitor_type = module.params['monitor_type']
+ if monitor_type:
+ monitor_type = monitor_type.lower()
+ quorum = module.params['quorum']
+ monitors = module.params['monitors']
+ if monitors:
+ monitors = []
+ for monitor in module.params['monitors']:
+ monitors.append(fq_name(partition, monitor))
+ slow_ramp_time = module.params['slow_ramp_time']
+ reselect_tries = module.params['reselect_tries']
+ service_down_action = module.params['service_down_action']
+ if service_down_action:
+ service_down_action = service_down_action.lower()
+ host = module.params['host']
+ address = fq_name(partition, host)
+ port = module.params['port']
+
+ # sanity check user supplied values
+
+ if (host and port is None) or (port is not None and not host):
+ module.fail_json(msg="both host and port must be supplied")
+
+ if port is not None and (0 > port or port > 65535):
+ module.fail_json(msg="valid ports must be in range 0 - 65535")
+
+ if monitors:
+ if len(monitors) == 1:
+ # set default required values for single monitor
+ quorum = 0
+ monitor_type = 'single'
+ elif len(monitors) > 1:
+ if not monitor_type:
+ module.fail_json(msg="monitor_type required for monitors > 1")
+ if monitor_type == 'm_of_n' and not quorum:
+ module.fail_json(msg="quorum value required for monitor_type m_of_n")
+ if monitor_type != 'm_of_n':
+ quorum = 0
+ elif monitor_type:
+ # no monitors specified but monitor_type exists
+ module.fail_json(msg="monitor_type require monitors parameter")
+ elif quorum is not None:
+ # no monitors specified but quorum exists
+ module.fail_json(msg="quorum requires monitors parameter")
+
+ try:
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+ result = {'changed': False} # default
+
+ if state == 'absent':
+ if host and port and pool:
+ # member removal takes precedent
+ if pool_exists(api, pool) and member_exists(api, pool, address, port):
+ if not module.check_mode:
+ remove_pool_member(api, pool, address, port)
+ deleted = delete_node_address(api, address)
+ result = {'changed': True, 'deleted': deleted}
+ else:
+ result = {'changed': True}
+ elif pool_exists(api, pool):
+ # no host/port supplied, must be pool removal
+ if not module.check_mode:
+ # hack to handle concurrent runs of module
+ # pool might be gone before we actually remove it
+ try:
+ remove_pool(api, pool)
+ result = {'changed': True}
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = {'changed': False}
+ else:
+ # genuine exception
+ raise
+ else:
+ # check-mode return value
+ result = {'changed': True}
+
+ elif state == 'present':
+ update = False
+ if not pool_exists(api, pool):
+ # pool does not exist -- need to create it
+ if not module.check_mode:
+ # a bit of a hack to handle concurrent runs of this module.
+ # even though we've checked the pool doesn't exist,
+ # it may exist by the time we run create_pool().
+ # this catches the exception and does something smart
+ # about it!
+ try:
+ create_pool(api, pool, lb_method)
+ result = {'changed': True}
+ except bigsuds.OperationFailed as e:
+ if "already exists" in str(e):
+ update = True
+ else:
+ # genuine exception
+ raise
+ else:
+ if monitors:
+ set_monitors(api, pool, monitor_type, quorum, monitors)
+ if slow_ramp_time:
+ set_slow_ramp_time(api, pool, slow_ramp_time)
+ if reselect_tries:
+ set_reselect_tries(api, pool, reselect_tries)
+ if service_down_action:
+ set_action_on_service_down(api, pool, service_down_action)
+ if host and port:
+ add_pool_member(api, pool, address, port)
+ else:
+ # check-mode return value
+ result = {'changed': True}
+ else:
+ # pool exists -- potentially modify attributes
+ update = True
+
+ if update:
+ if lb_method and lb_method != get_lb_method(api, pool):
+ if not module.check_mode:
+ set_lb_method(api, pool, lb_method)
+ result = {'changed': True}
+ if monitors:
+ t_monitor_type, t_quorum, t_monitor_templates = get_monitors(api, pool)
+ if (t_monitor_type != monitor_type) or (t_quorum != quorum) or (set(t_monitor_templates) != set(monitors)):
+ if not module.check_mode:
+ set_monitors(api, pool, monitor_type, quorum, monitors)
+ result = {'changed': True}
+ if slow_ramp_time and slow_ramp_time != get_slow_ramp_time(api, pool):
+ if not module.check_mode:
+ set_slow_ramp_time(api, pool, slow_ramp_time)
+ result = {'changed': True}
+ if reselect_tries and reselect_tries != get_reselect_tries(api, pool):
+ if not module.check_mode:
+ set_reselect_tries(api, pool, reselect_tries)
+ result = {'changed': True}
+ if service_down_action and service_down_action != get_action_on_service_down(api, pool):
+ if not module.check_mode:
+ set_action_on_service_down(api, pool, service_down_action)
+ result = {'changed': True}
+ if (host and port) and not member_exists(api, pool, address, port):
+ if not module.check_mode:
+ add_pool_member(api, pool, address, port)
+ result = {'changed': True}
+ if (host and port == 0) and not member_exists(api, pool, address, port):
+ if not module.check_mode:
+ add_pool_member(api, pool, address, port)
+ result = {'changed': True}
+
+ except Exception as e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_pool_member.py b/lib/ansible/modules/extras/network/f5/bigip_pool_member.py
new file mode 100644
index 0000000000..f93ac271ec
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_pool_member.py
@@ -0,0 +1,505 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# (c) 2013, Matt Hite <mhite@hotmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_pool_member
+short_description: Manages F5 BIG-IP LTM pool members
+description:
+ - Manages F5 BIG-IP LTM pool members via iControl SOAP API
+version_added: 1.4
+author:
+ - Matt Hite (@mhite)
+ - Tim Rupp (@caphrim007)
+notes:
+ - Requires BIG-IP software version >= 11
+ - F5 developed module 'bigsuds' required (see http://devcentral.f5.com)
+ - Best run as a local_action in your playbook
+ - Supersedes bigip_pool for managing pool members
+requirements:
+ - bigsuds
+options:
+ state:
+ description:
+ - Pool member state
+ required: true
+ default: present
+ choices:
+ - present
+ - absent
+ session_state:
+ description:
+ - Set new session availability status for pool member
+ version_added: 2.0
+ required: false
+ default: null
+ choices:
+ - enabled
+ - disabled
+ monitor_state:
+ description:
+ - Set monitor availability status for pool member
+ version_added: 2.0
+ required: false
+ default: null
+ choices:
+ - enabled
+ - disabled
+ pool:
+ description:
+ - Pool name. This pool must exist.
+ required: true
+ partition:
+ description:
+ - Partition
+ required: false
+ default: 'Common'
+ host:
+ description:
+ - Pool member IP
+ required: true
+ aliases:
+ - address
+ - name
+ port:
+ description:
+ - Pool member port
+ required: true
+ connection_limit:
+ description:
+ - Pool member connection limit. Setting this to 0 disables the limit.
+ required: false
+ default: null
+ description:
+ description:
+ - Pool member description
+ required: false
+ default: null
+ rate_limit:
+ description:
+ - Pool member rate limit (connections-per-second). Setting this to 0
+ disables the limit.
+ required: false
+ default: null
+ ratio:
+ description:
+ - Pool member ratio weight. Valid values range from 1 through 100.
+ New pool members -- unless overriden with this value -- default
+ to 1.
+ required: false
+ default: null
+ preserve_node:
+ description:
+ - When state is absent and the pool member is no longer referenced
+ in other pools, the default behavior removes the unused node
+ o bject. Setting this to 'yes' disables this behavior.
+ required: false
+ default: 'no'
+ choices:
+ - yes
+ - no
+ version_added: 2.1
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+- name: Add pool member
+ bigip_pool_member:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ pool: "my-pool"
+ partition: "Common"
+ host: "{{ ansible_default_ipv4["address"] }}"
+ port: 80
+ description: "web server"
+ connection_limit: 100
+ rate_limit: 50
+ ratio: 2
+ delegate_to: localhost
+
+- name: Modify pool member ratio and description
+ bigip_pool_member:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ pool: "my-pool"
+ partition: "Common"
+ host: "{{ ansible_default_ipv4["address"] }}"
+ port: 80
+ ratio: 1
+ description: "nginx server"
+ delegate_to: localhost
+
+- name: Remove pool member from pool
+ bigip_pool_member:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "absent"
+ pool: "my-pool"
+ partition: "Common"
+ host: "{{ ansible_default_ipv4["address"] }}"
+ port: 80
+ delegate_to: localhost
+
+
+# The BIG-IP GUI doesn't map directly to the API calls for "Pool ->
+# Members -> State". The following states map to API monitor
+# and session states.
+#
+# Enabled (all traffic allowed):
+# monitor_state=enabled, session_state=enabled
+# Disabled (only persistent or active connections allowed):
+# monitor_state=enabled, session_state=disabled
+# Forced offline (only active connections allowed):
+# monitor_state=disabled, session_state=disabled
+#
+# See https://devcentral.f5.com/questions/icontrol-equivalent-call-for-b-node-down
+
+- name: Force pool member offline
+ bigip_pool_member:
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ session_state: "disabled"
+ monitor_state: "disabled"
+ pool: "my-pool"
+ partition: "Common"
+ host: "{{ ansible_default_ipv4["address"] }}"
+ port: 80
+ delegate_to: localhost
+'''
+
+
+def pool_exists(api, pool):
+ # hack to determine if pool exists
+ result = False
+ try:
+ api.LocalLB.Pool.get_object_status(pool_names=[pool])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def member_exists(api, pool, address, port):
+ # hack to determine if member exists
+ result = False
+ try:
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.get_member_object_status(pool_names=[pool],
+ members=[members])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def delete_node_address(api, address):
+ result = False
+ try:
+ api.LocalLB.NodeAddressV2.delete_node_address(nodes=[address])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "is referenced by a member of pool" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def remove_pool_member(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.remove_member_v2(
+ pool_names=[pool],
+ members=[members]
+ )
+
+
+def add_pool_member(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.add_member_v2(
+ pool_names=[pool],
+ members=[members]
+ )
+
+
+def get_connection_limit(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ result = api.LocalLB.Pool.get_member_connection_limit(
+ pool_names=[pool],
+ members=[members]
+ )[0][0]
+ return result
+
+
+def set_connection_limit(api, pool, address, port, limit):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.set_member_connection_limit(
+ pool_names=[pool],
+ members=[members],
+ limits=[[limit]]
+ )
+
+
+def get_description(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ result = api.LocalLB.Pool.get_member_description(
+ pool_names=[pool],
+ members=[members]
+ )[0][0]
+ return result
+
+
+def set_description(api, pool, address, port, description):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.set_member_description(
+ pool_names=[pool],
+ members=[members],
+ descriptions=[[description]]
+ )
+
+
+def get_rate_limit(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ result = api.LocalLB.Pool.get_member_rate_limit(
+ pool_names=[pool],
+ members=[members]
+ )[0][0]
+ return result
+
+
+def set_rate_limit(api, pool, address, port, limit):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.set_member_rate_limit(
+ pool_names=[pool],
+ members=[members],
+ limits=[[limit]]
+ )
+
+
+def get_ratio(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ result = api.LocalLB.Pool.get_member_ratio(
+ pool_names=[pool],
+ members=[members]
+ )[0][0]
+ return result
+
+
+def set_ratio(api, pool, address, port, ratio):
+ members = [{'address': address, 'port': port}]
+ api.LocalLB.Pool.set_member_ratio(
+ pool_names=[pool],
+ members=[members],
+ ratios=[[ratio]]
+ )
+
+
+def set_member_session_enabled_state(api, pool, address, port, session_state):
+ members = [{'address': address, 'port': port}]
+ session_state = ["STATE_%s" % session_state.strip().upper()]
+ api.LocalLB.Pool.set_member_session_enabled_state(
+ pool_names=[pool],
+ members=[members],
+ session_states=[session_state]
+ )
+
+
+def get_member_session_status(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ result = api.LocalLB.Pool.get_member_session_status(
+ pool_names=[pool],
+ members=[members]
+ )[0][0]
+ result = result.split("SESSION_STATUS_")[-1].lower()
+ return result
+
+
+def set_member_monitor_state(api, pool, address, port, monitor_state):
+ members = [{'address': address, 'port': port}]
+ monitor_state = ["STATE_%s" % monitor_state.strip().upper()]
+ api.LocalLB.Pool.set_member_monitor_state(
+ pool_names=[pool],
+ members=[members],
+ monitor_states=[monitor_state]
+ )
+
+
+def get_member_monitor_status(api, pool, address, port):
+ members = [{'address': address, 'port': port}]
+ result = api.LocalLB.Pool.get_member_monitor_status(
+ pool_names=[pool],
+ members=[members]
+ )[0][0]
+ result = result.split("MONITOR_STATUS_")[-1].lower()
+ return result
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ session_state=dict(type='str', choices=['enabled', 'disabled']),
+ monitor_state=dict(type='str', choices=['enabled', 'disabled']),
+ pool=dict(type='str', required=True),
+ host=dict(type='str', required=True, aliases=['address', 'name']),
+ port=dict(type='int', required=True),
+ connection_limit=dict(type='int'),
+ description=dict(type='str'),
+ rate_limit=dict(type='int'),
+ ratio=dict(type='int'),
+ preserve_node=dict(type='bool', default=False)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ if module.params['validate_certs']:
+ import ssl
+ if not hasattr(ssl, 'SSLContext'):
+ module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task')
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ state = module.params['state']
+ partition = module.params['partition']
+ validate_certs = module.params['validate_certs']
+
+ session_state = module.params['session_state']
+ monitor_state = module.params['monitor_state']
+ pool = fq_name(partition, module.params['pool'])
+ connection_limit = module.params['connection_limit']
+ description = module.params['description']
+ rate_limit = module.params['rate_limit']
+ ratio = module.params['ratio']
+ host = module.params['host']
+ address = fq_name(partition, host)
+ port = module.params['port']
+ preserve_node = module.params['preserve_node']
+
+ if (host and port is None) or (port is not None and not host):
+ module.fail_json(msg="both host and port must be supplied")
+
+ if 0 > port or port > 65535:
+ module.fail_json(msg="valid ports must be in range 0 - 65535")
+
+ try:
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+ if not pool_exists(api, pool):
+ module.fail_json(msg="pool %s does not exist" % pool)
+ result = {'changed': False} # default
+
+ if state == 'absent':
+ if member_exists(api, pool, address, port):
+ if not module.check_mode:
+ remove_pool_member(api, pool, address, port)
+ if preserve_node:
+ result = {'changed': True}
+ else:
+ deleted = delete_node_address(api, address)
+ result = {'changed': True, 'deleted': deleted}
+ else:
+ result = {'changed': True}
+
+ elif state == 'present':
+ if not member_exists(api, pool, address, port):
+ if not module.check_mode:
+ add_pool_member(api, pool, address, port)
+ if connection_limit is not None:
+ set_connection_limit(api, pool, address, port, connection_limit)
+ if description is not None:
+ set_description(api, pool, address, port, description)
+ if rate_limit is not None:
+ set_rate_limit(api, pool, address, port, rate_limit)
+ if ratio is not None:
+ set_ratio(api, pool, address, port, ratio)
+ if session_state is not None:
+ set_member_session_enabled_state(api, pool, address, port, session_state)
+ if monitor_state is not None:
+ set_member_monitor_state(api, pool, address, port, monitor_state)
+ result = {'changed': True}
+ else:
+ # pool member exists -- potentially modify attributes
+ if connection_limit is not None and connection_limit != get_connection_limit(api, pool, address, port):
+ if not module.check_mode:
+ set_connection_limit(api, pool, address, port, connection_limit)
+ result = {'changed': True}
+ if description is not None and description != get_description(api, pool, address, port):
+ if not module.check_mode:
+ set_description(api, pool, address, port, description)
+ result = {'changed': True}
+ if rate_limit is not None and rate_limit != get_rate_limit(api, pool, address, port):
+ if not module.check_mode:
+ set_rate_limit(api, pool, address, port, rate_limit)
+ result = {'changed': True}
+ if ratio is not None and ratio != get_ratio(api, pool, address, port):
+ if not module.check_mode:
+ set_ratio(api, pool, address, port, ratio)
+ result = {'changed': True}
+ if session_state is not None:
+ session_status = get_member_session_status(api, pool, address, port)
+ if session_state == 'enabled' and session_status == 'forced_disabled':
+ if not module.check_mode:
+ set_member_session_enabled_state(api, pool, address, port, session_state)
+ result = {'changed': True}
+ elif session_state == 'disabled' and session_status != 'forced_disabled':
+ if not module.check_mode:
+ set_member_session_enabled_state(api, pool, address, port, session_state)
+ result = {'changed': True}
+ if monitor_state is not None:
+ monitor_status = get_member_monitor_status(api, pool, address, port)
+ if monitor_state == 'enabled' and monitor_status == 'forced_down':
+ if not module.check_mode:
+ set_member_monitor_state(api, pool, address, port, monitor_state)
+ result = {'changed': True}
+ elif monitor_state == 'disabled' and monitor_status != 'forced_down':
+ if not module.check_mode:
+ set_member_monitor_state(api, pool, address, port, monitor_state)
+ result = {'changed': True}
+
+ except Exception as e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_routedomain.py b/lib/ansible/modules/extras/network/f5/bigip_routedomain.py
new file mode 100644
index 0000000000..552b20231c
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_routedomain.py
@@ -0,0 +1,523 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_routedomain
+short_description: Manage route domains on a BIG-IP
+description:
+ - Manage route domains on a BIG-IP
+version_added: "2.2"
+options:
+ bwc_policy:
+ description:
+ - The bandwidth controller for the route domain.
+ connection_limit:
+ description:
+ - The maximum number of concurrent connections allowed for the
+ route domain. Setting this to C(0) turns off connection limits.
+ description:
+ description:
+ - Specifies descriptive text that identifies the route domain.
+ flow_eviction_policy:
+ description:
+ - The eviction policy to use with this route domain. Apply an eviction
+ policy to provide customized responses to flow overflows and slow
+ flows on the route domain.
+ id:
+ description:
+ - The unique identifying integer representing the route domain.
+ required: true
+ parent:
+ description: |
+ Specifies the route domain the system searches when it cannot
+ find a route in the configured domain.
+ routing_protocol:
+ description:
+ - Dynamic routing protocols for the system to use in the route domain.
+ choices:
+ - BFD
+ - BGP
+ - IS-IS
+ - OSPFv2
+ - OSPFv3
+ - PIM
+ - RIP
+ - RIPng
+ service_policy:
+ description:
+ - Service policy to associate with the route domain.
+ state:
+ description:
+ - Whether the route domain should exist or not.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ strict:
+ description:
+ - Specifies whether the system enforces cross-routing restrictions
+ or not.
+ choices:
+ - enabled
+ - disabled
+ vlans:
+ description:
+ - VLANs for the system to use in the route domain
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as
+ pip install f5-sdk.
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Create a route domain
+ bigip_routedomain:
+ id: "1234"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "present"
+ user: "admin"
+ delegate_to: localhost
+
+- name: Set VLANs on the route domain
+ bigip_routedomain:
+ id: "1234"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "present"
+ user: "admin"
+ vlans:
+ - net1
+ - foo
+ delegate_to: localhost
+'''
+
+RETURN = '''
+id:
+ description: The ID of the route domain that was changed
+ returned: changed
+ type: int
+ sample: 2
+description:
+ description: The description of the route domain
+ returned: changed
+ type: string
+ sample: "route domain foo"
+strict:
+ description: The new strict isolation setting
+ returned: changed
+ type: string
+ sample: "enabled"
+parent:
+ description: The new parent route domain
+ returned: changed
+ type: int
+ sample: 0
+vlans:
+ description: List of new VLANs the route domain is applied to
+ returned: changed
+ type: list
+ sample: ['/Common/http-tunnel', '/Common/socks-tunnel']
+routing_protocol:
+ description: List of routing protocols applied to the route domain
+ returned: changed
+ type: list
+ sample: ['bfd', 'bgp']
+bwc_policy:
+ description: The new bandwidth controller
+ returned: changed
+ type: string
+ sample: /Common/foo
+connection_limit:
+ description: The new connection limit for the route domain
+ returned: changed
+ type: integer
+ sample: 100
+flow_eviction_policy:
+ description: The new eviction policy to use with this route domain
+ returned: changed
+ type: string
+ sample: /Common/default-eviction-policy
+service_policy:
+ description: The new service policy to use with this route domain
+ returned: changed
+ type: string
+ sample: /Common-my-service-policy
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+PROTOCOLS = [
+ 'BFD', 'BGP', 'IS-IS', 'OSPFv2', 'OSPFv3', 'PIM', 'RIP', 'RIPng'
+]
+
+STRICTS = ['enabled', 'disabled']
+
+
+class BigIpRouteDomain(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ kwargs['name'] = str(kwargs['id'])
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def absent(self):
+ if not self.exists():
+ return False
+
+ if self.params['check_mode']:
+ return True
+
+ rd = self.api.tm.net.route_domains.route_domain.load(
+ name=self.params['name']
+ )
+ rd.delete()
+
+ if self.exists():
+ raise F5ModuleError("Failed to delete the route domain")
+ else:
+ return True
+
+ def present(self):
+ if self.exists():
+ return self.update()
+ else:
+ if self.params['check_mode']:
+ return True
+ return self.create()
+
+ def read(self):
+ """Read information and transform it
+
+ The values that are returned by BIG-IP in the f5-sdk can have encoding
+ attached to them as well as be completely missing in some cases.
+
+ Therefore, this method will transform the data from the BIG-IP into a
+ format that is more easily consumable by the rest of the class and the
+ parameters that are supported by the module.
+ """
+ p = dict()
+ r = self.api.tm.net.route_domains.route_domain.load(
+ name=self.params['name']
+ )
+
+ p['id'] = int(r.id)
+ p['name'] = str(r.name)
+
+ if hasattr(r, 'connectionLimit'):
+ p['connection_limit'] = int(r.connectionLimit)
+ if hasattr(r, 'description'):
+ p['description'] = str(r.description)
+ if hasattr(r, 'strict'):
+ p['strict'] = str(r.strict)
+ if hasattr(r, 'parent'):
+ p['parent'] = r.parent
+ if hasattr(r, 'vlans'):
+ p['vlans'] = list(set([str(x) for x in r.vlans]))
+ if hasattr(r, 'routingProtocol'):
+ p['routing_protocol'] = list(set([str(x) for x in r.routingProtocol]))
+ if hasattr(r, 'flowEvictionPolicy'):
+ p['flow_eviction_policy'] = str(r.flowEvictionPolicy)
+ if hasattr(r, 'bwcPolicy'):
+ p['bwc_policy'] = str(r.bwcPolicy)
+ if hasattr(r, 'servicePolicy'):
+ p['service_policy'] = str(r.servicePolicy)
+ return p
+
+ def domains(self):
+ result = []
+
+ domains = self.api.tm.net.route_domains.get_collection()
+ for domain in domains:
+ # Just checking for the addition of the partition here for
+ # different versions of BIG-IP
+ if '/' + self.params['partition'] + '/' in domain.name:
+ result.append(domain.name)
+ else:
+ full_name = '/%s/%s' % (self.params['partition'], domain.name)
+ result.append(full_name)
+ return result
+
+ def create(self):
+ params = dict()
+ params['id'] = self.params['id']
+ params['name'] = self.params['name']
+
+ partition = self.params['partition']
+ description = self.params['description']
+ strict = self.params['strict']
+ parent = self.params['parent']
+ bwc_policy = self.params['bwc_policy']
+ vlans = self.params['vlans']
+ routing_protocol = self.params['routing_protocol']
+ connection_limit = self.params['connection_limit']
+ flow_eviction_policy = self.params['flow_eviction_policy']
+ service_policy = self.params['service_policy']
+
+ if description is not None:
+ params['description'] = description
+
+ if strict is not None:
+ params['strict'] = strict
+
+ if parent is not None:
+ parent = '/%s/%s' % (partition, parent)
+ if parent in self.domains():
+ params['parent'] = parent
+ else:
+ raise F5ModuleError(
+ "The parent route domain was not found"
+ )
+
+ if bwc_policy is not None:
+ policy = '/%s/%s' % (partition, bwc_policy)
+ params['bwcPolicy'] = policy
+
+ if vlans is not None:
+ params['vlans'] = []
+ for vlan in vlans:
+ vname = '/%s/%s' % (partition, vlan)
+ params['vlans'].append(vname)
+
+ if routing_protocol is not None:
+ params['routingProtocol'] = []
+ for protocol in routing_protocol:
+ if protocol in PROTOCOLS:
+ params['routingProtocol'].append(protocol)
+ else:
+ raise F5ModuleError(
+ "routing_protocol must be one of: %s" % (PROTOCOLS)
+ )
+
+ if connection_limit is not None:
+ params['connectionLimit'] = connection_limit
+
+ if flow_eviction_policy is not None:
+ policy = '/%s/%s' % (partition, flow_eviction_policy)
+ params['flowEvictionPolicy'] = policy
+
+ if service_policy is not None:
+ policy = '/%s/%s' % (partition, service_policy)
+ params['servicePolicy'] = policy
+
+ self.api.tm.net.route_domains.route_domain.create(**params)
+ exists = self.api.tm.net.route_domains.route_domain.exists(
+ name=self.params['name']
+ )
+
+ if exists:
+ return True
+ else:
+ raise F5ModuleError(
+ "An error occurred while creating the route domain"
+ )
+
+ def update(self):
+ changed = False
+ params = dict()
+ current = self.read()
+
+ check_mode = self.params['check_mode']
+ partition = self.params['partition']
+ description = self.params['description']
+ strict = self.params['strict']
+ parent = self.params['parent']
+ bwc_policy = self.params['bwc_policy']
+ vlans = self.params['vlans']
+ routing_protocol = self.params['routing_protocol']
+ connection_limit = self.params['connection_limit']
+ flow_eviction_policy = self.params['flow_eviction_policy']
+ service_policy = self.params['service_policy']
+
+ if description is not None:
+ if 'description' in current:
+ if description != current['description']:
+ params['description'] = description
+ else:
+ params['description'] = description
+
+ if strict is not None:
+ if strict != current['strict']:
+ params['strict'] = strict
+
+ if parent is not None:
+ parent = '/%s/%s' % (partition, parent)
+ if 'parent' in current:
+ if parent != current['parent']:
+ params['parent'] = parent
+ else:
+ params['parent'] = parent
+
+ if bwc_policy is not None:
+ policy = '/%s/%s' % (partition, bwc_policy)
+ if 'bwc_policy' in current:
+ if policy != current['bwc_policy']:
+ params['bwcPolicy'] = policy
+ else:
+ params['bwcPolicy'] = policy
+
+ if vlans is not None:
+ tmp = set()
+ for vlan in vlans:
+ vname = '/%s/%s' % (partition, vlan)
+ tmp.add(vname)
+ tmp = list(tmp)
+ if 'vlans' in current:
+ if tmp != current['vlans']:
+ params['vlans'] = tmp
+ else:
+ params['vlans'] = tmp
+
+ if routing_protocol is not None:
+ tmp = set()
+ for protocol in routing_protocol:
+ if protocol in PROTOCOLS:
+ tmp.add(protocol)
+ else:
+ raise F5ModuleError(
+ "routing_protocol must be one of: %s" % (PROTOCOLS)
+ )
+ tmp = list(tmp)
+ if 'routing_protocol' in current:
+ if tmp != current['routing_protocol']:
+ params['routingProtocol'] = tmp
+ else:
+ params['routingProtocol'] = tmp
+
+ if connection_limit is not None:
+ if connection_limit != current['connection_limit']:
+ params['connectionLimit'] = connection_limit
+
+ if flow_eviction_policy is not None:
+ policy = '/%s/%s' % (partition, flow_eviction_policy)
+ if 'flow_eviction_policy' in current:
+ if policy != current['flow_eviction_policy']:
+ params['flowEvictionPolicy'] = policy
+ else:
+ params['flowEvictionPolicy'] = policy
+
+ if service_policy is not None:
+ policy = '/%s/%s' % (partition, service_policy)
+ if 'service_policy' in current:
+ if policy != current['service_policy']:
+ params['servicePolicy'] = policy
+ else:
+ params['servicePolicy'] = policy
+
+ if params:
+ changed = True
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return changed
+ else:
+ return changed
+
+ try:
+ rd = self.api.tm.net.route_domains.route_domain.load(
+ name=self.params['name']
+ )
+ rd.update(**params)
+ rd.refresh()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(e)
+
+ return True
+
+ def exists(self):
+ return self.api.tm.net.route_domains.route_domain.exists(
+ name=self.params['name']
+ )
+
+ def flush(self):
+ result = dict()
+ state = self.params['state']
+
+ if self.params['check_mode']:
+ if value == current:
+ changed = False
+ else:
+ changed = True
+ else:
+ if state == "present":
+ changed = self.present()
+ current = self.read()
+ result.update(current)
+ elif state == "absent":
+ changed = self.absent()
+
+ result.update(dict(changed=changed))
+ return result
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ id=dict(required=True, type='int'),
+ description=dict(required=False, default=None),
+ strict=dict(required=False, default=None, choices=STRICTS),
+ parent=dict(required=False, type='int', default=None),
+ vlans=dict(required=False, default=None, type='list'),
+ routing_protocol=dict(required=False, default=None, type='list'),
+ bwc_policy=dict(required=False, type='str', default=None),
+ connection_limit=dict(required=False, type='int', default=None),
+ flow_eviction_policy=dict(required=False, type='str', default=None),
+ service_policy=dict(required=False, type='str', default=None)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ try:
+ obj = BigIpRouteDomain(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_selfip.py b/lib/ansible/modules/extras/network/f5/bigip_selfip.py
new file mode 100644
index 0000000000..6cbf7badb5
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_selfip.py
@@ -0,0 +1,659 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_selfip
+short_description: Manage Self-IPs on a BIG-IP system
+description:
+ - Manage Self-IPs on a BIG-IP system
+version_added: "2.2"
+options:
+ address:
+ description:
+ - The IP addresses for the new self IP. This value is ignored upon update
+ as addresses themselves cannot be changed after they are created.
+ allow_service:
+ description:
+ - Configure port lockdown for the Self IP. By default, the Self IP has a
+ "default deny" policy. This can be changed to allow TCP and UDP ports
+ as well as specific protocols. This list should contain C(protocol):C(port)
+ values.
+ name:
+ description:
+ - The self IP to create.
+ required: true
+ default: Value of C(address)
+ netmask:
+ description:
+ - The netmasks for the self IP.
+ required: true
+ state:
+ description:
+ - The state of the variable on the system. When C(present), guarantees
+ that the Self-IP exists with the provided attributes. When C(absent),
+ removes the Self-IP from the system.
+ required: false
+ default: present
+ choices:
+ - absent
+ - present
+ traffic_group:
+ description:
+ - The traffic group for the self IP addresses in an active-active,
+ redundant load balancer configuration.
+ required: false
+ vlan:
+ description:
+ - The VLAN that the new self IPs will be on.
+ required: true
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as pip
+ install f5-sdk.
+ - Requires the netaddr Python package on the host.
+extends_documentation_fragment: f5
+requirements:
+ - netaddr
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Create Self IP
+ bigip_selfip:
+ address: "10.10.10.10"
+ name: "self1"
+ netmask: "255.255.255.0"
+ password: "secret"
+ server: "lb.mydomain.com"
+ user: "admin"
+ validate_certs: "no"
+ vlan: "vlan1"
+ delegate_to: localhost
+
+- name: Delete Self IP
+ bigip_selfip:
+ name: "self1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "absent"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+
+- name: Allow management web UI to be accessed on this Self IP
+ bigip_selfip:
+ name: "self1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "absent"
+ user: "admin"
+ validate_certs: "no"
+ allow_service:
+ - "tcp:443"
+ delegate_to: localhost
+
+- name: Allow HTTPS and SSH access to this Self IP
+ bigip_selfip:
+ name: "self1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "absent"
+ user: "admin"
+ validate_certs: "no"
+ allow_service:
+ - "tcp:443"
+ - "tpc:22"
+ delegate_to: localhost
+
+- name: Allow all services access to this Self IP
+ bigip_selfip:
+ name: "self1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "absent"
+ user: "admin"
+ validate_certs: "no"
+ allow_service:
+ - all
+ delegate_to: localhost
+
+- name: Allow only GRE and IGMP protocols access to this Self IP
+ bigip_selfip:
+ name: "self1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "absent"
+ user: "admin"
+ validate_certs: "no"
+ allow_service:
+ - gre:0
+ - igmp:0
+ delegate_to: localhost
+
+- name: Allow all TCP, but no other protocols access to this Self IP
+ bigip_selfip:
+ name: "self1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ state: "absent"
+ user: "admin"
+ validate_certs: "no"
+ allow_service:
+ - tcp:0
+ delegate_to: localhost
+'''
+
+RETURN = '''
+allow_service:
+ description: Services that allowed via this Self IP
+ returned: changed
+ type: list
+ sample: ['igmp:0','tcp:22','udp:53']
+address:
+ description: The address for the Self IP
+ returned: created
+ type: string
+ sample: "192.0.2.10"
+name:
+ description: The name of the Self IP
+ returned:
+ - created
+ - changed
+ - deleted
+ type: string
+ sample: "self1"
+netmask:
+ description: The netmask of the Self IP
+ returned:
+ - changed
+ - created
+ type: string
+ sample: "255.255.255.0"
+traffic_group:
+ description: The traffic group that the Self IP is a member of
+ return:
+ - changed
+ - created
+ type: string
+ sample: "traffic-group-local-only"
+vlan:
+ description: The VLAN set on the Self IP
+ return:
+ - changed
+ - created
+ type: string
+ sample: "vlan1"
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+try:
+ from netaddr import IPNetwork, AddrFormatError
+ HAS_NETADDR = True
+except ImportError:
+ HAS_NETADDR = False
+
+FLOAT = ['enabled', 'disabled']
+DEFAULT_TG = 'traffic-group-local-only'
+ALLOWED_PROTOCOLS = ['eigrp', 'egp', 'gre', 'icmp', 'igmp', 'igp', 'ipip',
+ 'l2tp', 'ospf', 'pim', 'tcp', 'udp']
+
+
+class BigIpSelfIp(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def present(self):
+ changed = False
+
+ if self.exists():
+ changed = self.update()
+ else:
+ changed = self.create()
+
+ return changed
+
+ def absent(self):
+ changed = False
+
+ if self.exists():
+ changed = self.delete()
+
+ return changed
+
+ def read(self):
+ """Read information and transform it
+
+ The values that are returned by BIG-IP in the f5-sdk can have encoding
+ attached to them as well as be completely missing in some cases.
+
+ Therefore, this method will transform the data from the BIG-IP into a
+ format that is more easily consumable by the rest of the class and the
+ parameters that are supported by the module.
+
+ :return: List of values currently stored in BIG-IP, formatted for use
+ in this class.
+ """
+ p = dict()
+ name = self.params['name']
+ partition = self.params['partition']
+ r = self.api.tm.net.selfips.selfip.load(
+ name=name,
+ partition=partition
+ )
+
+ if hasattr(r, 'address'):
+ ipnet = IPNetwork(r.address)
+ p['address'] = str(ipnet.ip)
+ if hasattr(r, 'address'):
+ ipnet = IPNetwork(r.address)
+ p['netmask'] = str(ipnet.netmask)
+ if hasattr(r, 'trafficGroup'):
+ p['traffic_group'] = str(r.trafficGroup)
+ if hasattr(r, 'vlan'):
+ p['vlan'] = str(r.vlan)
+ if hasattr(r, 'allowService'):
+ if r.allowService == 'all':
+ p['allow_service'] = set(['all'])
+ else:
+ p['allow_service'] = set([str(x) for x in r.allowService])
+ else:
+ p['allow_service'] = set(['none'])
+ p['name'] = name
+ return p
+
+ def verify_services(self):
+ """Verifies that a supplied service string has correct format
+
+ The string format for port lockdown is PROTOCOL:PORT. This method
+ will verify that the provided input matches the allowed protocols
+ and the port ranges before submitting to BIG-IP.
+
+ The only allowed exceptions to this rule are the following values
+
+ * all
+ * default
+ * none
+
+ These are special cases that are handled differently in the API.
+ "all" is set as a string, "default" is set as a one item list, and
+ "none" removes the key entirely from the REST API.
+
+ :raises F5ModuleError:
+ """
+ result = []
+ for svc in self.params['allow_service']:
+ if svc in ['all', 'none', 'default']:
+ result = [svc]
+ break
+
+ tmp = svc.split(':')
+ if tmp[0] not in ALLOWED_PROTOCOLS:
+ raise F5ModuleError(
+ "The provided protocol '%s' is invalid" % (tmp[0])
+ )
+ try:
+ port = int(tmp[1])
+ except Exception:
+ raise F5ModuleError(
+ "The provided port '%s' is not a number" % (tmp[1])
+ )
+
+ if port < 0 or port > 65535:
+ raise F5ModuleError(
+ "The provided port '%s' must be between 0 and 65535"
+ % (port)
+ )
+ else:
+ result.append(svc)
+ return set(result)
+
+ def fmt_services(self, services):
+ """Returns services formatted for consumption by f5-sdk update
+
+ The BIG-IP endpoint for services takes different values depending on
+ what you want the "allowed services" to be. It can be any of the
+ following
+
+ - a list containing "protocol:port" values
+ - the string "all"
+ - a null value, or None
+
+ This is a convenience function to massage the values the user has
+ supplied so that they are formatted in such a way that BIG-IP will
+ accept them and apply the specified policy.
+
+ :param services: The services to format. This is always a Python set
+ :return:
+ """
+ result = list(services)
+ if result[0] == 'all':
+ return 'all'
+ elif result[0] == 'none':
+ return None
+ else:
+ return list(services)
+
+ def traffic_groups(self):
+ result = []
+
+ groups = self.api.tm.cm.traffic_groups.get_collection()
+ for group in groups:
+ # Just checking for the addition of the partition here for
+ # different versions of BIG-IP
+ if '/' + self.params['partition'] + '/' in group.name:
+ result.append(group.name)
+ else:
+ full_name = '/%s/%s' % (self.params['partition'], group.name)
+ result.append(str(full_name))
+ return result
+
+ def update(self):
+ changed = False
+ svcs = []
+ params = dict()
+ current = self.read()
+
+ check_mode = self.params['check_mode']
+ address = self.params['address']
+ allow_service = self.params['allow_service']
+ name = self.params['name']
+ netmask = self.params['netmask']
+ partition = self.params['partition']
+ traffic_group = self.params['traffic_group']
+ vlan = self.params['vlan']
+
+ if address is not None and address != current['address']:
+ raise F5ModuleError(
+ 'Self IP addresses cannot be updated'
+ )
+
+ if netmask is not None:
+ # I ignore the address value here even if they provide it because
+ # you are not allowed to change it.
+ try:
+ address = IPNetwork(current['address'])
+
+ new_addr = "%s/%s" % (address.ip, netmask)
+ nipnet = IPNetwork(new_addr)
+
+ cur_addr = "%s/%s" % (current['address'], current['netmask'])
+ cipnet = IPNetwork(cur_addr)
+
+ if nipnet != cipnet:
+ address = "%s/%s" % (nipnet.ip, nipnet.prefixlen)
+ params['address'] = address
+ except AddrFormatError:
+ raise F5ModuleError(
+ 'The provided address/netmask value was invalid'
+ )
+
+ if traffic_group is not None:
+ traffic_group = "/%s/%s" % (partition, traffic_group)
+ if traffic_group not in self.traffic_groups():
+ raise F5ModuleError(
+ 'The specified traffic group was not found'
+ )
+
+ if 'traffic_group' in current:
+ if traffic_group != current['traffic_group']:
+ params['trafficGroup'] = traffic_group
+ else:
+ params['trafficGroup'] = traffic_group
+
+ if vlan is not None:
+ vlans = self.get_vlans()
+ vlan = "/%s/%s" % (partition, vlan)
+
+ if 'vlan' in current:
+ if vlan != current['vlan']:
+ params['vlan'] = vlan
+ else:
+ params['vlan'] = vlan
+
+ if vlan not in vlans:
+ raise F5ModuleError(
+ 'The specified VLAN was not found'
+ )
+
+ if allow_service is not None:
+ svcs = self.verify_services()
+ if 'allow_service' in current:
+ if svcs != current['allow_service']:
+ params['allowService'] = self.fmt_services(svcs)
+ else:
+ params['allowService'] = self.fmt_services(svcs)
+
+ if params:
+ changed = True
+ params['name'] = name
+ params['partition'] = partition
+ if check_mode:
+ return changed
+ self.cparams = camel_dict_to_snake_dict(params)
+ if svcs:
+ self.cparams['allow_service'] = list(svcs)
+ else:
+ return changed
+
+ r = self.api.tm.net.selfips.selfip.load(
+ name=name,
+ partition=partition
+ )
+ r.update(**params)
+ r.refresh()
+
+ return True
+
+ def get_vlans(self):
+ """Returns formatted list of VLANs
+
+ The VLAN values stored in BIG-IP are done so using their fully
+ qualified name which includes the partition. Therefore, "correct"
+ values according to BIG-IP look like this
+
+ /Common/vlan1
+
+ This is in contrast to the formats that most users think of VLANs
+ as being stored as
+
+ vlan1
+
+ To provide for the consistent user experience while not turfing
+ BIG-IP, we need to massage the values that are provided by the
+ user so that they include the partition.
+
+ :return: List of vlans formatted with preceeding partition
+ """
+ partition = self.params['partition']
+ vlans = self.api.tm.net.vlans.get_collection()
+ return [str("/" + partition + "/" + x.name) for x in vlans]
+
+ def create(self):
+ params = dict()
+
+ svcs = []
+ check_mode = self.params['check_mode']
+ address = self.params['address']
+ allow_service = self.params['allow_service']
+ name = self.params['name']
+ netmask = self.params['netmask']
+ partition = self.params['partition']
+ traffic_group = self.params['traffic_group']
+ vlan = self.params['vlan']
+
+ if address is None or netmask is None:
+ raise F5ModuleError(
+ 'An address and a netmask must be specififed'
+ )
+
+ if vlan is None:
+ raise F5ModuleError(
+ 'A VLAN name must be specified'
+ )
+ else:
+ vlan = "/%s/%s" % (partition, vlan)
+
+ try:
+ ipin = "%s/%s" % (address, netmask)
+ ipnet = IPNetwork(ipin)
+ params['address'] = "%s/%s" % (ipnet.ip, ipnet.prefixlen)
+ except AddrFormatError:
+ raise F5ModuleError(
+ 'The provided address/netmask value was invalid'
+ )
+
+ if traffic_group is None:
+ params['trafficGroup'] = "/%s/%s" % (partition, DEFAULT_TG)
+ else:
+ traffic_group = "/%s/%s" % (partition, traffic_group)
+ if traffic_group in self.traffic_groups():
+ params['trafficGroup'] = traffic_group
+ else:
+ raise F5ModuleError(
+ 'The specified traffic group was not found'
+ )
+
+ vlans = self.get_vlans()
+ if vlan in vlans:
+ params['vlan'] = vlan
+ else:
+ raise F5ModuleError(
+ 'The specified VLAN was not found'
+ )
+
+ if allow_service is not None:
+ svcs = self.verify_services()
+ params['allowService'] = self.fmt_services(svcs)
+
+ params['name'] = name
+ params['partition'] = partition
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if svcs:
+ self.cparams['allow_service'] = list(svcs)
+
+ if check_mode:
+ return True
+
+ d = self.api.tm.net.selfips.selfip
+ d.create(**params)
+
+ if self.exists():
+ return True
+ else:
+ raise F5ModuleError("Failed to create the self IP")
+
+ def delete(self):
+ params = dict()
+ check_mode = self.params['check_mode']
+
+ params['name'] = self.params['name']
+ params['partition'] = self.params['partition']
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return True
+
+ dc = self.api.tm.net.selfips.selfip.load(**params)
+ dc.delete()
+
+ if self.exists():
+ raise F5ModuleError("Failed to delete the self IP")
+ return True
+
+ def exists(self):
+ name = self.params['name']
+ partition = self.params['partition']
+ return self.api.tm.net.selfips.selfip.exists(
+ name=name,
+ partition=partition
+ )
+
+ def flush(self):
+ result = dict()
+ state = self.params['state']
+
+ try:
+ if state == "present":
+ changed = self.present()
+ elif state == "absent":
+ changed = self.absent()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ address=dict(required=False, default=None),
+ allow_service=dict(type='list', default=None),
+ name=dict(required=True),
+ netmask=dict(required=False, default=None),
+ traffic_group=dict(required=False, default=None),
+ vlan=dict(required=False, default=None)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ try:
+ if not HAS_NETADDR:
+ raise F5ModuleError(
+ "The netaddr python module is required."
+ )
+
+ obj = BigIpSelfIp(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_ssl_certificate.py b/lib/ansible/modules/extras/network/f5/bigip_ssl_certificate.py
new file mode 100644
index 0000000000..076caba9f5
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_ssl_certificate.py
@@ -0,0 +1,516 @@
+#!/usr/bin/python
+#
+# (c) 2016, Kevin Coming (@waffie1)
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+module: bigip_ssl_certificate
+short_description: Import/Delete certificates from BIG-IP
+description:
+ - This module will import/delete SSL certificates on BIG-IP LTM.
+ Certificates can be imported from certificate and key files on the local
+ disk, in PEM format.
+version_added: 2.2
+options:
+ cert_content:
+ description:
+ - When used instead of 'cert_src', sets the contents of a certificate directly
+ to the specified value. This is used with lookup plugins or for anything
+ with formatting or templating. Either one of C(key_src),
+ C(key_content), C(cert_src) or C(cert_content) must be provided when
+ C(state) is C(present).
+ required: false
+ key_content:
+ description:
+ - When used instead of 'key_src', sets the contents of a certificate key
+ directly to the specified value. This is used with lookup plugins or for
+ anything with formatting or templating. Either one of C(key_src),
+ C(key_content), C(cert_src) or C(cert_content) must be provided when
+ C(state) is C(present).
+ required: false
+ state:
+ description:
+ - Certificate and key state. This determines if the provided certificate
+ and key is to be made C(present) on the device or C(absent).
+ required: true
+ default: present
+ choices:
+ - present
+ - absent
+ partition:
+ description:
+ - BIG-IP partition to use when adding/deleting certificate.
+ required: false
+ default: Common
+ name:
+ description:
+ - SSL Certificate Name. This is the cert/key pair name used
+ when importing a certificate/key into the F5. It also
+ determines the filenames of the objects on the LTM
+ (:Partition:name.cer_11111_1 and :Partition_name.key_11111_1).
+ required: true
+ cert_src:
+ description:
+ - This is the local filename of the certificate. Either one of C(key_src),
+ C(key_content), C(cert_src) or C(cert_content) must be provided when
+ C(state) is C(present).
+ required: false
+ key_src:
+ description:
+ - This is the local filename of the private key. Either one of C(key_src),
+ C(key_content), C(cert_src) or C(cert_content) must be provided when
+ C(state) is C(present).
+ required: false
+ passphrase:
+ description:
+ - Passphrase on certificate private key
+ required: false
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as pip
+ install f5-sdk.
+ - Requires the netaddr Python package on the host.
+ - If you use this module, you will not be able to remove the certificates
+ and keys that are managed, via the web UI. You can only remove them via
+ tmsh or these modules.
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk >= 1.3.1
+ - BigIP >= v12
+author:
+ - Kevin Coming (@waffie1)
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Import PEM Certificate from local disk
+ bigip_ssl_certificate:
+ name: "certificate-name"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ cert_src: "/path/to/cert.crt"
+ key_src: "/path/to/key.key"
+ delegate_to: localhost
+
+- name: Use a file lookup to import PEM Certificate
+ bigip_ssl_certificate:
+ name: "certificate-name"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "present"
+ cert_content: "{{ lookup('file', '/path/to/cert.crt') }}"
+ key_content: "{{ lookup('file', '/path/to/key.key') }}"
+ delegate_to: localhost
+
+- name: "Delete Certificate"
+ bigip_ssl_certificate:
+ name: "certificate-name"
+ server: "lb.mydomain.com"
+ user: "admin"
+ password: "secret"
+ state: "absent"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+cert_name:
+ description: >
+ The name of the SSL certificate. The C(cert_name) and
+ C(key_name) will be equal to each other.
+ returned:
+ - created
+ - changed
+ - deleted
+ type: string
+ sample: "cert1"
+key_name:
+ description: >
+ The name of the SSL certificate key. The C(key_name) and
+ C(cert_name) will be equal to each other.
+ returned:
+ - created
+ - changed
+ - deleted
+ type: string
+ sample: "key1"
+partition:
+ description: Partition in which the cert/key was created
+ returned:
+ - changed
+ - created
+ - deleted
+ type: string
+ sample: "Common"
+key_checksum:
+ description: SHA1 checksum of the key that was provided
+ return:
+ - changed
+ - created
+ type: string
+ sample: "cf23df2207d99a74fbe169e3eba035e633b65d94"
+cert_checksum:
+ description: SHA1 checksum of the cert that was provided
+ return:
+ - changed
+ - created
+ type: string
+ sample: "f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0"
+'''
+
+
+try:
+ from f5.bigip.contexts import TransactionContextManager
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+
+import hashlib
+import StringIO
+
+
+class BigIpSslCertificate(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ required_args = ['key_content', 'key_src', 'cert_content', 'cert_src']
+
+ ksource = kwargs['key_src']
+ if ksource:
+ with open(ksource) as f:
+ kwargs['key_content'] = f.read()
+
+ csource = kwargs['cert_src']
+ if csource:
+ with open(csource) as f:
+ kwargs['cert_content'] = f.read()
+
+ if kwargs['state'] == 'present':
+ if not any(kwargs[k] is not None for k in required_args):
+ raise F5ModuleError(
+ "Either 'key_content', 'key_src', 'cert_content' or "
+ "'cert_src' must be provided"
+ )
+
+ # This is the remote BIG-IP path from where it will look for certs
+ # to install.
+ self.dlpath = '/var/config/rest/downloads'
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def exists(self):
+ cert = self.cert_exists()
+ key = self.key_exists()
+
+ if cert and key:
+ return True
+ else:
+ return False
+
+ def get_hash(self, content):
+ k = hashlib.sha1()
+ s = StringIO.StringIO(content)
+ while True:
+ data = s.read(1024)
+ if not data:
+ break
+ k.update(data)
+ return k.hexdigest()
+
+ def present(self):
+ current = self.read()
+ changed = False
+ do_key = False
+ do_cert = False
+ chash = None
+ khash = None
+
+ check_mode = self.params['check_mode']
+ name = self.params['name']
+ partition = self.params['partition']
+ cert_content = self.params['cert_content']
+ key_content = self.params['key_content']
+ passphrase = self.params['passphrase']
+
+ # Technically you dont need to provide us with anything in the form
+ # of content for your cert, but that's kind of illogical, so we just
+ # return saying you didn't "do" anything if you left the cert and keys
+ # empty.
+ if not cert_content and not key_content:
+ return False
+
+ if key_content is not None:
+ if 'key_checksum' in current:
+ khash = self.get_hash(key_content)
+ if khash not in current['key_checksum']:
+ do_key = "update"
+ else:
+ do_key = "create"
+
+ if cert_content is not None:
+ if 'cert_checksum' in current:
+ chash = self.get_hash(cert_content)
+ if chash not in current['cert_checksum']:
+ do_cert = "update"
+ else:
+ do_cert = "create"
+
+ if do_cert or do_key:
+ changed = True
+ params = dict()
+ params['cert_name'] = name
+ params['key_name'] = name
+ params['partition'] = partition
+ if khash:
+ params['key_checksum'] = khash
+ if chash:
+ params['cert_checksum'] = chash
+ self.cparams = params
+
+ if check_mode:
+ return changed
+
+ if not do_cert and not do_key:
+ return False
+
+ tx = self.api.tm.transactions.transaction
+ with TransactionContextManager(tx) as api:
+ if do_cert:
+ # Upload the content of a certificate as a StringIO object
+ cstring = StringIO.StringIO(cert_content)
+ filename = "%s.crt" % (name)
+ filepath = os.path.join(self.dlpath, filename)
+ api.shared.file_transfer.uploads.upload_stringio(
+ cstring,
+ filename
+ )
+
+ if do_cert == "update":
+ # Install the certificate
+ params = {
+ 'name': name,
+ 'partition': partition
+ }
+ cert = api.tm.sys.file.ssl_certs.ssl_cert.load(**params)
+
+ # This works because, while the source path is the same,
+ # calling update causes the file to be re-read
+ cert.update()
+ changed = True
+ elif do_cert == "create":
+ # Install the certificate
+ params = {
+ 'sourcePath': "file://" + filepath,
+ 'name': name,
+ 'partition': partition
+ }
+ api.tm.sys.file.ssl_certs.ssl_cert.create(**params)
+ changed = True
+
+ if do_key:
+ # Upload the content of a certificate key as a StringIO object
+ kstring = StringIO.StringIO(key_content)
+ filename = "%s.key" % (name)
+ filepath = os.path.join(self.dlpath, filename)
+ api.shared.file_transfer.uploads.upload_stringio(
+ kstring,
+ filename
+ )
+
+ if do_key == "update":
+ # Install the key
+ params = {
+ 'name': name,
+ 'partition': partition
+ }
+ key = api.tm.sys.file.ssl_keys.ssl_key.load(**params)
+
+ params = dict()
+
+ if passphrase:
+ params['passphrase'] = passphrase
+ else:
+ params['passphrase'] = None
+
+ key.update(**params)
+ changed = True
+ elif do_key == "create":
+ # Install the key
+ params = {
+ 'sourcePath': "file://" + filepath,
+ 'name': name,
+ 'partition': partition
+ }
+ if passphrase:
+ params['passphrase'] = self.params['passphrase']
+ else:
+ params['passphrase'] = None
+
+ api.tm.sys.file.ssl_keys.ssl_key.create(**params)
+ changed = True
+ return changed
+
+ def key_exists(self):
+ return self.api.tm.sys.file.ssl_keys.ssl_key.exists(
+ name=self.params['name'],
+ partition=self.params['partition']
+ )
+
+ def cert_exists(self):
+ return self.api.tm.sys.file.ssl_certs.ssl_cert.exists(
+ name=self.params['name'],
+ partition=self.params['partition']
+ )
+
+ def read(self):
+ p = dict()
+ name = self.params['name']
+ partition = self.params['partition']
+
+ if self.key_exists():
+ key = self.api.tm.sys.file.ssl_keys.ssl_key.load(
+ name=name,
+ partition=partition
+ )
+ if hasattr(key, 'checksum'):
+ p['key_checksum'] = str(key.checksum)
+
+ if self.cert_exists():
+ cert = self.api.tm.sys.file.ssl_certs.ssl_cert.load(
+ name=name,
+ partition=partition
+ )
+ if hasattr(cert, 'checksum'):
+ p['cert_checksum'] = str(cert.checksum)
+
+ p['name'] = name
+ return p
+
+ def flush(self):
+ result = dict()
+ state = self.params['state']
+
+ try:
+ if state == "present":
+ changed = self.present()
+ elif state == "absent":
+ changed = self.absent()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+ def absent(self):
+ changed = False
+
+ if self.exists():
+ changed = self.delete()
+
+ return changed
+
+ def delete(self):
+ changed = False
+
+ check_mode = self.params['check_mode']
+
+ delete_cert = self.cert_exists()
+ delete_key = self.key_exists()
+
+ if not delete_cert and not delete_key:
+ return changed
+
+ if check_mode:
+ params = dict()
+ params['cert_name'] = name
+ params['key_name'] = name
+ params['partition'] = partition
+ self.cparams = params
+ return True
+
+ tx = self.api.tm.transactions.transaction
+ with TransactionContextManager(tx) as api:
+ if delete_cert:
+ # Delete the certificate
+ c = api.tm.sys.file.ssl_certs.ssl_cert.load(
+ name=self.params['name'],
+ partition=self.params['partition']
+ )
+ c.delete()
+ changed = True
+
+ if delete_key:
+ # Delete the certificate key
+ k = self.api.tm.sys.file.ssl_keys.ssl_key.load(
+ name=self.params['name'],
+ partition=self.params['partition']
+ )
+ k.delete()
+ changed = True
+ return changed
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ name=dict(type='str', required=True),
+ cert_content=dict(type='str', default=None),
+ cert_src=dict(type='path', default=None),
+ key_content=dict(type='str', default=None),
+ key_src=dict(type='path', default=None),
+ passphrase=dict(type='str', default=None, no_log=True)
+ )
+
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ mutually_exclusive=[
+ ['key_content', 'key_src'],
+ ['cert_content', 'cert_src']
+ ]
+ )
+
+ try:
+ obj = BigIpSslCertificate(check_mode=module.check_mode,
+ **module.params)
+ result = obj.flush()
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_sys_db.py b/lib/ansible/modules/extras/network/f5/bigip_sys_db.py
new file mode 100644
index 0000000000..54f5dd74fc
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_sys_db.py
@@ -0,0 +1,221 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_sys_db
+short_description: Manage BIG-IP system database variables
+description:
+ - Manage BIG-IP system database variables
+version_added: "2.2"
+options:
+ key:
+ description:
+ - The database variable to manipulate.
+ required: true
+ state:
+ description:
+ - The state of the variable on the system. When C(present), guarantees
+ that an existing variable is set to C(value). When C(reset) sets the
+ variable back to the default value. At least one of value and state
+ C(reset) are required.
+ required: false
+ default: present
+ choices:
+ - present
+ - reset
+ value:
+ description:
+ - The value to set the key to. At least one of value and state C(reset)
+ are required.
+ required: false
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as pip
+ install f5-sdk.
+ - Requires BIG-IP version 12.0.0 or greater
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Set the boot.quiet DB variable on the BIG-IP
+ bigip_sys_db:
+ user: "admin"
+ password: "secret"
+ server: "lb.mydomain.com"
+ key: "boot.quiet"
+ value: "disable"
+ delegate_to: localhost
+
+- name: Disable the initial setup screen
+ bigip_sys_db:
+ user: "admin"
+ password: "secret"
+ server: "lb.mydomain.com"
+ key: "setup.run"
+ value: "false"
+ delegate_to: localhost
+
+- name: Reset the initial setup screen
+ bigip_sys_db:
+ user: "admin"
+ password: "secret"
+ server: "lb.mydomain.com"
+ key: "setup.run"
+ state: "reset"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+name:
+ description: The key in the system database that was specified
+ returned: changed and success
+ type: string
+ sample: "setup.run"
+default_value:
+ description: The default value of the key
+ returned: changed and success
+ type: string
+ sample: "true"
+value:
+ description: The value that you set the key to
+ returned: changed and success
+ type: string
+ sample: "false"
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+
+class BigIpSysDb(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def flush(self):
+ result = dict()
+ state = self.params['state']
+ value = self.params['value']
+
+ if not state == 'reset' and not value:
+ raise F5ModuleError(
+ "When setting a key, a value must be supplied"
+ )
+
+ current = self.read()
+
+ if self.params['check_mode']:
+ if value == current:
+ changed = False
+ else:
+ changed = True
+ else:
+ if state == "present":
+ changed = self.present()
+ elif state == "reset":
+ changed = self.reset()
+ current = self.read()
+ result.update(
+ name=current.name,
+ default_value=current.defaultValue,
+ value=current.value
+ )
+
+ result.update(dict(changed=changed))
+ return result
+
+ def read(self):
+ dbs = self.api.tm.sys.dbs.db.load(
+ name=self.params['key']
+ )
+ return dbs
+
+ def present(self):
+ current = self.read()
+
+ if current.value == self.params['value']:
+ return False
+
+ current.update(value=self.params['value'])
+ current.refresh()
+
+ if current.value != self.params['value']:
+ raise F5ModuleError(
+ "Failed to set the DB variable"
+ )
+ return True
+
+ def reset(self):
+ current = self.read()
+
+ default = current.defaultValue
+ if current.value == default:
+ return False
+
+ current.update(value=default)
+ current.refresh()
+
+ if current.value != current.defaultValue:
+ raise F5ModuleError(
+ "Failed to reset the DB variable"
+ )
+
+ return True
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ key=dict(required=True),
+ state=dict(default='present', choices=['present', 'reset']),
+ value=dict(required=False, default=None)
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ try:
+ obj = BigIpSysDb(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py b/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py
new file mode 100644
index 0000000000..89d25103f6
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_virtual_server.py
@@ -0,0 +1,614 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2015, Etienne Carriere <etienne.carriere@gmail.com>
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_virtual_server
+short_description: "Manages F5 BIG-IP LTM virtual servers"
+description:
+ - "Manages F5 BIG-IP LTM virtual servers via iControl SOAP API"
+version_added: "2.1"
+author:
+ - Etienne Carriere (@Etienne-Carriere)
+ - Tim Rupp (@caphrim007)
+notes:
+ - "Requires BIG-IP software version >= 11"
+ - "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
+ - "Best run as a local_action in your playbook"
+requirements:
+ - bigsuds
+options:
+ state:
+ description:
+ - Virtual Server state
+ - Absent, delete the VS if present
+ - C(present) (and its synonym enabled), create if needed the VS and set
+ state to enabled
+ - C(disabled), create if needed the VS and set state to disabled
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ - enabled
+ - disabled
+ aliases: []
+ partition:
+ description:
+ - Partition
+ required: false
+ default: 'Common'
+ name:
+ description:
+ - Virtual server name
+ required: true
+ aliases:
+ - vs
+ destination:
+ description:
+ - Destination IP of the virtual server (only host is currently supported).
+ Required when state=present and vs does not exist.
+ required: true
+ aliases:
+ - address
+ - ip
+ port:
+ description:
+ - Port of the virtual server . Required when state=present and vs does not exist
+ required: false
+ default: None
+ all_profiles:
+ description:
+ - List of all Profiles (HTTP,ClientSSL,ServerSSL,etc) that must be used
+ by the virtual server
+ required: false
+ default: None
+ all_rules:
+ version_added: "2.2"
+ description:
+ - List of rules to be applied in priority order
+ required: false
+ default: None
+ all_enabled_vlans:
+ version_added: "2.2"
+ description:
+ - List of vlans to be enabled
+ required: false
+ default: None
+ pool:
+ description:
+ - Default pool for the virtual server
+ required: false
+ default: None
+ snat:
+ description:
+ - Source network address policy
+ required: false
+ default: None
+ default_persistence_profile:
+ description:
+ - Default Profile which manages the session persistence
+ required: false
+ default: None
+ description:
+ description:
+ - Virtual server description
+ required: false
+ default: None
+extends_documentation_fragment: f5
+'''
+
+EXAMPLES = '''
+- name: Add virtual server
+ bigip_virtual_server:
+ server: lb.mydomain.net
+ user: admin
+ password: secret
+ state: present
+ partition: MyPartition
+ name: myvirtualserver
+ destination: "{{ ansible_default_ipv4['address'] }}"
+ port: 443
+ pool: "{{ mypool }}"
+ snat: Automap
+ description: Test Virtual Server
+ all_profiles:
+ - http
+ - clientssl
+ all_enabled_vlans:
+ - /Common/vlan2
+ delegate_to: localhost
+
+- name: Modify Port of the Virtual Server
+ bigip_virtual_server:
+ server: lb.mydomain.net
+ user: admin
+ password: secret
+ state: present
+ partition: MyPartition
+ name: myvirtualserver
+ port: 8080
+ delegate_to: localhost
+
+- name: Delete virtual server
+ bigip_virtual_server:
+ server: lb.mydomain.net
+ user: admin
+ password: secret
+ state: absent
+ partition: MyPartition
+ name: myvirtualserver
+ delegate_to: localhost
+'''
+
+RETURN = '''
+---
+deleted:
+ description: Name of a virtual server that was deleted
+ returned: changed
+ type: string
+ sample: "my-virtual-server"
+'''
+
+
+# map of state values
+STATES = {
+ 'enabled': 'STATE_ENABLED',
+ 'disabled': 'STATE_DISABLED'
+}
+
+STATUSES = {
+ 'enabled': 'SESSION_STATUS_ENABLED',
+ 'disabled': 'SESSION_STATUS_DISABLED',
+ 'offline': 'SESSION_STATUS_FORCED_DISABLED'
+}
+
+
+def vs_exists(api, vs):
+ # hack to determine if pool exists
+ result = False
+ try:
+ api.LocalLB.VirtualServer.get_object_status(virtual_servers=[vs])
+ result = True
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result = False
+ else:
+ # genuine exception
+ raise
+ return result
+
+
+def vs_create(api, name, destination, port, pool):
+ _profiles = [[{'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': 'tcp'}]]
+ created = False
+ # a bit of a hack to handle concurrent runs of this module.
+ # even though we've checked the vs doesn't exist,
+ # it may exist by the time we run create_vs().
+ # this catches the exception and does something smart
+ # about it!
+ try:
+ api.LocalLB.VirtualServer.create(
+ definitions=[{'name': [name], 'address': [destination], 'port': port, 'protocol': 'PROTOCOL_TCP'}],
+ wildmasks=['255.255.255.255'],
+ resources=[{'type': 'RESOURCE_TYPE_POOL', 'default_pool_name': pool}],
+ profiles=_profiles)
+ created = True
+ return created
+ except bigsuds.OperationFailed as e:
+ if "already exists" not in str(e):
+ raise Exception('Error on creating Virtual Server : %s' % e)
+
+
+def vs_remove(api, name):
+ api.LocalLB.VirtualServer.delete_virtual_server(
+ virtual_servers=[name]
+ )
+
+
+def get_rules(api, name):
+ return api.LocalLB.VirtualServer.get_rule(
+ virtual_servers=[name]
+ )[0]
+
+
+def set_rules(api, name, rules_list):
+ updated = False
+ if rules_list is None:
+ return False
+ rules_list = list(enumerate(rules_list))
+ try:
+ current_rules = map(lambda x: (x['priority'], x['rule_name']), get_rules(api, name))
+ to_add_rules = []
+ for i, x in rules_list:
+ if (i, x) not in current_rules:
+ to_add_rules.append({'priority': i, 'rule_name': x})
+ to_del_rules = []
+ for i, x in current_rules:
+ if (i, x) not in rules_list:
+ to_del_rules.append({'priority': i, 'rule_name': x})
+ if len(to_del_rules) > 0:
+ api.LocalLB.VirtualServer.remove_rule(
+ virtual_servers=[name],
+ rules=[to_del_rules]
+ )
+ updated = True
+ if len(to_add_rules) > 0:
+ api.LocalLB.VirtualServer.add_rule(
+ virtual_servers=[name],
+ rules=[to_add_rules]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting rules : %s' % e)
+
+
+def get_profiles(api, name):
+ return api.LocalLB.VirtualServer.get_profile(
+ virtual_servers=[name]
+ )[0]
+
+
+def set_profiles(api, name, profiles_list):
+ updated = False
+ try:
+ if profiles_list is None:
+ return False
+ current_profiles = map(lambda x: x['profile_name'], get_profiles(api, name))
+ to_add_profiles = []
+ for x in profiles_list:
+ if x not in current_profiles:
+ to_add_profiles.append({'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': x})
+ to_del_profiles = []
+ for x in current_profiles:
+ if (x not in profiles_list) and (x != "/Common/tcp"):
+ to_del_profiles.append({'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': x})
+ if len(to_del_profiles) > 0:
+ api.LocalLB.VirtualServer.remove_profile(
+ virtual_servers=[name],
+ profiles=[to_del_profiles]
+ )
+ updated = True
+ if len(to_add_profiles) > 0:
+ api.LocalLB.VirtualServer.add_profile(
+ virtual_servers=[name],
+ profiles=[to_add_profiles]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting profiles : %s' % e)
+
+def set_enabled_vlans(api, name, vlans_enabled_list):
+ updated = False
+ try:
+ if vlans_enabled_list is None:
+ return False
+
+ to_add_vlans = []
+ for x in vlans_enabled_list:
+ to_add_vlans.append(x)
+
+ api.LocalLB.VirtualServer.set_vlan(
+ virtual_servers=[name],
+ vlans = [{ 'state':'STATE_ENABLED', 'vlans':[to_add_vlans] }]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting enabled vlans : %s' % e)
+
+def set_snat(api, name, snat):
+ updated = False
+ try:
+ current_state = get_snat_type(api, name)
+ if snat is None:
+ return updated
+ elif snat == 'None' and current_state != 'SRC_TRANS_NONE':
+ api.LocalLB.VirtualServer.set_source_address_translation_none(
+ virtual_servers=[name]
+ )
+ updated = True
+ elif snat == 'Automap' and current_state != 'SRC_TRANS_AUTOMAP':
+ api.LocalLB.VirtualServer.set_source_address_translation_automap(
+ virtual_servers=[name]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting snat : %s' % e)
+
+
+def get_snat_type(api, name):
+ return api.LocalLB.VirtualServer.get_source_address_translation_type(
+ virtual_servers=[name]
+ )[0]
+
+
+def get_pool(api, name):
+ return api.LocalLB.VirtualServer.get_default_pool_name(
+ virtual_servers=[name]
+ )[0]
+
+
+def set_pool(api, name, pool):
+ updated = False
+ try:
+ current_pool = get_pool(api, name)
+ if pool is not None and (pool != current_pool):
+ api.LocalLB.VirtualServer.set_default_pool_name(
+ virtual_servers=[name],
+ default_pools=[pool]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting pool : %s' % e)
+
+
+def get_destination(api, name):
+ return api.LocalLB.VirtualServer.get_destination_v2(
+ virtual_servers=[name]
+ )[0]
+
+
+def set_destination(api, name, destination):
+ updated = False
+ try:
+ current_destination = get_destination(api, name)
+ if destination is not None and destination != current_destination['address']:
+ api.LocalLB.VirtualServer.set_destination_v2(
+ virtual_servers=[name],
+ destinations=[{'address': destination, 'port': current_destination['port']}]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting destination : %s' % e)
+
+
+def set_port(api, name, port):
+ updated = False
+ try:
+ current_destination = get_destination(api, name)
+ if port is not None and port != current_destination['port']:
+ api.LocalLB.VirtualServer.set_destination_v2(
+ virtual_servers=[name],
+ destinations=[{'address': current_destination['address'], 'port': port}]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting port : %s' % e)
+
+
+def get_state(api, name):
+ return api.LocalLB.VirtualServer.get_enabled_state(
+ virtual_servers=[name]
+ )[0]
+
+
+def set_state(api, name, state):
+ updated = False
+ try:
+ current_state = get_state(api, name)
+ # We consider that being present is equivalent to enabled
+ if state == 'present':
+ state = 'enabled'
+ if STATES[state] != current_state:
+ api.LocalLB.VirtualServer.set_enabled_state(
+ virtual_servers=[name],
+ states=[STATES[state]]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting state : %s' % e)
+
+
+def get_description(api, name):
+ return api.LocalLB.VirtualServer.get_description(
+ virtual_servers=[name]
+ )[0]
+
+
+def set_description(api, name, description):
+ updated = False
+ try:
+ current_description = get_description(api, name)
+ if description is not None and current_description != description:
+ api.LocalLB.VirtualServer.set_description(
+ virtual_servers=[name],
+ descriptions=[description]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting description : %s ' % e)
+
+
+def get_persistence_profiles(api, name):
+ return api.LocalLB.VirtualServer.get_persistence_profile(
+ virtual_servers=[name]
+ )[0]
+
+
+def set_default_persistence_profiles(api, name, persistence_profile):
+ updated = False
+ if persistence_profile is None:
+ return updated
+ try:
+ current_persistence_profiles = get_persistence_profiles(api, name)
+ default = None
+ for profile in current_persistence_profiles:
+ if profile['default_profile']:
+ default = profile['profile_name']
+ break
+ if default is not None and default != persistence_profile:
+ api.LocalLB.VirtualServer.remove_persistence_profile(
+ virtual_servers=[name],
+ profiles=[[{'profile_name': default, 'default_profile': True}]]
+ )
+ if default != persistence_profile:
+ api.LocalLB.VirtualServer.add_persistence_profile(
+ virtual_servers=[name],
+ profiles=[[{'profile_name': persistence_profile, 'default_profile': True}]]
+ )
+ updated = True
+ return updated
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on setting default persistence profile : %s' % e)
+
+
+def main():
+ argument_spec = f5_argument_spec()
+ argument_spec.update(dict(
+ state=dict(type='str', default='present',
+ choices=['present', 'absent', 'disabled', 'enabled']),
+ name=dict(type='str', required=True, aliases=['vs']),
+ destination=dict(type='str', aliases=['address', 'ip']),
+ port=dict(type='int'),
+ all_profiles=dict(type='list'),
+ all_rules=dict(type='list'),
+ all_enabled_vlans=dict(type='list'),
+ pool=dict(type='str'),
+ description=dict(type='str'),
+ snat=dict(type='str'),
+ default_persistence_profile=dict(type='str')
+ ))
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True
+ )
+
+ if not bigsuds_found:
+ module.fail_json(msg="the python bigsuds module is required")
+
+ if module.params['validate_certs']:
+ import ssl
+ if not hasattr(ssl, 'SSLContext'):
+ module.fail_json(msg='bigsuds does not support verifying certificates with python < 2.7.9. Either update python or set validate_certs=False on the task')
+
+ server = module.params['server']
+ server_port = module.params['server_port']
+ user = module.params['user']
+ password = module.params['password']
+ state = module.params['state']
+ partition = module.params['partition']
+ validate_certs = module.params['validate_certs']
+
+ name = fq_name(partition, module.params['name'])
+ destination = module.params['destination']
+ port = module.params['port']
+ all_profiles = fq_list_names(partition, module.params['all_profiles'])
+ all_rules = fq_list_names(partition, module.params['all_rules'])
+ all_enabled_vlans = fq_list_names(partition, module.params['all_enabled_vlans'])
+ pool = fq_name(partition, module.params['pool'])
+ description = module.params['description']
+ snat = module.params['snat']
+ default_persistence_profile = fq_name(partition, module.params['default_persistence_profile'])
+
+ if 1 > port > 65535:
+ module.fail_json(msg="valid ports must be in range 1 - 65535")
+
+ try:
+ api = bigip_api(server, user, password, validate_certs, port=server_port)
+ result = {'changed': False} # default
+
+ if state == 'absent':
+ if not module.check_mode:
+ if vs_exists(api, name):
+ # hack to handle concurrent runs of module
+ # pool might be gone before we actually remove
+ try:
+ vs_remove(api, name)
+ result = {'changed': True, 'deleted': name}
+ except bigsuds.OperationFailed as e:
+ if "was not found" in str(e):
+ result['changed'] = False
+ else:
+ raise
+ else:
+ # check-mode return value
+ result = {'changed': True}
+
+ else:
+ update = False
+ if not vs_exists(api, name):
+ if (not destination) or (not port):
+ module.fail_json(msg="both destination and port must be supplied to create a VS")
+ if not module.check_mode:
+ # a bit of a hack to handle concurrent runs of this module.
+ # even though we've checked the virtual_server doesn't exist,
+ # it may exist by the time we run virtual_server().
+ # this catches the exception and does something smart
+ # about it!
+ try:
+ vs_create(api, name, destination, port, pool)
+ set_profiles(api, name, all_profiles)
+ set_enabled_vlans(api, name, all_enabled_vlans)
+ set_rules(api, name, all_rules)
+ set_snat(api, name, snat)
+ set_description(api, name, description)
+ set_default_persistence_profiles(api, name, default_persistence_profile)
+ set_state(api, name, state)
+ result = {'changed': True}
+ except bigsuds.OperationFailed as e:
+ raise Exception('Error on creating Virtual Server : %s' % e)
+ else:
+ # check-mode return value
+ result = {'changed': True}
+ else:
+ update = True
+ if update:
+ # VS exists
+ if not module.check_mode:
+ # Have a transaction for all the changes
+ try:
+ api.System.Session.start_transaction()
+ result['changed'] |= set_destination(api, name, fq_name(partition, destination))
+ result['changed'] |= set_port(api, name, port)
+ result['changed'] |= set_pool(api, name, pool)
+ result['changed'] |= set_description(api, name, description)
+ result['changed'] |= set_snat(api, name, snat)
+ result['changed'] |= set_profiles(api, name, all_profiles)
+ result['changed'] |= set_enabled_vlans(api, name, all_enabled_vlans)
+ result['changed'] |= set_rules(api, name, all_rules)
+ result['changed'] |= set_default_persistence_profiles(api, name, default_persistence_profile)
+ result['changed'] |= set_state(api, name, state)
+ api.System.Session.submit_transaction()
+ except Exception as e:
+ raise Exception("Error on updating Virtual Server : %s" % e)
+ else:
+ # check-mode return value
+ result = {'changed': True}
+
+ except Exception as e:
+ module.fail_json(msg="received exception: %s" % e)
+
+ module.exit_json(**result)
+# import module snippets
+from ansible.module_utils.basic import *
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/extras/network/f5/bigip_vlan.py b/lib/ansible/modules/extras/network/f5/bigip_vlan.py
new file mode 100644
index 0000000000..4e13d2508c
--- /dev/null
+++ b/lib/ansible/modules/extras/network/f5/bigip_vlan.py
@@ -0,0 +1,445 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: bigip_vlan
+short_description: Manage VLANs on a BIG-IP system
+description:
+ - Manage VLANs on a BIG-IP system
+version_added: "2.2"
+options:
+ description:
+ description:
+ - The description to give to the VLAN.
+ tagged_interfaces:
+ description:
+ - Specifies a list of tagged interfaces and trunks that you want to
+ configure for the VLAN. Use tagged interfaces or trunks when
+ you want to assign a single interface or trunk to multiple VLANs.
+ required: false
+ aliases:
+ - tagged_interface
+ untagged_interfaces:
+ description:
+ - Specifies a list of untagged interfaces and trunks that you want to
+ configure for the VLAN.
+ required: false
+ aliases:
+ - untagged_interface
+ name:
+ description:
+ - The VLAN to manage. If the special VLAN C(ALL) is specified with
+ the C(state) value of C(absent) then all VLANs will be removed.
+ required: true
+ state:
+ description:
+ - The state of the VLAN on the system. When C(present), guarantees
+ that the VLAN exists with the provided attributes. When C(absent),
+ removes the VLAN from the system.
+ required: false
+ default: present
+ choices:
+ - absent
+ - present
+ tag:
+ description:
+ - Tag number for the VLAN. The tag number can be any integer between 1
+ and 4094. The system automatically assigns a tag number if you do not
+ specify a value.
+notes:
+ - Requires the f5-sdk Python package on the host. This is as easy as pip
+ install f5-sdk.
+ - Requires BIG-IP versions >= 12.0.0
+extends_documentation_fragment: f5
+requirements:
+ - f5-sdk
+author:
+ - Tim Rupp (@caphrim007)
+'''
+
+EXAMPLES = '''
+- name: Create VLAN
+ bigip_vlan:
+ name: "net1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+
+- name: Set VLAN tag
+ bigip_vlan:
+ name: "net1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ tag: "2345"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+
+- name: Add VLAN 2345 as tagged to interface 1.1
+ bigip_vlan:
+ tagged_interface: 1.1
+ name: "net1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ tag: "2345"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+
+- name: Add VLAN 1234 as tagged to interfaces 1.1 and 1.2
+ bigip_vlan:
+ tagged_interfaces:
+ - 1.1
+ - 1.2
+ name: "net1"
+ password: "secret"
+ server: "lb.mydomain.com"
+ tag: "1234"
+ user: "admin"
+ validate_certs: "no"
+ delegate_to: localhost
+'''
+
+RETURN = '''
+description:
+ description: The description set on the VLAN
+ returned: changed
+ type: string
+ sample: foo VLAN
+interfaces:
+ description: Interfaces that the VLAN is assigned to
+ returned: changed
+ type: list
+ sample: ['1.1','1.2']
+name:
+ description: The name of the VLAN
+ returned: changed
+ type: string
+ sample: net1
+partition:
+ description: The partition that the VLAN was created on
+ returned: changed
+ type: string
+ sample: Common
+tag:
+ description: The ID of the VLAN
+ returned: changed
+ type: int
+ sample: 2345
+'''
+
+try:
+ from f5.bigip import ManagementRoot
+ from icontrol.session import iControlUnexpectedHTTPError
+ HAS_F5SDK = True
+except ImportError:
+ HAS_F5SDK = False
+
+
+class BigIpVlan(object):
+ def __init__(self, *args, **kwargs):
+ if not HAS_F5SDK:
+ raise F5ModuleError("The python f5-sdk module is required")
+
+ # The params that change in the module
+ self.cparams = dict()
+
+ # Stores the params that are sent to the module
+ self.params = kwargs
+ self.api = ManagementRoot(kwargs['server'],
+ kwargs['user'],
+ kwargs['password'],
+ port=kwargs['server_port'])
+
+ def present(self):
+ if self.exists():
+ return self.update()
+ else:
+ return self.create()
+
+ def absent(self):
+ changed = False
+
+ if self.exists():
+ changed = self.delete()
+
+ return changed
+
+ def read(self):
+ """Read information and transform it
+
+ The values that are returned by BIG-IP in the f5-sdk can have encoding
+ attached to them as well as be completely missing in some cases.
+
+ Therefore, this method will transform the data from the BIG-IP into a
+ format that is more easily consumable by the rest of the class and the
+ parameters that are supported by the module.
+ """
+ p = dict()
+ name = self.params['name']
+ partition = self.params['partition']
+ r = self.api.tm.net.vlans.vlan.load(
+ name=name,
+ partition=partition
+ )
+ ifcs = r.interfaces_s.get_collection()
+ if hasattr(r, 'tag'):
+ p['tag'] = int(r.tag)
+ if hasattr(r, 'description'):
+ p['description'] = str(r.description)
+ if len(ifcs) is not 0:
+ untagged = []
+ tagged = []
+ for x in ifcs:
+ if hasattr(x, 'tagged'):
+ tagged.append(str(x.name))
+ elif hasattr(x, 'untagged'):
+ untagged.append(str(x.name))
+ if untagged:
+ p['untagged_interfaces'] = list(set(untagged))
+ if tagged:
+ p['tagged_interfaces'] = list(set(tagged))
+ p['name'] = name
+ return p
+
+ def create(self):
+ params = dict()
+
+ check_mode = self.params['check_mode']
+ description = self.params['description']
+ name = self.params['name']
+ untagged_interfaces = self.params['untagged_interfaces']
+ tagged_interfaces = self.params['tagged_interfaces']
+ partition = self.params['partition']
+ tag = self.params['tag']
+
+ if tag is not None:
+ params['tag'] = tag
+
+ if untagged_interfaces is not None or tagged_interfaces is not None:
+ tmp = []
+ ifcs = self.api.tm.net.interfaces.get_collection()
+ ifcs = [str(x.name) for x in ifcs]
+
+ if len(ifcs) is 0:
+ raise F5ModuleError(
+ 'No interfaces were found'
+ )
+
+ pinterfaces = []
+ if untagged_interfaces:
+ interfaces = untagged_interfaces
+ elif tagged_interfaces:
+ interfaces = tagged_interfaces
+
+ for ifc in interfaces:
+ ifc = str(ifc)
+ if ifc in ifcs:
+ pinterfaces.append(ifc)
+
+ if tagged_interfaces:
+ tmp = [dict(name=x, tagged=True) for x in pinterfaces]
+ elif untagged_interfaces:
+ tmp = [dict(name=x, untagged=True) for x in pinterfaces]
+
+ if tmp:
+ params['interfaces'] = tmp
+
+ if description is not None:
+ params['description'] = self.params['description']
+
+ params['name'] = name
+ params['partition'] = partition
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return True
+
+ d = self.api.tm.net.vlans.vlan
+ d.create(**params)
+
+ if self.exists():
+ return True
+ else:
+ raise F5ModuleError("Failed to create the VLAN")
+
+ def update(self):
+ changed = False
+ params = dict()
+ current = self.read()
+
+ check_mode = self.params['check_mode']
+ description = self.params['description']
+ name = self.params['name']
+ tag = self.params['tag']
+ partition = self.params['partition']
+ tagged_interfaces = self.params['tagged_interfaces']
+ untagged_interfaces = self.params['untagged_interfaces']
+
+ if untagged_interfaces is not None or tagged_interfaces is not None:
+ ifcs = self.api.tm.net.interfaces.get_collection()
+ ifcs = [str(x.name) for x in ifcs]
+
+ if len(ifcs) is 0:
+ raise F5ModuleError(
+ 'No interfaces were found'
+ )
+
+ pinterfaces = []
+ if untagged_interfaces:
+ interfaces = untagged_interfaces
+ elif tagged_interfaces:
+ interfaces = tagged_interfaces
+
+ for ifc in interfaces:
+ ifc = str(ifc)
+ if ifc in ifcs:
+ pinterfaces.append(ifc)
+ else:
+ raise F5ModuleError(
+ 'The specified interface "%s" was not found' % (ifc)
+ )
+
+ if tagged_interfaces:
+ tmp = [dict(name=x, tagged=True) for x in pinterfaces]
+ if 'tagged_interfaces' in current:
+ if pinterfaces != current['tagged_interfaces']:
+ params['interfaces'] = tmp
+ else:
+ params['interfaces'] = tmp
+ elif untagged_interfaces:
+ tmp = [dict(name=x, untagged=True) for x in pinterfaces]
+ if 'untagged_interfaces' in current:
+ if pinterfaces != current['untagged_interfaces']:
+ params['interfaces'] = tmp
+ else:
+ params['interfaces'] = tmp
+
+ if description is not None:
+ if 'description' in current:
+ if description != current['description']:
+ params['description'] = description
+ else:
+ params['description'] = description
+
+ if tag is not None:
+ if 'tag' in current:
+ if tag != current['tag']:
+ params['tag'] = tag
+ else:
+ params['tag'] = tag
+
+ if params:
+ changed = True
+ params['name'] = name
+ params['partition'] = partition
+ if check_mode:
+ return changed
+ self.cparams = camel_dict_to_snake_dict(params)
+ else:
+ return changed
+
+ r = self.api.tm.net.vlans.vlan.load(
+ name=name,
+ partition=partition
+ )
+ r.update(**params)
+ r.refresh()
+
+ return True
+
+ def delete(self):
+ params = dict()
+ check_mode = self.params['check_mode']
+
+ params['name'] = self.params['name']
+ params['partition'] = self.params['partition']
+
+ self.cparams = camel_dict_to_snake_dict(params)
+ if check_mode:
+ return True
+
+ dc = self.api.tm.net.vlans.vlan.load(**params)
+ dc.delete()
+
+ if self.exists():
+ raise F5ModuleError("Failed to delete the VLAN")
+ return True
+
+ def exists(self):
+ name = self.params['name']
+ partition = self.params['partition']
+ return self.api.tm.net.vlans.vlan.exists(
+ name=name,
+ partition=partition
+ )
+
+ def flush(self):
+ result = dict()
+ state = self.params['state']
+
+ try:
+ if state == "present":
+ changed = self.present()
+ elif state == "absent":
+ changed = self.absent()
+ except iControlUnexpectedHTTPError as e:
+ raise F5ModuleError(str(e))
+
+ result.update(**self.cparams)
+ result.update(dict(changed=changed))
+ return result
+
+
+def main():
+ argument_spec = f5_argument_spec()
+
+ meta_args = dict(
+ description=dict(required=False, default=None),
+ tagged_interfaces=dict(required=False, default=None, type='list', aliases=['tagged_interface']),
+ untagged_interfaces=dict(required=False, default=None, type='list', aliases=['untagged_interface']),
+ name=dict(required=True),
+ tag=dict(required=False, default=None, type='int')
+ )
+ argument_spec.update(meta_args)
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ mutually_exclusive=[
+ ['tagged_interfaces', 'untagged_interfaces']
+ ]
+ )
+
+ try:
+ obj = BigIpVlan(check_mode=module.check_mode, **module.params)
+ result = obj.flush()
+
+ module.exit_json(**result)
+ except F5ModuleError as e:
+ module.fail_json(msg=str(e))
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible.module_utils.f5 import *
+
+if __name__ == '__main__':
+ main()