Various small fixes for f5 ansible modules (#49492)

This commit is contained in:
Tim Rupp 2018-12-04 11:15:49 -08:00 committed by GitHub
parent 6200d32c0d
commit 138690519d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 148 additions and 184 deletions

View file

@ -1003,7 +1003,7 @@ class ArgumentSpec(object):
name=dict(
required=True,
),
file=dict(),
file=dict(type='path'),
template=dict(
choices=self.template_map
),

View file

@ -44,7 +44,9 @@ options:
country:
description:
- Specifies a country.
- Full continent names and their abbreviated versions are supported.
- In addition to the country full names, you may also specify their abbreviated
form, such as C(US) instead of C(United States).
- Valid country codes can be found here https://countrycode.org/.
state:
description:
- Specifies a state in a given country.

View file

@ -66,21 +66,23 @@ EXAMPLES = r'''
content: "{{ lookup('template', 'irule.tcl') }}"
module: ltm
name: MyiRule
password: secret
server: lb.mydomain.com
state: present
user: admin
provider:
user: admin
password: secret
server: lb.mydomain.com
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.tcl
state: present
user: admin
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
'''
@ -528,13 +530,9 @@ class ArgumentSpec(object):
def __init__(self):
self.supports_check_mode = True
argument_spec = dict(
content=dict(
required=False,
default=None
),
content=dict(),
src=dict(
required=False,
default=None
type='path',
),
name=dict(required=True),
module=dict(

View file

@ -64,10 +64,11 @@ EXAMPLES = r'''
description: Route to TACACS
gateway: 10.10.10.10
network: 11.11.11.0/24
password: secret
server: lb.mydomain.com
state: present
user: admin
provider:
user: admin
password: secret
server: lb.mydomain.com
delegate_to: localhost
'''
@ -214,6 +215,8 @@ class Difference(object):
def network(self):
if self.want.network is None:
return None
if self.want.network == '0.0.0.0/0' and self.have.network == 'default':
return None
if self.want.network != self.have.network:
raise F5ModuleError(
"'network' cannot be changed after it is set."

View file

@ -92,6 +92,15 @@ options:
for an HTTPS monitor.
- This parameter is only supported on BIG-IP versions 13.x and later.
version_added: 2.8
up_interval:
description:
- Specifies the interval for the system to use to perform the health check
when a resource is up.
- When C(0), specifies that the system uses the interval specified in
C(interval) to check the health of the resource.
- When any other number, enables specification of a different interval to
use when checking the health of a resource that is up.
version_added: 2.8
partition:
description:
- Device partition to manage resources on.
@ -167,6 +176,11 @@ time_until_up:
returned: changed
type: int
sample: 2
up_interval:
description: Interval for the system to use to perform the health check when a resource is up.
returned: changed
type: int
sample: 0
'''
from ansible.module_utils.basic import AnsibleModule
@ -207,6 +221,7 @@ class Parameters(AnsibleF5Parameters):
'recv': 'receive',
'recvDisable': 'receive_disable',
'sslProfile': 'ssl_profile',
'upInterval': 'up_interval',
}
api_attributes = [
@ -222,6 +237,7 @@ class Parameters(AnsibleF5Parameters):
'recvDisable',
'description',
'sslProfile',
'upInterval',
]
returnables = [
@ -236,6 +252,7 @@ class Parameters(AnsibleF5Parameters):
'receive_disable',
'description',
'ssl_profile',
'up_interval',
]
updatables = [
@ -250,6 +267,7 @@ class Parameters(AnsibleF5Parameters):
'receive_disable',
'description',
'ssl_profile',
'up_interval',
]
@property
@ -667,6 +685,7 @@ class ArgumentSpec(object):
receive=dict(),
receive_disable=dict(required=False),
ip=dict(),
up_interval=dict(type='int'),
port=dict(),
interval=dict(type='int'),
timeout=dict(type='int'),

View file

@ -483,6 +483,7 @@ except ImportError:
class Parameters(AnsibleF5Parameters):
api_map = {
'clientTimeout': 'client_timeout',
'defaultsFrom': 'parent',
'explicitFlowMigration': 'explicit_flow_migration',
'idleTimeout': 'idle_timeout',
'ipDfMode': 'ip_df_mode',
@ -518,6 +519,7 @@ class Parameters(AnsibleF5Parameters):
api_attributes = [
'clientTimeout',
'defaultsFrom',
'description',
'explicitFlowMigration',
'idleTimeout',
@ -570,6 +572,7 @@ class Parameters(AnsibleF5Parameters):
'loose_close',
'loose_initialization',
'mss_override',
'parent',
'reassemble_fragments',
'receive_window_size',
'reset_on_timeout',
@ -606,6 +609,7 @@ class Parameters(AnsibleF5Parameters):
'loose_close',
'loose_initialization',
'mss_override',
'parent',
'reassemble_fragments',
'receive_window_size',
'reset_on_timeout',

View file

@ -50,12 +50,12 @@ options:
description:
- Specifies the administrative partition to which the user has access.
C(partition_access) is required when creating a new account.
Should be in the form "partition:role". Valid roles include
C(acceleration-policy-editor), C(admin), C(application-editor), C(auditor)
C(certificate-manager), C(guest), C(irule-manager), C(manager), C(no-access)
Should be in the form "partition:role".
- Valid roles include C(acceleration-policy-editor), C(admin), C(application-editor),
C(auditor), C(certificate-manager), C(guest), C(irule-manager), C(manager), C(no-access),
C(operator), C(resource-admin), C(user-manager), C(web-application-security-administrator),
and C(web-application-security-editor). Partition portion of tuple should
be an existing partition or the value 'all'.
and C(web-application-security-editor).
- Partition portion of tuple should be an existing partition or the value 'all'.
state:
description:
- Whether the account should exist or not, taking action if the state is
@ -67,8 +67,8 @@ options:
update_password:
description:
- C(always) will allow to update passwords if the user chooses to do so.
C(on_create) will only set the password for newly created users. When
C(username_credential) is C(root), this value will be forced to C(always).
C(on_create) will only set the password for newly created users.
- When C(username_credential) is C(root), this value will be forced to C(always).
default: always
choices:
- always
@ -625,16 +625,6 @@ class BaseManager(object):
raise F5ModuleError(err)
def validate_create_parameters(self):
"""Password credentials and partition access are mandatory,
when creating a user resource.
"""
if self.want.password_credential and \
self.want.update_password != 'on_create':
err = "The 'update_password' option " \
"needs to be set to 'on_create' when creating " \
"a resource with a password."
raise F5ModuleError(err)
if self.want.partition_access is None:
err = "The 'partition_access' option " \
"is required when creating a resource."

View file

@ -98,6 +98,7 @@ options:
- Required when C(state) is C(present) and virtual server does not exist.
- When C(type) is C(internal), this parameter is ignored. For all other types,
it is required.
- Destination can also be specified as a name for an existing Virtual Address.
aliases:
- address
- ip
@ -296,7 +297,7 @@ options:
version_added: 2.8
mask:
description:
- Specifies the destination address network mask. This parameter will work with IPv4 and IPv6 tye of addresses.
- Specifies the destination address network mask. This parameter will work with IPv4 and IPv6 type of addresses.
- This is an optional parameter which can be specified when creating or updating virtual server.
- If C(destination) is set in CIDR notation format and C(mask) is provided the C(mask) parameter takes precedence.
- If catchall destination is specified, i.e. C(0.0.0.0) for IPv4 C(::) for IPv6,
@ -305,6 +306,8 @@ options:
C(ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff) is set for IPv4 and IPv6 addresses respectively.
- When C(destination) is provided in CIDR notation format and C(mask) is not specified the mask parameter is
inferred from C(destination).
- When C(destination) is provided as Virtual Address name, and C(mask) is not specified,
the mask will be C(None) allowing device set it with its internal defaults.
version_added: 2.8
ip_protocol:
description:
@ -403,30 +406,29 @@ author:
EXAMPLES = r'''
- name: Modify Port of the Virtual Server
bigip_virtual_server:
server: lb.mydomain.net
user: admin
password: secret
state: present
partition: Common
name: my-virtual-server
port: 8080
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Delete virtual server
bigip_virtual_server:
server: lb.mydomain.net
user: admin
password: secret
state: absent
partition: Common
name: my-virtual-server
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Add virtual server
bigip_virtual_server:
server: lb.mydomain.net
user: admin
password: secret
state: present
partition: Common
name: my-virtual-server
@ -449,6 +451,10 @@ EXAMPLES = r'''
- ltm-policy-3
enabled_vlans:
- /Common/vlan2
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Add FastL4 virtual server
@ -459,95 +465,108 @@ EXAMPLES = r'''
profiles:
- fastL4
state: present
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Add iRules to the Virtual Server
bigip_virtual_server:
server: lb.mydomain.net
user: admin
password: secret
name: my-virtual-server
irules:
- irule1
- irule2
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Remove one iRule from the Virtual Server
bigip_virtual_server:
server: lb.mydomain.net
user: admin
password: secret
name: my-virtual-server
irules:
- irule2
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Remove all iRules from the Virtual Server
bigip_virtual_server:
server: lb.mydomain.net
user: admin
password: secret
name: my-virtual-server
irules: ""
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Remove pool from the Virtual Server
bigip_virtual_server:
server: lb.mydomain.net
user: admin
password: secret
name: my-virtual-server
pool: ""
provider:
server: lb.mydomain.net
user: admin
password: secret
delegate_to: localhost
- name: Add metadata to virtual
bigip_pool:
server: lb.mydomain.com
user: admin
password: secret
state: absent
name: my-pool
partition: Common
metadata:
ansible: 2.4
updated_at: 2017-12-20T17:50:46Z
provider:
server: lb.mydomain.com
user: admin
password: secret
delegate_to: localhost
- name: Add virtual with two profiles
bigip_pool:
server: lb.mydomain.com
user: admin
password: secret
state: absent
name: my-pool
partition: Common
profiles:
- http
- tcp
provider:
server: lb.mydomain.com
user: admin
password: secret
delegate_to: localhost
- name: Remove HTTP profile from previous virtual
bigip_pool:
server: lb.mydomain.com
user: admin
password: secret
state: absent
name: my-pool
partition: Common
profiles:
- tcp
provider:
server: lb.mydomain.com
user: admin
password: secret
delegate_to: localhost
- name: Add the HTTP profile back to the previous virtual
bigip_pool:
server: lb.mydomain.com
user: admin
password: secret
state: absent
name: my-pool
partition: Common
profiles:
- http
- tcp
provider:
server: lb.mydomain.com
user: admin
password: secret
delegate_to: localhost
'''
@ -678,6 +697,7 @@ ip_intelligence_policy:
type: string
sample: /Common/ip-intelligence
'''
import os
import re
@ -1150,15 +1170,6 @@ class ApiParameters(Parameters):
return result
destination = re.sub(r'^/[a-zA-Z0-9_.-]+/', '', self._values['destination'])
if is_valid_ip(destination):
result = Destination(
ip=destination,
port=None,
route_domain=None,
mask=self.mask
)
return result
# Covers the following examples
#
# /Common/2700:bc00:1f10:101::6%2.80
@ -1177,11 +1188,6 @@ class ApiParameters(Parameters):
port = matches.group('port')
if port == 'any':
port = 0
ip = matches.group('ip')
if not is_valid_ip(ip):
raise F5ModuleError(
"The provided destination is not a valid IP address"
)
result = Destination(
ip=matches.group('ip'),
port=port,
@ -1193,11 +1199,6 @@ class ApiParameters(Parameters):
pattern = r'(?P<ip>[^%]+)%(?P<route_domain>[0-9]+)'
matches = re.search(pattern, destination)
if matches:
ip = matches.group('ip')
if not is_valid_ip(ip):
raise F5ModuleError(
"The provided destination is not a valid IP address"
)
result = Destination(
ip=matches.group('ip'),
port=None,
@ -1206,22 +1207,20 @@ class ApiParameters(Parameters):
)
return result
parts = destination.split('.')
if len(parts) == 4:
# IPv4
ip, port = destination.split(':')
if not is_valid_ip(ip):
raise F5ModuleError(
"The provided destination is not a valid IP address"
)
pattern = r'(?P<ip>^[a-zA-Z0-9_.-]+):(?P<port>[0-9]+)'
matches = re.search(pattern, destination)
if matches:
# this will match any IPv4 address as well as any alphanumeric Virtual Address
# that does not look like an IPv6 address.
result = Destination(
ip=ip,
port=int(port),
ip=matches.group('ip'),
port=int(matches.group('port')),
route_domain=None,
mask=self.mask
)
return result
elif len(parts) == 2:
parts = destination.split('.')
if len(parts) == 2:
# IPv6
ip, port = destination.split('.')
try:
@ -1230,10 +1229,6 @@ class ApiParameters(Parameters):
# Can be a port of "any". This only happens with IPv6
if port == 'any':
port = 0
if not is_valid_ip(ip):
raise F5ModuleError(
"The provided destination is not a valid IP address"
)
result = Destination(
ip=ip,
port=port,
@ -1241,6 +1236,16 @@ class ApiParameters(Parameters):
mask=self.mask
)
return result
# this check needs to be the last as for some reason IPv6 addr with %2 %2.port were also caught
if is_valid_ip(destination):
result = Destination(
ip=destination,
port=None,
route_domain=None,
mask=self.mask
)
return result
else:
result = Destination(ip=None, port=None, route_domain=None, mask=None)
return result
@ -1456,11 +1461,14 @@ class ModuleParameters(Parameters):
@property
def destination(self):
pattern = r'^[a-zA-Z0-9_.-]+'
addr = self._values['destination'].split("%")[0].split('/')[0]
if not is_valid_ip(addr):
raise F5ModuleError(
"The provided destination is not a valid IP address"
)
matches = re.search(pattern, addr)
if not matches:
raise F5ModuleError(
"The provided destination is not a valid IP address or a Virtual Address name"
)
result = self._format_destination(addr, self.port, self.route_domain)
return result
@ -1470,6 +1478,11 @@ class ModuleParameters(Parameters):
return None
result = self._values['destination'].split("%")
if len(result) > 1:
pattern = r'^[a-zA-Z0-9_.-]+'
matches = re.search(pattern, result[0])
if matches and not is_valid_ip(matches.group(0)):
# we need to strip RD because when using Virtual Address names the RD is not needed.
return None
return int(result[1])
return None
@ -1479,8 +1492,9 @@ class ModuleParameters(Parameters):
if self._values['destination'] is None:
result = Destination(ip=None, port=None, route_domain=None, mask=None)
return result
sanitized = self._values['destination'].split("%")[0].split('/')[0]
addr = compress_address(u'{0}'.format(sanitized))
addr = self._values['destination'].split("%")[0].split('/')[0]
if is_valid_ip(addr):
addr = compress_address(u'{0}'.format(addr))
result = Destination(ip=addr, port=self.port, route_domain=self.route_domain, mask=self.mask)
return result
@ -1494,8 +1508,11 @@ class ModuleParameters(Parameters):
if addr in ['::', '::/0', '::/any6']:
return 'any6'
if self._values['mask'] is None:
return get_netmask(u'{0}'.format(addr))
return compress_address(u'{0}'.format(self._values['mask']))
if is_valid_ip(addr):
return get_netmask(addr)
else:
return None
return compress_address(self._values['mask'])
@property
def port(self):
@ -2648,12 +2665,6 @@ class Difference(object):
def source(self):
if self.want.source is None:
return None
want = ip_interface(u'{0}'.format(self.want.source))
have = ip_interface(u'{0}'.format(self.have.destination_tuple.ip))
if want.version != have.version:
raise F5ModuleError(
"The source and destination addresses for the virtual server must be be the same type (IPv4 or IPv6)."
)
if self.want.source != self.have.source:
return self.want.source
@ -3053,7 +3064,7 @@ class ModuleManager(object):
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'code' in response and response['code'] in [400, 404]:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
@ -3097,7 +3108,9 @@ class ModuleManager(object):
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] in [400, 403]:
# Code 404 can occur when you specify a fallback profile that does
# not exist
if 'code' in response and response['code'] in [400, 403, 404]:
if 'message' in response:
raise F5ModuleError(response['message'])
else:

View file

@ -140,6 +140,7 @@ options:
choices:
- reboot
- restart-all
- failover
version_added: 2.8
sflow_poll_interval:
description:
@ -914,7 +915,7 @@ class ArgumentSpec(object):
fail_safe=dict(type='bool'),
fail_safe_timeout=dict(type='int'),
fail_safe_action=dict(
choices=['reboot', 'restart-all']
choices=['reboot', 'restart-all', 'failover']
),
sflow_poll_interval=dict(type='int'),
sflow_sampling_rate=dict(type='int'),

View file

@ -169,39 +169,6 @@ class TestManager(unittest.TestCase):
assert results['changed'] is True
assert results['partition_access'] == access
def test_create_user_raises(self, *args):
access = [{'name': 'Common', 'role': 'guest'}]
set_module_args(dict(
username_credential='someuser',
password_credential='testpass',
partition_access=access,
password='password',
server='localhost',
user='admin'
))
module = AnsibleModule(
argument_spec=self.spec.argument_spec,
supports_check_mode=self.spec.supports_check_mode
)
# Override methods to force specific logic in the module to happen
pm = PartitionedManager(module=module, params=module.params)
pm.create_on_device = Mock(return_value=True)
pm.exists = Mock(return_value=False)
mm = ModuleManager(module=module)
mm.is_version_less_than_13 = Mock(return_value=False)
mm.get_manager = Mock(return_value=pm)
msg = "The 'update_password' option " \
"needs to be set to 'on_create' when creating " \
"a resource with a password."
with pytest.raises(F5ModuleError) as ex:
mm.exec_module()
assert str(ex.value) == msg
def test_create_user_partition_access_raises(self, *args):
set_module_args(dict(
username_credential='someuser',
@ -589,39 +556,6 @@ class TestLegacyManager(unittest.TestCase):
assert results['changed'] is True
assert results['partition_access'] == access
def test_create_user_raises(self, *args):
access = [{'name': 'Common', 'role': 'guest'}]
set_module_args(dict(
username_credential='someuser',
password_credential='testpass',
partition_access=access,
password='password',
server='localhost',
user='admin'
))
module = AnsibleModule(
argument_spec=self.spec.argument_spec,
supports_check_mode=self.spec.supports_check_mode
)
# Override methods to force specific logic in the module to happen
upm = UnpartitionedManager(module=module, params=module.params)
upm.create_on_device = Mock(return_value=True)
upm.exists = Mock(return_value=False)
mm = ModuleManager(module=module)
mm.is_version_less_than_13 = Mock(return_value=True)
mm.get_manager = Mock(return_value=upm)
msg = "The 'update_password' option " \
"needs to be set to 'on_create' when creating " \
"a resource with a password."
with pytest.raises(F5ModuleError) as ex:
mm.exec_module()
assert str(ex.value) == msg
def test_create_user_partition_access_raises(self, *args):
set_module_args(dict(
username_credential='someuser',