mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-22 12:50:22 -07:00
Refactors and deprecation removals (#34830)
This patch is primarily a refactor to make the validate-modules arg-spec no longer generate a traceback. It additionally includes removal of deprecated code in the virtual server module.
This commit is contained in:
parent
a10aee0fc3
commit
c548ab0f18
26 changed files with 1612 additions and 2099 deletions
|
@ -30,7 +30,6 @@ options:
|
|||
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
|
||||
|
@ -39,14 +38,9 @@ options:
|
|||
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)
|
||||
'''
|
||||
|
@ -98,15 +92,37 @@ value:
|
|||
sample: false
|
||||
'''
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
|
@ -124,16 +140,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if self.api_map is not None and api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._values['key']
|
||||
|
@ -143,12 +149,17 @@ class Parameters(AnsibleF5Parameters):
|
|||
self._values['key'] = value
|
||||
|
||||
|
||||
class Changes(Parameters):
|
||||
pass
|
||||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.have = None
|
||||
self.want = Parameters(self.client.module.params)
|
||||
self.changes = Parameters()
|
||||
self.want = Parameters(params=self.module.params)
|
||||
self.changes = Changes()
|
||||
|
||||
def _update_changed_options(self):
|
||||
changed = {}
|
||||
|
@ -159,10 +170,10 @@ class ModuleManager(object):
|
|||
if attr1 != attr2:
|
||||
changed[key] = attr1
|
||||
if self.want.state == 'reset':
|
||||
if str(self.want.value) == str(self.want.default_value):
|
||||
changed[self.want.key] = self.want.value
|
||||
if str(self.have.value) != str(self.have.default_value):
|
||||
changed[self.want.key] = self.have.default_value
|
||||
if changed:
|
||||
self.changes = Parameters(changed)
|
||||
self.changes = Changes(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -189,7 +200,7 @@ class ModuleManager(object):
|
|||
name=self.want.key
|
||||
)
|
||||
result = resource.attrs
|
||||
return Parameters(result)
|
||||
return Parameters(params=result)
|
||||
|
||||
def exists(self):
|
||||
resource = self.client.api.tm.sys.dbs.db.load(
|
||||
|
@ -213,7 +224,7 @@ class ModuleManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
@ -235,9 +246,11 @@ class ModuleManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
self.reset_on_device()
|
||||
self.want.update({'key': self.want.key})
|
||||
self.want.update({'value': self.have.default_value})
|
||||
if self.exists():
|
||||
return True
|
||||
else:
|
||||
|
@ -249,13 +262,13 @@ class ModuleManager(object):
|
|||
resource = self.client.api.tm.sys.dbs.db.load(
|
||||
name=self.want.key
|
||||
)
|
||||
resource.update(value=self.want.default_value)
|
||||
resource.update(value=self.have.default_value)
|
||||
|
||||
|
||||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
key=dict(required=True),
|
||||
state=dict(
|
||||
default='present',
|
||||
|
@ -263,27 +276,30 @@ class ArgumentSpec(object):
|
|||
),
|
||||
value=dict()
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
client.module.fail_json(msg=str(e))
|
||||
cleanup_tokens(client)
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -30,48 +30,44 @@ options:
|
|||
gui_setup:
|
||||
description:
|
||||
- C(enable) or C(disabled) the Setup utility in the browser-based
|
||||
Configuration utility
|
||||
choices: ['yes', 'no']
|
||||
Configuration utility.
|
||||
type: bool
|
||||
lcd_display:
|
||||
description:
|
||||
- Specifies, when C(enabled), that the system menu displays on the
|
||||
LCD screen on the front of the unit. This setting has no effect
|
||||
when used on the VE platform.
|
||||
choices: ['yes', 'no']
|
||||
type: bool
|
||||
mgmt_dhcp:
|
||||
description:
|
||||
- Specifies whether or not to enable DHCP client on the management
|
||||
interface
|
||||
choices: ['yes', 'no']
|
||||
type: bool
|
||||
net_reboot:
|
||||
description:
|
||||
- Specifies, when C(enabled), that the next time you reboot the system,
|
||||
the system boots to an ISO image on the network, rather than an
|
||||
internal media drive.
|
||||
choices: ['yes', 'no']
|
||||
type: bool
|
||||
quiet_boot:
|
||||
description:
|
||||
- Specifies, when C(enabled), that the system suppresses informational
|
||||
text on the console during the boot cycle. When C(disabled), the
|
||||
system presents messages and informational text on the console during
|
||||
the boot cycle.
|
||||
choices: ['yes', 'no']
|
||||
type: bool
|
||||
security_banner:
|
||||
description:
|
||||
- Specifies whether the system displays an advisory message on the
|
||||
login screen.
|
||||
choices: ['yes', 'no']
|
||||
type: bool
|
||||
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:
|
||||
- present
|
||||
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
|
||||
|
@ -139,20 +135,40 @@ security_banner:
|
|||
sample: enabled
|
||||
'''
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_FALSE
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
|
@ -182,46 +198,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
'mgmt_dhcp', 'net_reboot', 'quiet_boot', 'console_timeout'
|
||||
]
|
||||
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
self._values['__warnings'] = []
|
||||
if params:
|
||||
self.update(params=params)
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if self.api_map is not None and api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
|
||||
class ApiParameters(Parameters):
|
||||
pass
|
||||
|
@ -323,9 +299,10 @@ class Difference(object):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.want = ModuleParameters(params=self.client.module.params)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.want = ModuleParameters(params=self.module.params)
|
||||
self.have = ApiParameters()
|
||||
self.changes = UsableChanges()
|
||||
|
||||
|
@ -335,7 +312,7 @@ class ModuleManager(object):
|
|||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = UsableChanges(changed)
|
||||
self.changes = UsableChanges(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
|
@ -351,7 +328,7 @@ class ModuleManager(object):
|
|||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = UsableChanges(changed)
|
||||
self.changes = UsableChanges(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -369,7 +346,7 @@ class ModuleManager(object):
|
|||
except iControlUnexpectedHTTPError as e:
|
||||
raise F5ModuleError(str(e))
|
||||
|
||||
reportable = ReportableChanges(self.changes.to_return())
|
||||
reportable = ReportableChanges(params=self.changes.to_return())
|
||||
changes = reportable.to_return()
|
||||
result.update(**changes)
|
||||
result.update(dict(changed=changed))
|
||||
|
@ -379,7 +356,7 @@ class ModuleManager(object):
|
|||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.client.module.deprecate(
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
@ -390,13 +367,13 @@ class ModuleManager(object):
|
|||
def read_current_from_device(self):
|
||||
resource = self.client.api.tm.sys.global_settings.load()
|
||||
result = resource.attrs
|
||||
return ApiParameters(result)
|
||||
return ApiParameters(params=result)
|
||||
|
||||
def update(self):
|
||||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
@ -412,7 +389,7 @@ class ArgumentSpec(object):
|
|||
self.supports_check_mode = True
|
||||
self.states = ['present']
|
||||
self.on_off_choices = ['enabled', 'disabled', 'True', 'False'] + list(BOOLEANS)
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
security_banner=dict(
|
||||
choices=self.on_off_choices
|
||||
),
|
||||
|
@ -435,39 +412,30 @@ class ArgumentSpec(object):
|
|||
console_timeout=dict(required=False, type='int', default=None),
|
||||
state=dict(default='present', choices=['present'])
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
|
||||
|
||||
def cleanup_tokens(client):
|
||||
try:
|
||||
resource = client.api.shared.authz.tokens_s.token.load(
|
||||
name=client.api.icrs.token
|
||||
)
|
||||
resource.delete()
|
||||
except Exception:
|
||||
pass
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
cleanup_tokens(client)
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -24,11 +24,20 @@ options:
|
|||
description:
|
||||
- The name of the traffic group.
|
||||
required: True
|
||||
notes:
|
||||
- Requires the f5-sdk Python package on the host. This is as easy as
|
||||
C(pip install f5-sdk).
|
||||
requirements:
|
||||
- f5-sdk >= 3.0.5
|
||||
partition:
|
||||
description:
|
||||
- Device partition to manage resources on.
|
||||
default: Common
|
||||
version_added: 2.5
|
||||
state:
|
||||
description:
|
||||
- When C(present), ensures that the traffic group exists.
|
||||
- When C(absent), ensures the traffic group is removed.
|
||||
default: present
|
||||
choices:
|
||||
- present
|
||||
- absent
|
||||
version_added: 2.5
|
||||
extends_documentation_fragment: f5
|
||||
author:
|
||||
- Tim Rupp (@caphrim007)
|
||||
|
@ -49,18 +58,38 @@ RETURN = r'''
|
|||
# only common fields returned
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
|
@ -80,36 +109,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
|
||||
]
|
||||
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
self._values['__warnings'] = []
|
||||
if params:
|
||||
self.update(params=params)
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
def to_return(self):
|
||||
result = {}
|
||||
try:
|
||||
|
@ -120,16 +119,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
pass
|
||||
return result
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if self.api_map is not None and api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
|
||||
class Changes(Parameters):
|
||||
pass
|
||||
|
@ -164,9 +153,10 @@ class Difference(object):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.want = Parameters(self.client.module.params)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.want = Parameters(params=self.module.params)
|
||||
self.changes = Changes()
|
||||
|
||||
def _set_changed_options(self):
|
||||
|
@ -175,38 +165,9 @@ class ModuleManager(object):
|
|||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = Changes(changed)
|
||||
self.changes = Changes(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
"""Sets the changed updatables when updating a resource
|
||||
|
||||
A module needs to know what changed to determine whether to update
|
||||
a resource (or set of resources). This method accomplishes this by
|
||||
invoking the Difference engine code.
|
||||
|
||||
Each parameter in the `Parameter` class' `updatables` array will be
|
||||
given to the Difference engine's `compare` method. This is done in the
|
||||
order the updatables are listed in the array.
|
||||
|
||||
The `compare` method updates the `changes` dictionary if the following
|
||||
way,
|
||||
|
||||
* If `None` is returned, a change will not be registered.
|
||||
* If a dictionary is returned, the `changes` dictionary will be updated
|
||||
with the values in what was returned.
|
||||
* Otherwise, the `changes` dictionary's key (the parameter being
|
||||
compared) will be set to the value that is returned by `compare`
|
||||
|
||||
The dictionary behavior is in place to allow you to change the key
|
||||
that is set in the `changes` dictionary. There are frequently cases
|
||||
where there is not a clean API map that can be set, nor a way to
|
||||
otherwise allow you to change the attribute name of the resource being
|
||||
updated before it is sent off to the remote device. Using a dictionary
|
||||
return value of `compare` allows you to do this.
|
||||
|
||||
Returns:
|
||||
bool: True when changes are present. False otherwise.
|
||||
"""
|
||||
diff = Difference(self.want, self.have)
|
||||
updatables = Parameters.updatables
|
||||
changed = dict()
|
||||
|
@ -220,7 +181,7 @@ class ModuleManager(object):
|
|||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = Changes(changed)
|
||||
self.changes = Changes(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -252,7 +213,7 @@ class ModuleManager(object):
|
|||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.client.module.deprecate(
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
@ -274,13 +235,13 @@ class ModuleManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
|
@ -293,7 +254,7 @@ class ModuleManager(object):
|
|||
raise F5ModuleError(
|
||||
"Traffic groups can only be created in the /Common partition"
|
||||
)
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
return True
|
||||
|
@ -333,49 +294,44 @@ class ModuleManager(object):
|
|||
partition=self.want.partition
|
||||
)
|
||||
result = resource.attrs
|
||||
return Parameters(result)
|
||||
return Parameters(params=result)
|
||||
|
||||
|
||||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
state=dict(default='present', choices=['absent', 'present'])
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
partition=dict(
|
||||
default='Common',
|
||||
fallback=(env_fallback, ['F5_PARTITION'])
|
||||
)
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
|
||||
|
||||
def cleanup_tokens(client):
|
||||
try:
|
||||
resource = client.api.shared.authz.tokens_s.token.load(
|
||||
name=client.api.icrs.token
|
||||
)
|
||||
resource.delete()
|
||||
except Exception:
|
||||
pass
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
cleanup_tokens(client)
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -87,8 +87,6 @@ options:
|
|||
- installed
|
||||
- present
|
||||
notes:
|
||||
- Requires the f5-sdk Python package on the host. This is as easy as
|
||||
pip install f5-sdk.
|
||||
- Only the most basic checks are performed by this module. Other checks and
|
||||
considerations need to be taken into account. See the following URL.
|
||||
https://support.f5.com/kb/en-us/solutions/public/11000/300/sol11318.html
|
||||
|
@ -110,8 +108,6 @@ notes:
|
|||
- This module does not support restoring encrypted archives on replacement
|
||||
RMA units.
|
||||
extends_documentation_fragment: f5
|
||||
requirements:
|
||||
- f5-sdk
|
||||
author:
|
||||
- Tim Rupp (@caphrim007)
|
||||
'''
|
||||
|
@ -182,15 +178,41 @@ RETURN = r'''
|
|||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
from distutils.version import LooseVersion
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.six import iteritems
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
|
@ -200,11 +222,6 @@ except ImportError:
|
|||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
api_map = {}
|
||||
|
@ -292,14 +309,15 @@ class Parameters(AnsibleF5Parameters):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.client = kwargs.get('client', None)
|
||||
self.kwargs = kwargs
|
||||
|
||||
def exec_module(self):
|
||||
if self.is_version_v1():
|
||||
manager = V1Manager(self.client)
|
||||
manager = V1Manager(**self.kwargs)
|
||||
else:
|
||||
manager = V2Manager(self.client)
|
||||
manager = V2Manager(**self.kwargs)
|
||||
|
||||
return manager.exec_module()
|
||||
|
||||
|
@ -321,10 +339,10 @@ class ModuleManager(object):
|
|||
|
||||
|
||||
class BaseManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.have = None
|
||||
self.want = Parameters(self.client.module.params)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.want = Parameters(params=self.module.params)
|
||||
self.changes = Parameters()
|
||||
|
||||
def exec_module(self):
|
||||
|
@ -352,7 +370,7 @@ class BaseManager(object):
|
|||
return self.create()
|
||||
|
||||
def update(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
if self.want.force:
|
||||
return True
|
||||
return False
|
||||
|
@ -365,7 +383,7 @@ class BaseManager(object):
|
|||
return False
|
||||
|
||||
def create(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
if not self.exists():
|
||||
|
@ -386,7 +404,7 @@ class BaseManager(object):
|
|||
return False
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
|
@ -466,7 +484,6 @@ class V1Manager(BaseManager):
|
|||
* No API to upload UCS files
|
||||
|
||||
"""
|
||||
|
||||
def create_on_device(self):
|
||||
remote_path = "/var/local/ucs"
|
||||
tpath_name = '/var/config/rest/downloads'
|
||||
|
@ -563,7 +580,7 @@ class V2Manager(V1Manager):
|
|||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
force=dict(
|
||||
type='bool',
|
||||
default='no'
|
||||
|
@ -585,30 +602,30 @@ class ArgumentSpec(object):
|
|||
),
|
||||
ucs=dict(required=True)
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
if sys.version_info < (2, 7):
|
||||
raise F5ModuleError("F5 Ansible modules require Python >= 2.7")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
client.module.fail_json(msg=str(e))
|
||||
cleanup_tokens(client)
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -74,13 +74,14 @@ options:
|
|||
choices:
|
||||
- always
|
||||
- on_create
|
||||
partition:
|
||||
description:
|
||||
- Device partition to manage resources on.
|
||||
default: Common
|
||||
version_added: 2.5
|
||||
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)
|
||||
- Wojciech Wypior (@wojtek0806)
|
||||
|
@ -191,24 +192,45 @@ shell:
|
|||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
from distutils.version import LooseVersion
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
api_map = {
|
||||
|
@ -228,36 +250,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
'shell', 'partitionAccess', 'description', 'name', 'password'
|
||||
]
|
||||
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
self._values['__warnings'] = []
|
||||
if params:
|
||||
self.update(params=params)
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
@property
|
||||
def partition_access(self):
|
||||
"""Partition access values will require some transformation.
|
||||
|
@ -324,18 +316,28 @@ class Parameters(AnsibleF5Parameters):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.kwargs = kwargs
|
||||
|
||||
def exec_module(self):
|
||||
if self.is_root_username_credential():
|
||||
manager = RootUserManager(self.client)
|
||||
manager = self.get_manager('root')
|
||||
elif self.is_version_less_than_13():
|
||||
manager = UnparitionedManager(self.client)
|
||||
manager = self.get_manager('v1')
|
||||
else:
|
||||
manager = PartitionedManager(self.client)
|
||||
manager = self.get_manager('v2')
|
||||
return manager.exec_module()
|
||||
|
||||
def get_manager(self, type):
|
||||
if type == 'root':
|
||||
return RootUserManager(**self.kwargs)
|
||||
elif type == 'v1':
|
||||
return UnparitionedManager(**self.kwargs)
|
||||
elif type == 'v2':
|
||||
return PartitionedManager(**self.kwargs)
|
||||
|
||||
def is_version_less_than_13(self):
|
||||
"""Checks to see if the TMOS version is less than 13
|
||||
|
||||
|
@ -351,17 +353,18 @@ class ModuleManager(object):
|
|||
return False
|
||||
|
||||
def is_root_username_credential(self):
|
||||
user = self.client.module.params.get('username_credential', None)
|
||||
user = self.module.params.get('username_credential', None)
|
||||
if user == 'root':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class BaseManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.have = None
|
||||
self.want = Parameters(self.client.module.params)
|
||||
self.want = Parameters(params=self.module.params)
|
||||
self.changes = Parameters()
|
||||
|
||||
def exec_module(self):
|
||||
|
@ -388,7 +391,7 @@ class BaseManager(object):
|
|||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = Parameters(changed)
|
||||
self.changes = Parameters(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
changed = {}
|
||||
|
@ -411,7 +414,7 @@ class BaseManager(object):
|
|||
changed[key] = attr1
|
||||
|
||||
if changed:
|
||||
self.changes = Parameters(changed)
|
||||
self.changes = Parameters(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -481,13 +484,13 @@ class BaseManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
|
@ -499,7 +502,7 @@ class BaseManager(object):
|
|||
if self.want.shell == 'bash':
|
||||
self.validate_shell_parameter()
|
||||
self._set_changed_options()
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
return True
|
||||
|
@ -518,7 +521,7 @@ class UnparitionedManager(BaseManager):
|
|||
def read_current_from_device(self):
|
||||
tmp_res = self.client.api.tm.auth.users.user.load(name=self.want.name)
|
||||
result = tmp_res.attrs
|
||||
return Parameters(result)
|
||||
return Parameters(params=result)
|
||||
|
||||
def exists(self):
|
||||
return self.client.api.tm.auth.users.user.exists(name=self.want.name)
|
||||
|
@ -570,7 +573,7 @@ class PartitionedManager(BaseManager):
|
|||
def read_current_from_device(self):
|
||||
resource = self._read_one_resource_from_collection()
|
||||
result = resource.attrs
|
||||
return Parameters(result)
|
||||
return Parameters(params=result)
|
||||
|
||||
def exists(self):
|
||||
collection = self.client.api.tm.auth.users.get_collection(
|
||||
|
@ -646,7 +649,7 @@ class RootUserManager(BaseManager):
|
|||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
name=dict(
|
||||
required=True,
|
||||
aliases=['username_credential']
|
||||
|
@ -664,41 +667,37 @@ class ArgumentSpec(object):
|
|||
update_password=dict(
|
||||
default='always',
|
||||
choices=['always', 'on_create']
|
||||
),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
partition=dict(
|
||||
default='Common',
|
||||
fallback=(env_fallback, ['F5_PARTITION'])
|
||||
)
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
|
||||
|
||||
def cleanup_tokens(client):
|
||||
try:
|
||||
resource = client.api.shared.authz.tokens_s.token.load(
|
||||
name=client.api.icrs.token
|
||||
)
|
||||
resource.delete()
|
||||
except Exception:
|
||||
pass
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
cleanup_tokens(client)
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -112,9 +112,11 @@ options:
|
|||
- The number you can specify depends on the type of hardware you have.
|
||||
- In the event of a reboot, the system persists the guest to the same slot on
|
||||
which it ran prior to the reboot.
|
||||
partition:
|
||||
description:
|
||||
- Device partition to manage resources on.
|
||||
default: Common
|
||||
notes:
|
||||
- Requires the f5-sdk Python package on the host. This is as easy as pip
|
||||
install f5-sdk.
|
||||
- This module can take a lot of time to deploy vCMP guests. This is an intrinsic
|
||||
limitation of the vCMP system because it is booting real VMs on the BIG-IP
|
||||
device. This boot time is very similar in length to the time it takes to
|
||||
|
@ -123,8 +125,6 @@ notes:
|
|||
means that it is not unusual for a vCMP host with many guests to take a
|
||||
long time (60+ minutes) to reboot and bring all the guests online. The
|
||||
BIG-IP chassis will be available before all vCMP guests are online.
|
||||
requirements:
|
||||
- f5-sdk >= 3.0.3
|
||||
- netaddr
|
||||
extends_documentation_fragment: f5
|
||||
author:
|
||||
|
@ -173,16 +173,41 @@ vlans:
|
|||
sample: ['/Common/vlan1', '/Common/vlan2']
|
||||
'''
|
||||
|
||||
import time
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
from collections import namedtuple
|
||||
|
||||
import time
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
try:
|
||||
from netaddr import IPAddress, AddrFormatError, IPNetwork
|
||||
|
@ -192,7 +217,6 @@ except ImportError:
|
|||
|
||||
try:
|
||||
from f5.utils.responses.handlers import Stats
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
@ -222,37 +246,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
'state'
|
||||
]
|
||||
|
||||
def __init__(self, params=None, client=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
self._values['__warnings'] = []
|
||||
if params:
|
||||
self.update(params=params)
|
||||
self.client = client
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
def _fqdn_name(self, value):
|
||||
if value is not None and not value.startswith('/'):
|
||||
return '/{0}/{1}'.format(self.partition, value)
|
||||
|
@ -268,16 +261,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
pass
|
||||
return result
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if self.api_map is not None and api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
@property
|
||||
def mgmt_route(self):
|
||||
if self._values['mgmt_route'] is None:
|
||||
|
@ -394,9 +377,10 @@ class Difference(object):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.want = Parameters(client=client, params=self.client.module.params)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.want = Parameters(client=self.client, params=self.module.params)
|
||||
self.changes = Changes()
|
||||
|
||||
def _set_changed_options(self):
|
||||
|
@ -405,7 +389,7 @@ class ModuleManager(object):
|
|||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = Changes(changed)
|
||||
self.changes = Changes(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
|
@ -418,7 +402,7 @@ class ModuleManager(object):
|
|||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = Parameters(changed)
|
||||
self.changes = Parameters(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -450,7 +434,7 @@ class ModuleManager(object):
|
|||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.client.module.deprecate(
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
@ -476,7 +460,7 @@ class ModuleManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
if self.want.state == 'provisioned':
|
||||
|
@ -488,7 +472,7 @@ class ModuleManager(object):
|
|||
return True
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
if self.want.delete_virtual_disk:
|
||||
self.have = self.read_current_from_device()
|
||||
|
@ -501,7 +485,7 @@ class ModuleManager(object):
|
|||
|
||||
def create(self):
|
||||
self._set_changed_options()
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
if self.want.mgmt_tuple.subnet is None:
|
||||
self.want.update(dict(
|
||||
|
@ -547,7 +531,7 @@ class ModuleManager(object):
|
|||
name=self.want.name
|
||||
)
|
||||
result = resource.attrs
|
||||
return Parameters(result)
|
||||
return Parameters(params=result)
|
||||
|
||||
def remove_virtual_disk(self):
|
||||
if self.virtual_disk_exists():
|
||||
|
@ -666,7 +650,7 @@ class ModuleManager(object):
|
|||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
vlans=dict(type='list'),
|
||||
mgmt_network=dict(choices=['bridged', 'isolated', 'host only']),
|
||||
|
@ -680,47 +664,41 @@ class ArgumentSpec(object):
|
|||
delete_virtual_disk=dict(
|
||||
type='bool', default='no'
|
||||
),
|
||||
cores_per_slot=dict(type='int')
|
||||
cores_per_slot=dict(type='int'),
|
||||
partition=dict(
|
||||
default='Common',
|
||||
fallback=(env_fallback, ['F5_PARTITION'])
|
||||
)
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
self.required_if = [
|
||||
['mgmt_network', 'bridged', ['mgmt_address']]
|
||||
]
|
||||
|
||||
|
||||
def cleanup_tokens(client):
|
||||
try:
|
||||
resource = client.api.shared.authz.tokens_s.token.load(
|
||||
name=client.api.icrs.token
|
||||
)
|
||||
resource.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
if not HAS_NETADDR:
|
||||
raise F5ModuleError("The python netaddr module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
if not HAS_NETADDR:
|
||||
module.fail_json(msg="The python netaddr module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
cleanup_tokens(client)
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -100,14 +100,11 @@ options:
|
|||
- Specifies whether the system uses route advertisement for this
|
||||
virtual address. When disabled, the system does not advertise
|
||||
routes for this virtual address.
|
||||
choices:
|
||||
- yes
|
||||
- no
|
||||
type: bool
|
||||
partition:
|
||||
description:
|
||||
- Device partition to manage resources on.
|
||||
required: False
|
||||
default: 'Common'
|
||||
default: Common
|
||||
version_added: 2.5
|
||||
traffic_group:
|
||||
description:
|
||||
|
@ -116,13 +113,10 @@ options:
|
|||
will be used.
|
||||
version_added: 2.5
|
||||
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. This is as easy as pip
|
||||
install netaddr.
|
||||
extends_documentation_fragment: f5
|
||||
requirements:
|
||||
- f5-sdk
|
||||
- netaddr
|
||||
author:
|
||||
- Tim Rupp (@caphrim007)
|
||||
|
@ -193,24 +187,47 @@ state:
|
|||
sample: disabled
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_FALSE
|
||||
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
try:
|
||||
import netaddr
|
||||
HAS_NETADDR = True
|
||||
except ImportError:
|
||||
HAS_NETADDR = False
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_TRUE
|
||||
from ansible.module_utils.parsing.convert_bool import BOOLEANS_FALSE
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
api_map = {
|
||||
|
@ -349,17 +366,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(
|
||||
self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
|
||||
class Changes(Parameters):
|
||||
pass
|
||||
|
@ -393,10 +399,11 @@ class Difference(object):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.have = None
|
||||
self.want = Parameters(self.client.module.params)
|
||||
self.want = Parameters(client=self.client, params=self.module.params)
|
||||
self.changes = Changes()
|
||||
|
||||
def _set_changed_options(self):
|
||||
|
@ -405,7 +412,7 @@ class ModuleManager(object):
|
|||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = Changes(changed)
|
||||
self.changes = Changes(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
|
@ -421,7 +428,7 @@ class ModuleManager(object):
|
|||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = Changes(changed)
|
||||
self.changes = Changes(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -467,7 +474,7 @@ class ModuleManager(object):
|
|||
partition=self.want.partition
|
||||
)
|
||||
result = resource.attrs
|
||||
return Parameters(result)
|
||||
return Parameters(params=result)
|
||||
|
||||
def exists(self):
|
||||
result = self.client.api.tm.ltm.virtual_address_s.virtual_address.exists(
|
||||
|
@ -481,18 +488,18 @@ class ModuleManager(object):
|
|||
if self.want.netmask is not None:
|
||||
if self.have.netmask != self.want.netmask:
|
||||
raise F5ModuleError(
|
||||
"The netmask cannot be changed. Delete and recreate"
|
||||
"The netmask cannot be changed. Delete and recreate "
|
||||
"the virtual address if you need to do this."
|
||||
)
|
||||
if self.want.address is not None:
|
||||
if self.have.address != self.want.address:
|
||||
raise F5ModuleError(
|
||||
"The address cannot be changed. Delete and recreate"
|
||||
"The address cannot be changed. Delete and recreate "
|
||||
"the virtual address if you need to do this."
|
||||
)
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
@ -509,7 +516,7 @@ class ModuleManager(object):
|
|||
self._set_changed_options()
|
||||
if self.want.traffic_group is None:
|
||||
self.want.update({'traffic_group': '/Common/traffic-group-1'})
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
if self.exists():
|
||||
|
@ -527,7 +534,7 @@ class ModuleManager(object):
|
|||
)
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
|
@ -545,7 +552,7 @@ class ModuleManager(object):
|
|||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
state=dict(
|
||||
default='present',
|
||||
choices=['present', 'absent', 'disabled', 'enabled']
|
||||
|
@ -577,29 +584,38 @@ class ArgumentSpec(object):
|
|||
use_route_advertisement=dict(
|
||||
type='bool'
|
||||
),
|
||||
traffic_group=dict()
|
||||
traffic_group=dict(),
|
||||
partition=dict(
|
||||
default='Common',
|
||||
fallback=(env_fallback, ['F5_PARTITION'])
|
||||
)
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
if not HAS_NETADDR:
|
||||
module.fail_json(msg="The python netaddr module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
client.module.fail_json(msg=str(e))
|
||||
cleanup_tokens(client)
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -137,14 +137,6 @@ options:
|
|||
- Default Profile which manages the session persistence.
|
||||
- If you want to remove the existing default persistence profile, specify an
|
||||
empty value; C(""). See the documentation for an example.
|
||||
route_advertisement_state:
|
||||
description:
|
||||
- Enable route advertisement for destination.
|
||||
- Deprecated in 2.4. Use the C(bigip_virtual_address) module instead.
|
||||
choices:
|
||||
- enabled
|
||||
- disabled
|
||||
version_added: "2.3"
|
||||
description:
|
||||
description:
|
||||
- Virtual server description.
|
||||
|
@ -171,12 +163,9 @@ options:
|
|||
version_added: 2.5
|
||||
notes:
|
||||
- Requires BIG-IP software version >= 11
|
||||
- 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. This is as easy as pip
|
||||
install netaddr.
|
||||
requirements:
|
||||
- f5-sdk
|
||||
- netaddr
|
||||
extends_documentation_fragment: f5
|
||||
author:
|
||||
|
@ -381,18 +370,38 @@ metadata:
|
|||
|
||||
import re
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
from collections import namedtuple
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
try:
|
||||
import netaddr
|
||||
|
@ -402,100 +411,6 @@ except ImportError:
|
|||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
if params:
|
||||
self.update(params=params)
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
def to_return(self):
|
||||
result = {}
|
||||
for returnable in self.returnables:
|
||||
try:
|
||||
result[returnable] = getattr(self, returnable)
|
||||
except Exception as ex:
|
||||
pass
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
def _fqdn_name(self, value):
|
||||
if value is not None and not value.startswith('/'):
|
||||
return '/{0}/{1}'.format(self.partition, value)
|
||||
return value
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if self.api_map is not None and api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
|
||||
class VirtualAddressParameters(Parameters):
|
||||
api_map = {
|
||||
'routeAdvertisement': 'route_advertisement_state'
|
||||
}
|
||||
returnables = [
|
||||
'route_advertisement_state'
|
||||
]
|
||||
|
||||
updatables = [
|
||||
'route_advertisement_state'
|
||||
]
|
||||
|
||||
api_attributes = [
|
||||
'routeAdvertisement'
|
||||
]
|
||||
|
||||
|
||||
class VirtualAddressModuleParameters(VirtualAddressParameters):
|
||||
@property
|
||||
def route_advertisement_state(self):
|
||||
# TODO: Remove in 2.5
|
||||
if self._values['route_advertisement_state'] is None:
|
||||
return None
|
||||
if self._values['__warnings'] is None:
|
||||
self._values['__warnings'] = []
|
||||
self._values['__warnings'].append(
|
||||
dict(
|
||||
msg="Usage of the 'route_advertisement_state' parameter is deprecated. Use the bigip_virtual_address module instead",
|
||||
version='2.4'
|
||||
)
|
||||
)
|
||||
return str(self._values['route_advertisement_state'])
|
||||
|
||||
|
||||
class VirtualAddressApiParameters(VirtualAddressParameters):
|
||||
pass
|
||||
|
||||
|
||||
class VirtualServerParameters(Parameters):
|
||||
api_map = {
|
||||
'sourceAddressTranslation': 'snat',
|
||||
'fallbackPersistence': 'fallback_persistence_profile',
|
||||
|
@ -566,12 +481,25 @@ class VirtualServerParameters(Parameters):
|
|||
'vlans_disabled'
|
||||
]
|
||||
|
||||
def __init__(self, params=None):
|
||||
super(VirtualServerParameters, self).__init__(params)
|
||||
self.profiles_mutex = [
|
||||
'sip', 'sipsession', 'iiop', 'rtsp', 'http', 'diameter',
|
||||
'diametersession', 'radius', 'ftp', 'tftp', 'dns', 'pptp', 'fix'
|
||||
]
|
||||
profiles_mutex = [
|
||||
'sip', 'sipsession', 'iiop', 'rtsp', 'http', 'diameter',
|
||||
'diametersession', 'radius', 'ftp', 'tftp', 'dns', 'pptp', 'fix'
|
||||
]
|
||||
|
||||
def to_return(self):
|
||||
result = {}
|
||||
for returnable in self.returnables:
|
||||
try:
|
||||
result[returnable] = getattr(self, returnable)
|
||||
except Exception as ex:
|
||||
pass
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
def _fqdn_name(self, value):
|
||||
if value is not None and not value.startswith('/'):
|
||||
return '/{0}/{1}'.format(self.partition, value)
|
||||
return value
|
||||
|
||||
def is_valid_ip(self, value):
|
||||
try:
|
||||
|
@ -618,7 +546,7 @@ class VirtualServerParameters(Parameters):
|
|||
return result
|
||||
|
||||
|
||||
class VirtualServerApiParameters(VirtualServerParameters):
|
||||
class ApiParameters(Parameters):
|
||||
@property
|
||||
def destination(self):
|
||||
if self._values['destination'] is None:
|
||||
|
@ -648,7 +576,7 @@ class VirtualServerApiParameters(VirtualServerParameters):
|
|||
if self._values['destination'] is None:
|
||||
result = Destination(ip=None, port=None, route_domain=None)
|
||||
return result
|
||||
destination = re.sub(r'^/[a-zA-Z_.-]+/', '', self._values['destination'])
|
||||
destination = re.sub(r'^/[a-zA-Z0-9_.-]+/', '', self._values['destination'])
|
||||
|
||||
if self.is_valid_ip(destination):
|
||||
result = Destination(
|
||||
|
@ -816,7 +744,7 @@ class VirtualServerApiParameters(VirtualServerParameters):
|
|||
return result
|
||||
|
||||
|
||||
class VirtualServerModuleParameters(VirtualServerParameters):
|
||||
class ModuleParameters(Parameters):
|
||||
def _handle_profile_context(self, tmp):
|
||||
if 'context' not in tmp:
|
||||
tmp['context'] = 'all'
|
||||
|
@ -1106,7 +1034,11 @@ class VirtualServerModuleParameters(VirtualServerParameters):
|
|||
return result
|
||||
|
||||
|
||||
class VirtualServerUsableChanges(VirtualServerParameters):
|
||||
class Changes(Parameters):
|
||||
pass
|
||||
|
||||
|
||||
class UsableChanges(Changes):
|
||||
@property
|
||||
def vlans(self):
|
||||
if self._values['vlans'] is None:
|
||||
|
@ -1118,11 +1050,7 @@ class VirtualServerUsableChanges(VirtualServerParameters):
|
|||
return self._values['vlans']
|
||||
|
||||
|
||||
class VirtualAddressUsableChanges(VirtualAddressParameters):
|
||||
pass
|
||||
|
||||
|
||||
class VirtualServerReportableChanges(VirtualServerParameters):
|
||||
class ReportableChanges(Changes):
|
||||
@property
|
||||
def snat(self):
|
||||
if self._values['snat'] is None:
|
||||
|
@ -1137,13 +1065,13 @@ class VirtualServerReportableChanges(VirtualServerParameters):
|
|||
|
||||
@property
|
||||
def destination(self):
|
||||
params = VirtualServerApiParameters(dict(destination=self._values['destination']))
|
||||
params = ApiParameters(params=dict(destination=self._values['destination']))
|
||||
result = params.destination_tuple.ip
|
||||
return result
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
params = VirtualServerApiParameters(dict(destination=self._values['destination']))
|
||||
params = ApiParameters(params=dict(destination=self._values['destination']))
|
||||
result = params.destination_tuple.port
|
||||
return result
|
||||
|
||||
|
@ -1175,10 +1103,6 @@ class VirtualServerReportableChanges(VirtualServerParameters):
|
|||
return self._values['vlans']
|
||||
|
||||
|
||||
class VirtualAddressReportableChanges(VirtualAddressParameters):
|
||||
pass
|
||||
|
||||
|
||||
class Difference(object):
|
||||
def __init__(self, want, have=None):
|
||||
self.have = have
|
||||
|
@ -1461,49 +1385,12 @@ class Difference(object):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
|
||||
def exec_module(self):
|
||||
managers = list()
|
||||
managers.append(self.get_manager('virtual_server'))
|
||||
if self.client.module.params['route_advertisement_state'] is not None:
|
||||
managers.append(self.get_manager('virtual_address'))
|
||||
result = self.execute_managers(managers)
|
||||
return result
|
||||
|
||||
def execute_managers(self, managers):
|
||||
results = dict(changed=False)
|
||||
for manager in managers:
|
||||
result = manager.exec_module()
|
||||
for k, v in iteritems(result):
|
||||
if k == 'changed':
|
||||
if v is True:
|
||||
results['changed'] = True
|
||||
else:
|
||||
results[k] = v
|
||||
return results
|
||||
|
||||
def get_manager(self, type):
|
||||
vsm = VirtualServerManager(self.client)
|
||||
if type == 'virtual_server':
|
||||
return vsm
|
||||
elif type == 'virtual_address':
|
||||
self.set_name_of_virtual_address()
|
||||
result = VirtualAddressManager(self.client)
|
||||
return result
|
||||
|
||||
def set_name_of_virtual_address(self):
|
||||
mgr = VirtualServerManager(self.client)
|
||||
params = mgr.read_current_from_device()
|
||||
destination = params.destination_tuple
|
||||
self.client.module.params['name'] = destination.ip
|
||||
|
||||
|
||||
class BaseManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.have = None
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.have = ApiParameters()
|
||||
self.want = ModuleParameters(client=self.client, params=self.module.params)
|
||||
self.changes = UsableChanges()
|
||||
|
||||
def exec_module(self):
|
||||
changed = False
|
||||
|
@ -1518,7 +1405,7 @@ class BaseManager(object):
|
|||
except iControlUnexpectedHTTPError as e:
|
||||
raise F5ModuleError(str(e))
|
||||
|
||||
reportable = self.get_reportable_changes()
|
||||
reportable = ReportableChanges(params=self.changes.to_return())
|
||||
changes = reportable.to_return()
|
||||
result.update(**changes)
|
||||
result.update(dict(changed=changed))
|
||||
|
@ -1528,7 +1415,7 @@ class BaseManager(object):
|
|||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.client.module.deprecate(
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
@ -1548,23 +1435,11 @@ class BaseManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
||||
def create(self):
|
||||
if self.client.check_mode:
|
||||
return True
|
||||
|
||||
# This must be changed back to a list to make a valid REST API
|
||||
# value. The module manipulates this as a normal dictionary
|
||||
if self.want.default_persistence_profile is not None:
|
||||
self.want.update({'default_persistence_profile': [self.want.default_persistence_profile]})
|
||||
|
||||
self.create_on_device()
|
||||
return True
|
||||
|
||||
def should_update(self):
|
||||
result = self._update_changed_options()
|
||||
if result:
|
||||
|
@ -1572,36 +1447,28 @@ class BaseManager(object):
|
|||
return False
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
raise F5ModuleError("Failed to delete the resource")
|
||||
return True
|
||||
|
||||
|
||||
class VirtualServerManager(BaseManager):
|
||||
def __init__(self, client):
|
||||
super(VirtualServerManager, self).__init__(client)
|
||||
self.have = None
|
||||
self.want = VirtualServerModuleParameters(self.client.module.params)
|
||||
self.changes = VirtualServerUsableChanges()
|
||||
|
||||
def get_reportable_changes(self):
|
||||
result = VirtualServerReportableChanges(self.changes.to_return())
|
||||
result = ReportableChanges(params=self.changes.to_return())
|
||||
return result
|
||||
|
||||
def _set_changed_options(self):
|
||||
changed = {}
|
||||
for key in VirtualServerParameters.returnables:
|
||||
for key in Parameters.returnables:
|
||||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = VirtualServerUsableChanges(changed)
|
||||
self.changes = UsableChanges(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
updatables = VirtualServerParameters.updatables
|
||||
updatables = Parameters.updatables
|
||||
changed = dict()
|
||||
for k in updatables:
|
||||
change = diff.compare(k)
|
||||
|
@ -1613,7 +1480,7 @@ class VirtualServerManager(BaseManager):
|
|||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = VirtualServerUsableChanges(changed)
|
||||
self.changes = UsableChanges(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -1628,6 +1495,11 @@ class VirtualServerManager(BaseManager):
|
|||
required_resources = ['destination', 'port']
|
||||
|
||||
self._set_changed_options()
|
||||
# This must be changed back to a list to make a valid REST API
|
||||
# value. The module manipulates this as a normal dictionary
|
||||
if self.want.default_persistence_profile is not None:
|
||||
self.want.update({'default_persistence_profile': [self.want.default_persistence_profile]})
|
||||
|
||||
if self.want.destination is None:
|
||||
raise F5ModuleError(
|
||||
"'destination' must be specified when creating a virtual server"
|
||||
|
@ -1652,7 +1524,10 @@ class VirtualServerManager(BaseManager):
|
|||
raise F5ModuleError(
|
||||
"The source and destination addresses for the virtual server must be be the same type (IPv4 or IPv6)."
|
||||
)
|
||||
return super(VirtualServerManager, self).create()
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
return True
|
||||
|
||||
def update_on_device(self):
|
||||
params = self.changes.api_params()
|
||||
|
@ -1674,7 +1549,7 @@ class VirtualServerManager(BaseManager):
|
|||
)
|
||||
params = result.attrs
|
||||
params.update(dict(kind=result.to_dict().get('kind', None)))
|
||||
result = VirtualServerApiParameters(params)
|
||||
result = ApiParameters(params=params)
|
||||
return result
|
||||
|
||||
def create_on_device(self):
|
||||
|
@ -1694,71 +1569,10 @@ class VirtualServerManager(BaseManager):
|
|||
resource.delete()
|
||||
|
||||
|
||||
class VirtualAddressManager(BaseManager):
|
||||
def __init__(self, client):
|
||||
super(VirtualAddressManager, self).__init__(client)
|
||||
self.want = VirtualAddressModuleParameters(self.client.module.params)
|
||||
self.have = VirtualAddressApiParameters()
|
||||
self.changes = VirtualAddressUsableChanges()
|
||||
|
||||
def get_reportable_changes(self):
|
||||
result = VirtualAddressReportableChanges(self.changes.to_return())
|
||||
return result
|
||||
|
||||
def _set_changed_options(self):
|
||||
changed = {}
|
||||
for key in VirtualAddressParameters.returnables:
|
||||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = VirtualAddressUsableChanges(changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
updatables = VirtualAddressParameters.updatables
|
||||
changed = dict()
|
||||
for k in updatables:
|
||||
change = diff.compare(k)
|
||||
if change is None:
|
||||
continue
|
||||
else:
|
||||
if isinstance(change, dict):
|
||||
changed.update(change)
|
||||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = VirtualAddressUsableChanges(changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
def read_current_from_device(self):
|
||||
result = self.client.api.tm.ltm.virtual_address_s.virtual_address.load(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
)
|
||||
result = VirtualAddressParameters(result.attrs)
|
||||
return result
|
||||
|
||||
def update_on_device(self):
|
||||
params = self.want.api_params()
|
||||
resource = self.client.api.tm.ltm.virtual_address_s.virtual_address.load(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
)
|
||||
resource.modify(**params)
|
||||
|
||||
def exists(self):
|
||||
result = self.client.api.tm.ltm.virtual_address_s.virtual_address.exists(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
state=dict(
|
||||
default='present',
|
||||
choices=['present', 'absent', 'disabled', 'enabled']
|
||||
|
@ -1798,54 +1612,45 @@ class ArgumentSpec(object):
|
|||
pool=dict(),
|
||||
description=dict(),
|
||||
snat=dict(),
|
||||
route_advertisement_state=dict(
|
||||
choices=['enabled', 'disabled']
|
||||
),
|
||||
default_persistence_profile=dict(),
|
||||
fallback_persistence_profile=dict(),
|
||||
source=dict(),
|
||||
metadata=dict(type='raw')
|
||||
metadata=dict(type='raw'),
|
||||
partition=dict(
|
||||
default='Common',
|
||||
fallback=(env_fallback, ['F5_PARTITION'])
|
||||
)
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
self.mutually_exclusive = [
|
||||
['enabled_vlans', 'disabled_vlans']
|
||||
]
|
||||
|
||||
|
||||
def cleanup_tokens(client):
|
||||
try:
|
||||
resource = client.api.shared.authz.tokens_s.token.load(
|
||||
name=client.api.icrs.token
|
||||
)
|
||||
resource.delete()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
if not HAS_NETADDR:
|
||||
raise F5ModuleError("The python netaddr module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name,
|
||||
mutually_exclusive=spec.mutually_exclusive
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
if not HAS_NETADDR:
|
||||
module.fail_json(msg="The python netaddr module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
cleanup_tokens(client)
|
||||
client.module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as ex:
|
||||
cleanup_tokens(client)
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(ex))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -55,13 +55,46 @@ options:
|
|||
- 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.
|
||||
mtu:
|
||||
description:
|
||||
- Specifies the maximum transmission unit (MTU) for traffic on this VLAN.
|
||||
When creating a new VLAN, if this parameter is not specified, the default
|
||||
value used will be C(1500).
|
||||
- This number must be between 576 to 9198.
|
||||
version_added: 2.5
|
||||
cmp_hash:
|
||||
description:
|
||||
- Specifies how the traffic on the VLAN will be disaggregated. The value
|
||||
selected determines the traffic disaggregation method. You can choose to
|
||||
disaggregate traffic based on C(source-address) (the source IP address),
|
||||
C(destination-address) (destination IP address), or C(default), which
|
||||
specifies that the default CMP hash uses L4 ports.
|
||||
- When creating a new VLAN, if this parameter is not specified, the default
|
||||
of C(default) is used.
|
||||
version_added: 2.5
|
||||
dag_tunnel:
|
||||
description:
|
||||
- Specifies how the disaggregator (DAG) distributes received tunnel-encapsulated
|
||||
packets to TMM instances. Select C(inner) to distribute packets based on information
|
||||
in inner headers. Select C(outer) to distribute packets based on information in
|
||||
outer headers without inspecting inner headers.
|
||||
- When creating a new VLAN, if this parameter is not specified, the default
|
||||
of C(outer) is used.
|
||||
- This parameter is not supported on Virtual Editions of BIG-IP.
|
||||
version_added: 2.5
|
||||
dag_round_robin:
|
||||
description:
|
||||
- Specifies whether some of the stateless traffic on the VLAN should be
|
||||
disaggregated in a round-robin order instead of using a static hash. The
|
||||
stateless traffic includes non-IP L2 traffic, ICMP, some UDP protocols,
|
||||
and so on.
|
||||
- When creating a new VLAN, if this parameter is not specified, the default
|
||||
of (no) is used.
|
||||
version_added: 2.5
|
||||
choices: [yes, no]
|
||||
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)
|
||||
- Wojciech Wypior (@wojtek0806)
|
||||
|
@ -114,134 +147,92 @@ EXAMPLES = r'''
|
|||
|
||||
RETURN = r'''
|
||||
description:
|
||||
description: The description set on the VLAN
|
||||
returned: changed
|
||||
type: string
|
||||
sample: foo VLAN
|
||||
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
|
||||
description: Interfaces that the VLAN is assigned to.
|
||||
returned: changed
|
||||
type: list
|
||||
sample: ['1.1','1.2']
|
||||
partition:
|
||||
description: The partition that the VLAN was created on
|
||||
returned: changed
|
||||
type: string
|
||||
sample: Common
|
||||
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
|
||||
description: The ID of the VLAN.
|
||||
returned: changed
|
||||
type: int
|
||||
sample: 2345
|
||||
cmp_hash:
|
||||
description: New traffic disaggregation method.
|
||||
returned: changed
|
||||
type: string
|
||||
sample: source-address
|
||||
dag_tunnel:
|
||||
description: The new DAG tunnel setting.
|
||||
returned: changed
|
||||
type: string
|
||||
sample: outer
|
||||
'''
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.basic import env_fallback
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
if params:
|
||||
self.update(params=params)
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
api_map = {
|
||||
'cmpHash': 'cmp_hash',
|
||||
'dagTunnel': 'dag_tunnel',
|
||||
'dagRoundRobin': 'dag_round_robin'
|
||||
}
|
||||
|
||||
updatables = [
|
||||
'tagged_interfaces', 'untagged_interfaces', 'tag',
|
||||
'description'
|
||||
'description', 'mtu', 'cmp_hash', 'dag_tunnel',
|
||||
'dag_round_robin'
|
||||
]
|
||||
|
||||
returnables = [
|
||||
'description', 'partition', 'name', 'tag', 'interfaces',
|
||||
'tagged_interfaces', 'untagged_interfaces'
|
||||
'description', 'partition', 'tag', 'interfaces',
|
||||
'tagged_interfaces', 'untagged_interfaces', 'mtu',
|
||||
'cmp_hash', 'dag_tunnel', 'dag_round_robin'
|
||||
]
|
||||
|
||||
api_attributes = [
|
||||
'description', 'interfaces', 'partition', 'name', 'tag'
|
||||
'description', 'interfaces', 'tag', 'mtu', 'cmpHash',
|
||||
'dagTunnel', 'dagRoundRobin'
|
||||
]
|
||||
api_map = {}
|
||||
|
||||
@property
|
||||
def interfaces(self):
|
||||
tagged = self._values['tagged_interfaces']
|
||||
untagged = self._values['untagged_interfaces']
|
||||
if tagged:
|
||||
return [dict(name=x, tagged=True) for x in tagged]
|
||||
if untagged:
|
||||
return [dict(name=x, untagged=True) for x in untagged]
|
||||
|
||||
@property
|
||||
def tagged_interfaces(self):
|
||||
value = self._values['tagged_interfaces']
|
||||
if value is None:
|
||||
return None
|
||||
ifcs = self._parse_return_ifcs()
|
||||
for ifc in value:
|
||||
if ifc not in ifcs:
|
||||
err = 'The specified interface "%s" was not found' % ifc
|
||||
raise F5ModuleError(err)
|
||||
return value
|
||||
|
||||
@property
|
||||
def untagged_interfaces(self):
|
||||
value = self._values['untagged_interfaces']
|
||||
if value is None:
|
||||
return None
|
||||
ifcs = self._parse_return_ifcs()
|
||||
for ifc in value:
|
||||
if ifc not in ifcs:
|
||||
err = 'The specified interface "%s" was not found' % ifc
|
||||
raise F5ModuleError(err)
|
||||
return value
|
||||
|
||||
def _get_interfaces_from_device(self):
|
||||
lst = self.client.api.tm.net.interfaces.get_collection()
|
||||
return lst
|
||||
|
||||
def _parse_return_ifcs(self):
|
||||
ifclst = self._get_interfaces_from_device()
|
||||
ifcs = [str(x.name) for x in ifclst]
|
||||
if not ifcs:
|
||||
err = 'No interfaces were found'
|
||||
raise F5ModuleError(err)
|
||||
return ifcs
|
||||
|
||||
def to_return(self):
|
||||
result = {}
|
||||
|
@ -250,26 +241,202 @@ class Parameters(AnsibleF5Parameters):
|
|||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
def api_params(self):
|
||||
|
||||
class ApiParameters(Parameters):
|
||||
@property
|
||||
def tagged_interfaces(self):
|
||||
if self._values['interfaces'] is None:
|
||||
return None
|
||||
result = [str(x.name) for x in self._values['interfaces'] if x.tagged is True]
|
||||
result = sorted(result)
|
||||
return result
|
||||
|
||||
@property
|
||||
def untagged_interfaces(self):
|
||||
if self._values['interfaces'] is None:
|
||||
return None
|
||||
result = [str(x.name) for x in self._values['interfaces'] if x.untagged is True]
|
||||
result = sorted(result)
|
||||
return result
|
||||
|
||||
|
||||
class ModuleParameters(Parameters):
|
||||
@property
|
||||
def untagged_interfaces(self):
|
||||
if self._values['untagged_interfaces'] is None:
|
||||
return None
|
||||
if self._values['untagged_interfaces'] is None:
|
||||
return None
|
||||
if len(self._values['untagged_interfaces']) == 1 and self._values['untagged_interfaces'][0] == '':
|
||||
return ''
|
||||
result = sorted([str(x) for x in self._values['untagged_interfaces']])
|
||||
return result
|
||||
|
||||
@property
|
||||
def tagged_interfaces(self):
|
||||
if self._values['tagged_interfaces'] is None:
|
||||
return None
|
||||
if self._values['tagged_interfaces'] is None:
|
||||
return None
|
||||
if len(self._values['tagged_interfaces']) == 1 and self._values['tagged_interfaces'][0] == '':
|
||||
return ''
|
||||
result = sorted([str(x) for x in self._values['tagged_interfaces']])
|
||||
return result
|
||||
|
||||
@property
|
||||
def mtu(self):
|
||||
if self._values['mtu'] is None:
|
||||
return None
|
||||
if int(self._values['mtu']) < 576 or int(self._values['mtu']) > 9198:
|
||||
raise F5ModuleError(
|
||||
"The mtu value must be between 576 - 9198"
|
||||
)
|
||||
return int(self._values['mtu'])
|
||||
|
||||
@property
|
||||
def cmp_hash(self):
|
||||
if self._values['cmp_hash'] is None:
|
||||
return None
|
||||
if self._values['cmp_hash'] in ['source-address', 'src', 'src-ip', 'source']:
|
||||
return 'src-ip'
|
||||
if self._values['cmp_hash'] in ['destination-address', 'dest', 'dst-ip', 'destination', 'dst']:
|
||||
return 'dst-ip'
|
||||
else:
|
||||
return 'default'
|
||||
|
||||
@property
|
||||
def dag_round_robin(self):
|
||||
if self._values['dag_round_robin'] is None:
|
||||
return None
|
||||
if self._values['dag_round_robin'] is True:
|
||||
return 'enabled'
|
||||
else:
|
||||
return 'disabled'
|
||||
|
||||
|
||||
class Changes(Parameters):
|
||||
def to_return(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(
|
||||
self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
try:
|
||||
for returnable in self.returnables:
|
||||
result[returnable] = getattr(self, returnable)
|
||||
result = self._filter_params(result)
|
||||
except Exception:
|
||||
pass
|
||||
return result
|
||||
|
||||
|
||||
class UsableChanges(Changes):
|
||||
pass
|
||||
|
||||
|
||||
class ReportableChanges(Changes):
|
||||
@property
|
||||
def tagged_interfaces(self):
|
||||
if self._values['interfaces'] is None:
|
||||
return None
|
||||
result = [str(x['name']) for x in self._values['interfaces'] if 'tagged' in x and x['tagged'] is True]
|
||||
result = sorted(result)
|
||||
return result
|
||||
|
||||
@property
|
||||
def untagged_interfaces(self):
|
||||
if self._values['interfaces'] is None:
|
||||
return None
|
||||
result = [str(x['name']) for x in self._values['interfaces'] if 'untagged' in x and x['untagged'] is True]
|
||||
result = sorted(result)
|
||||
return result
|
||||
|
||||
|
||||
class Difference(object):
|
||||
def __init__(self, want, have=None):
|
||||
self.want = want
|
||||
self.have = have
|
||||
|
||||
def compare(self, param):
|
||||
try:
|
||||
result = getattr(self, param)
|
||||
return result
|
||||
except AttributeError:
|
||||
return self.__default(param)
|
||||
|
||||
def __default(self, param):
|
||||
attr1 = getattr(self.want, param)
|
||||
try:
|
||||
attr2 = getattr(self.have, param)
|
||||
if attr1 != attr2:
|
||||
return attr1
|
||||
except AttributeError:
|
||||
return attr1
|
||||
|
||||
@property
|
||||
def untagged_interfaces(self):
|
||||
result = []
|
||||
if self.want.untagged_interfaces is None:
|
||||
return None
|
||||
elif self.want.untagged_interfaces == '' and self.have.untagged_interfaces is None:
|
||||
return None
|
||||
elif self.want.untagged_interfaces == '' and len(self.have.untagged_interfaces) > 0:
|
||||
pass
|
||||
elif not self.have.untagged_interfaces:
|
||||
result = dict(
|
||||
interfaces=[dict(name=x, untagged=True) for x in self.want.untagged_interfaces]
|
||||
)
|
||||
elif set(self.want.untagged_interfaces) != set(self.have.untagged_interfaces):
|
||||
result = dict(
|
||||
interfaces=[dict(name=x, untagged=True) for x in self.want.untagged_interfaces]
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return result
|
||||
|
||||
@property
|
||||
def tagged_interfaces(self):
|
||||
result = []
|
||||
if self.want.tagged_interfaces is None:
|
||||
return None
|
||||
elif self.want.tagged_interfaces == '' and self.have.tagged_interfaces is None:
|
||||
return None
|
||||
elif self.want.tagged_interfaces == '' and len(self.have.tagged_interfaces) > 0:
|
||||
pass
|
||||
elif not self.have.tagged_interfaces:
|
||||
result = dict(
|
||||
interfaces=[dict(name=x, tagged=True) for x in self.want.tagged_interfaces]
|
||||
)
|
||||
elif set(self.want.tagged_interfaces) != set(self.have.tagged_interfaces):
|
||||
result = dict(
|
||||
interfaces=[dict(name=x, tagged=True) for x in self.want.tagged_interfaces]
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return result
|
||||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.have = None
|
||||
self.want = Parameters()
|
||||
self.want.client = self.client
|
||||
self.want.update(self.client.module.params)
|
||||
self.changes = Parameters()
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.want = ModuleParameters(params=self.module.params)
|
||||
self.have = ApiParameters()
|
||||
self.changes = UsableChanges()
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
updatables = Parameters.updatables
|
||||
changed = dict()
|
||||
for k in updatables:
|
||||
change = diff.compare(k)
|
||||
if change is None:
|
||||
continue
|
||||
else:
|
||||
if isinstance(change, dict):
|
||||
changed.update(change)
|
||||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = UsableChanges(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
def exec_module(self):
|
||||
changed = False
|
||||
|
@ -284,39 +451,20 @@ class ModuleManager(object):
|
|||
except iControlUnexpectedHTTPError as e:
|
||||
raise F5ModuleError(str(e))
|
||||
|
||||
changes = self.changes.to_return()
|
||||
reportable = ReportableChanges(params=self.changes.to_return())
|
||||
changes = reportable.to_return()
|
||||
result.update(**changes)
|
||||
result.update(dict(changed=changed))
|
||||
self._announce_deprecations(result)
|
||||
return result
|
||||
|
||||
def _set_changed_options(self):
|
||||
changed = {}
|
||||
for key in Parameters.returnables:
|
||||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = Parameters(changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
changed = {}
|
||||
for key in Parameters.updatables:
|
||||
if getattr(self.want, key) is not None:
|
||||
attr1 = getattr(self.want, key)
|
||||
attr2 = getattr(self.have, key)
|
||||
if attr1 != attr2:
|
||||
changed[key] = attr1
|
||||
if changed:
|
||||
self.changes = Parameters(changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _have_interfaces(self, ifcs):
|
||||
untagged = [str(x.name) for x in ifcs if hasattr(x, 'untagged')]
|
||||
tagged = [str(x.name) for x in ifcs if hasattr(x, 'tagged')]
|
||||
if untagged:
|
||||
self.have.update({'untagged_interfaces': untagged})
|
||||
if tagged:
|
||||
self.have.update({'tagged_interfaces': tagged})
|
||||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
||||
def present(self):
|
||||
if self.exists():
|
||||
|
@ -336,18 +484,16 @@ class ModuleManager(object):
|
|||
return False
|
||||
|
||||
def update(self):
|
||||
self.have, ifcs = self.read_current_from_device()
|
||||
if ifcs:
|
||||
self._have_interfaces(ifcs)
|
||||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
|
@ -355,49 +501,59 @@ class ModuleManager(object):
|
|||
return True
|
||||
|
||||
def create(self):
|
||||
self._set_changed_options()
|
||||
if self.client.check_mode:
|
||||
self.have = ApiParameters()
|
||||
if self.want.mtu is None:
|
||||
self.want.update({'mtu': 1500})
|
||||
self._update_changed_options()
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
return True
|
||||
|
||||
def create_on_device(self):
|
||||
params = self.want.api_params()
|
||||
self.client.api.tm.net.vlans.vlan.create(**params)
|
||||
params = self.changes.api_params()
|
||||
self.client.api.tm.net.vlans.vlan.create(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition,
|
||||
**params
|
||||
)
|
||||
|
||||
def update_on_device(self):
|
||||
params = self.want.api_params()
|
||||
result = self.client.api.tm.net.vlans.vlan.load(
|
||||
name=self.want.name, partition=self.want.partition
|
||||
params = self.changes.api_params()
|
||||
resource = self.client.api.tm.net.vlans.vlan.load(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
)
|
||||
result.modify(**params)
|
||||
resource.modify(**params)
|
||||
|
||||
def exists(self):
|
||||
return self.client.api.tm.net.vlans.vlan.exists(
|
||||
name=self.want.name, partition=self.want.partition
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
)
|
||||
|
||||
def remove_from_device(self):
|
||||
result = self.client.api.tm.net.vlans.vlan.load(
|
||||
name=self.want.name, partition=self.want.partition
|
||||
resource = self.client.api.tm.net.vlans.vlan.load(
|
||||
name=self.want.name,
|
||||
partition=self.want.partition
|
||||
)
|
||||
if result:
|
||||
result.delete()
|
||||
if resource:
|
||||
resource.delete()
|
||||
|
||||
def read_current_from_device(self):
|
||||
tmp_res = self.client.api.tm.net.vlans.vlan.load(
|
||||
resource = self.client.api.tm.net.vlans.vlan.load(
|
||||
name=self.want.name, partition=self.want.partition
|
||||
)
|
||||
ifcs = tmp_res.interfaces_s.get_collection()
|
||||
|
||||
result = tmp_res.attrs
|
||||
return Parameters(result), ifcs
|
||||
interfaces = resource.interfaces_s.get_collection()
|
||||
result = resource.attrs
|
||||
result['interfaces'] = interfaces
|
||||
return ApiParameters(params=result)
|
||||
|
||||
|
||||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
name=dict(
|
||||
required=True,
|
||||
),
|
||||
|
@ -412,32 +568,56 @@ class ArgumentSpec(object):
|
|||
description=dict(),
|
||||
tag=dict(
|
||||
type='int'
|
||||
),
|
||||
mtu=dict(type='int'),
|
||||
cmp_hash=dict(
|
||||
choices=[
|
||||
'default',
|
||||
'destination-address', 'dest', 'dst-ip', 'destination', 'dst',
|
||||
'source-address', 'src', 'src-ip', 'source'
|
||||
]
|
||||
),
|
||||
dag_tunnel=dict(
|
||||
choices=['inner', 'outer']
|
||||
),
|
||||
dag_round_robin=dict(type='bool'),
|
||||
state=dict(
|
||||
default='present',
|
||||
choices=['present', 'absent']
|
||||
),
|
||||
partition=dict(
|
||||
default='Common',
|
||||
fallback=(env_fallback, ['F5_PARTITION'])
|
||||
)
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
self.mutually_exclusive = [
|
||||
['tagged_interfaces', 'untagged_interfaces']
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name,
|
||||
mutually_exclusive=[
|
||||
['tagged_interfaces', 'untagged_interfaces']
|
||||
]
|
||||
mutually_exclusive=spec.mutually_exclusive
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
client.module.exit_json(**results)
|
||||
cleanup_tokens(client)
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
client.module.fail_json(msg=str(e))
|
||||
cleanup_tokens(client)
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -40,11 +40,6 @@ options:
|
|||
msg:
|
||||
description:
|
||||
- This overrides the normal error message from a failure to meet the required conditions.
|
||||
notes:
|
||||
- Requires the f5-sdk Python package on the host. This is as easy as pip
|
||||
install f5-sdk.
|
||||
requirements:
|
||||
- f5-sdk >= 2.2.3
|
||||
extends_documentation_fragment: f5
|
||||
author:
|
||||
- Tim Rupp (@caphrim007)
|
||||
|
@ -84,133 +79,50 @@ import signal
|
|||
import time
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.f5_utils import F5_COMMON_ARGS
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
from f5.bigip import ManagementRoot as BigIpMgmt
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigip import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigip import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigip import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
def hard_timeout(client, want, start):
|
||||
def hard_timeout(module, want, start):
|
||||
elapsed = datetime.datetime.utcnow() - start
|
||||
client.module.fail_json(
|
||||
module.fail_json(
|
||||
want.msg or "Timeout when waiting for BIG-IP", elapsed=elapsed.seconds
|
||||
)
|
||||
|
||||
|
||||
class AnsibleF5ClientStub(AnsibleF5Client):
|
||||
"""Interim class to disconnect Params from connection
|
||||
|
||||
This module is an interim class that was made to separate the Ansible Module
|
||||
Parameters from the connection to BIG-IP.
|
||||
|
||||
Since this module needs to be able to control the connection process, the default
|
||||
class is not appropriate. Therefore, we overload it and re-define out the
|
||||
connection related work to a separate method.
|
||||
|
||||
This class should serve as a reason to break apart this work itself into separate
|
||||
classes in module_utils. There will be on-going work to do this and, when done,
|
||||
the result will replace this work here.
|
||||
|
||||
"""
|
||||
def __init__(self, argument_spec=None, supports_check_mode=False,
|
||||
mutually_exclusive=None, required_together=None,
|
||||
required_if=None, required_one_of=None, add_file_common_args=False,
|
||||
f5_product_name='bigip'):
|
||||
self.f5_product_name = f5_product_name
|
||||
|
||||
merged_arg_spec = dict()
|
||||
merged_arg_spec.update(F5_COMMON_ARGS)
|
||||
if argument_spec:
|
||||
merged_arg_spec.update(argument_spec)
|
||||
self.arg_spec = merged_arg_spec
|
||||
|
||||
mutually_exclusive_params = []
|
||||
if mutually_exclusive:
|
||||
mutually_exclusive_params += mutually_exclusive
|
||||
|
||||
required_together_params = []
|
||||
if required_together:
|
||||
required_together_params += required_together
|
||||
|
||||
self.module = AnsibleModule(
|
||||
argument_spec=merged_arg_spec,
|
||||
supports_check_mode=supports_check_mode,
|
||||
mutually_exclusive=mutually_exclusive_params,
|
||||
required_together=required_together_params,
|
||||
required_if=required_if,
|
||||
required_one_of=required_one_of,
|
||||
add_file_common_args=add_file_common_args
|
||||
)
|
||||
|
||||
self.check_mode = self.module.check_mode
|
||||
self._connect_params = self._get_connect_params()
|
||||
|
||||
def connect(self):
|
||||
try:
|
||||
if 'transport' not in self.module.params or self.module.params['transport'] != 'cli':
|
||||
self.api = self._get_mgmt_root(
|
||||
self.f5_product_name, **self._connect_params
|
||||
)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def _get_mgmt_root(self, type, **kwargs):
|
||||
if type == 'bigip':
|
||||
result = BigIpMgmt(
|
||||
kwargs['server'],
|
||||
kwargs['user'],
|
||||
kwargs['password'],
|
||||
port=kwargs['server_port'],
|
||||
timeout=1,
|
||||
token='tmos'
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
returnables = [
|
||||
'elapsed'
|
||||
]
|
||||
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
if params:
|
||||
self.update(params=params)
|
||||
self._values['__warnings'] = []
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
def to_return(self):
|
||||
result = {}
|
||||
try:
|
||||
|
@ -245,10 +157,11 @@ class Changes(Parameters):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.have = None
|
||||
self.want = Parameters(self.client.module.params)
|
||||
self.want = Parameters(params=self.module.params)
|
||||
self.changes = Parameters()
|
||||
|
||||
def exec_module(self):
|
||||
|
@ -268,7 +181,7 @@ class ModuleManager(object):
|
|||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.client.module.deprecate(
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
@ -276,7 +189,7 @@ class ModuleManager(object):
|
|||
def execute(self):
|
||||
signal.signal(
|
||||
signal.SIGALRM,
|
||||
lambda sig, frame: hard_timeout(self.client, self.want, start)
|
||||
lambda sig, frame: hard_timeout(self.module, self.want, start)
|
||||
)
|
||||
|
||||
# setup handler before scheduling signal, to eliminate a race
|
||||
|
@ -291,8 +204,8 @@ class ModuleManager(object):
|
|||
try:
|
||||
# The first test verifies that the REST API is available; this is done
|
||||
# by repeatedly trying to login to it.
|
||||
connected = self._connect_to_device()
|
||||
if not connected:
|
||||
self.client = F5Client(**self.module.params)
|
||||
if not self.client:
|
||||
continue
|
||||
|
||||
if self._device_is_rebooting():
|
||||
|
@ -333,17 +246,13 @@ class ModuleManager(object):
|
|||
continue
|
||||
else:
|
||||
elapsed = datetime.datetime.utcnow() - start
|
||||
self.client.module.fail_json(
|
||||
self.module.fail_json(
|
||||
msg=self.want.msg or "Timeout when waiting for BIG-IP", elapsed=elapsed.seconds
|
||||
)
|
||||
elapsed = datetime.datetime.utcnow() - start
|
||||
self.changes.update({'elapsed': elapsed.seconds})
|
||||
return False
|
||||
|
||||
def _connect_to_device(self):
|
||||
result = self.client.connect()
|
||||
return result
|
||||
|
||||
def _device_is_rebooting(self):
|
||||
output = self.client.api.tm.util.bash.exec_cmd(
|
||||
'run',
|
||||
|
@ -386,45 +295,33 @@ class ModuleManager(object):
|
|||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
timeout=dict(default=7200, type='int'),
|
||||
delay=dict(default=0, type='int'),
|
||||
sleep=dict(default=1, type='int'),
|
||||
msg=dict()
|
||||
)
|
||||
self.f5_product_name = 'bigip'
|
||||
|
||||
|
||||
def cleanup_tokens(client):
|
||||
try:
|
||||
resource = client.api.shared.authz.tokens_s.token.load(
|
||||
name=client.api.icrs.token
|
||||
)
|
||||
resource.delete()
|
||||
except Exception:
|
||||
pass
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5ClientStub(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name,
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
mm = ModuleManager(module=module)
|
||||
results = mm.exec_module()
|
||||
cleanup_tokens(client)
|
||||
client.module.exit_json(**results)
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
cleanup_tokens(client)
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -89,17 +89,37 @@ description:
|
|||
|
||||
import time
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigiq import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigiq import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigiq import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigiq import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
|
@ -119,36 +139,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
'description'
|
||||
]
|
||||
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
self._values['__warnings'] = []
|
||||
if params:
|
||||
self.update(params=params)
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
def to_return(self):
|
||||
result = {}
|
||||
try:
|
||||
|
@ -159,16 +149,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
pass
|
||||
return result
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if self.api_map is not None and api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
|
||||
class ApiParameters(Parameters):
|
||||
pass
|
||||
|
@ -230,10 +210,10 @@ class Difference(object):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.want = ModuleParameters(params=self.client.module.params)
|
||||
self.want.update(dict(client=client))
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.want = ModuleParameters(client=self.client, params=self.module.params)
|
||||
self.have = ApiParameters()
|
||||
self.changes = UsableChanges()
|
||||
|
||||
|
@ -243,7 +223,7 @@ class ModuleManager(object):
|
|||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = UsableChanges(changed)
|
||||
self.changes = UsableChanges(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
|
@ -259,7 +239,7 @@ class ModuleManager(object):
|
|||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = UsableChanges(changed)
|
||||
self.changes = UsableChanges(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -282,7 +262,7 @@ class ModuleManager(object):
|
|||
except iControlUnexpectedHTTPError as e:
|
||||
raise F5ModuleError(str(e))
|
||||
|
||||
reportable = ReportableChanges(self.changes.to_return())
|
||||
reportable = ReportableChanges(params=self.changes.to_return())
|
||||
changes = reportable.to_return()
|
||||
result.update(**changes)
|
||||
result.update(dict(changed=changed))
|
||||
|
@ -292,7 +272,7 @@ class ModuleManager(object):
|
|||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.client.module.deprecate(
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
@ -316,13 +296,13 @@ class ModuleManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
|
@ -331,7 +311,7 @@ class ModuleManager(object):
|
|||
|
||||
def create(self):
|
||||
self._set_changed_options()
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
if self.want.accept_eula is False:
|
||||
raise F5ModuleError(
|
||||
|
@ -401,42 +381,48 @@ class ModuleManager(object):
|
|||
if resource is None:
|
||||
return False
|
||||
result = resource.attrs
|
||||
return ApiParameters(result)
|
||||
return ApiParameters(params=result)
|
||||
|
||||
|
||||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
regkey_pool=dict(required=True),
|
||||
license_key=dict(required=True, no_log=True),
|
||||
description=dict(),
|
||||
accept_eula=dict(type='bool')
|
||||
accept_eula=dict(type='bool'),
|
||||
state=dict(
|
||||
default='present',
|
||||
choices=['present', 'absent']
|
||||
),
|
||||
)
|
||||
self.f5_product_name = 'bigiq'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
self.required_if = [
|
||||
['state', 'present', ['accept_eula']]
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
required_if=spec.required_if
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
client.module.exit_json(**results)
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -69,18 +69,37 @@ description:
|
|||
sample: My description
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Client
|
||||
from ansible.module_utils.f5_utils import AnsibleF5Parameters
|
||||
from ansible.module_utils.f5_utils import HAS_F5SDK
|
||||
from ansible.module_utils.f5_utils import F5ModuleError
|
||||
from ansible.module_utils.six import iteritems
|
||||
from collections import defaultdict
|
||||
HAS_DEVEL_IMPORTS = False
|
||||
|
||||
try:
|
||||
from ansible.module_utils.f5_utils import iControlUnexpectedHTTPError
|
||||
# Sideband repository used for dev
|
||||
from library.module_utils.network.f5.bigiq import HAS_F5SDK
|
||||
from library.module_utils.network.f5.bigiq import F5Client
|
||||
from library.module_utils.network.f5.common import F5ModuleError
|
||||
from library.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from library.module_utils.network.f5.common import cleanup_tokens
|
||||
from library.module_utils.network.f5.common import fqdn_name
|
||||
from library.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
HAS_DEVEL_IMPORTS = True
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
# Upstream Ansible
|
||||
from ansible.module_utils.network.f5.bigiq import HAS_F5SDK
|
||||
from ansible.module_utils.network.f5.bigiq import F5Client
|
||||
from ansible.module_utils.network.f5.common import F5ModuleError
|
||||
from ansible.module_utils.network.f5.common import AnsibleF5Parameters
|
||||
from ansible.module_utils.network.f5.common import cleanup_tokens
|
||||
from ansible.module_utils.network.f5.common import fqdn_name
|
||||
from ansible.module_utils.network.f5.common import f5_argument_spec
|
||||
try:
|
||||
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError
|
||||
except ImportError:
|
||||
HAS_F5SDK = False
|
||||
|
||||
|
||||
class Parameters(AnsibleF5Parameters):
|
||||
|
@ -100,36 +119,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
'description'
|
||||
]
|
||||
|
||||
def __init__(self, params=None):
|
||||
self._values = defaultdict(lambda: None)
|
||||
self._values['__warnings'] = []
|
||||
if params:
|
||||
self.update(params=params)
|
||||
|
||||
def update(self, params=None):
|
||||
if params:
|
||||
for k, v in iteritems(params):
|
||||
if self.api_map is not None and k in self.api_map:
|
||||
map_key = self.api_map[k]
|
||||
else:
|
||||
map_key = k
|
||||
|
||||
# Handle weird API parameters like `dns.proxy.__iter__` by
|
||||
# using a map provided by the module developer
|
||||
class_attr = getattr(type(self), map_key, None)
|
||||
if isinstance(class_attr, property):
|
||||
# There is a mapped value for the api_map key
|
||||
if class_attr.fset is None:
|
||||
# If the mapped value does not have
|
||||
# an associated setter
|
||||
self._values[map_key] = v
|
||||
else:
|
||||
# The mapped value has a setter
|
||||
setattr(self, map_key, v)
|
||||
else:
|
||||
# If the mapped value is not a @property
|
||||
self._values[map_key] = v
|
||||
|
||||
def to_return(self):
|
||||
result = {}
|
||||
try:
|
||||
|
@ -140,16 +129,6 @@ class Parameters(AnsibleF5Parameters):
|
|||
pass
|
||||
return result
|
||||
|
||||
def api_params(self):
|
||||
result = {}
|
||||
for api_attribute in self.api_attributes:
|
||||
if self.api_map is not None and api_attribute in self.api_map:
|
||||
result[api_attribute] = getattr(self, self.api_map[api_attribute])
|
||||
else:
|
||||
result[api_attribute] = getattr(self, api_attribute)
|
||||
result = self._filter_params(result)
|
||||
return result
|
||||
|
||||
|
||||
class ModuleParameters(Parameters):
|
||||
@property
|
||||
|
@ -214,10 +193,10 @@ class Difference(object):
|
|||
|
||||
|
||||
class ModuleManager(object):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.want = ModuleParameters(self.client.module.params)
|
||||
self.want.update({'client': client})
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.module = kwargs.get('module', None)
|
||||
self.client = kwargs.get('client', None)
|
||||
self.want = ModuleParameters(client=self.client, params=self.module.params)
|
||||
self.have = ApiParameters()
|
||||
self.changes = UsableChanges()
|
||||
|
||||
|
@ -227,7 +206,7 @@ class ModuleManager(object):
|
|||
if getattr(self.want, key) is not None:
|
||||
changed[key] = getattr(self.want, key)
|
||||
if changed:
|
||||
self.changes = UsableChanges(changed)
|
||||
self.changes = UsableChanges(params=changed)
|
||||
|
||||
def _update_changed_options(self):
|
||||
diff = Difference(self.want, self.have)
|
||||
|
@ -243,7 +222,7 @@ class ModuleManager(object):
|
|||
else:
|
||||
changed[k] = change
|
||||
if changed:
|
||||
self.changes = Changes(changed)
|
||||
self.changes = Changes(params=changed)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
@ -266,7 +245,7 @@ class ModuleManager(object):
|
|||
except iControlUnexpectedHTTPError as e:
|
||||
raise F5ModuleError(str(e))
|
||||
|
||||
reportable = ReportableChanges(self.changes.to_return())
|
||||
reportable = ReportableChanges(params=self.changes.to_return())
|
||||
changes = reportable.to_return()
|
||||
result.update(**changes)
|
||||
result.update(dict(changed=changed))
|
||||
|
@ -276,7 +255,7 @@ class ModuleManager(object):
|
|||
def _announce_deprecations(self, result):
|
||||
warnings = result.pop('__warnings', [])
|
||||
for warning in warnings:
|
||||
self.client.module.deprecate(
|
||||
self.module.deprecate(
|
||||
msg=warning['msg'],
|
||||
version=warning['version']
|
||||
)
|
||||
|
@ -297,13 +276,13 @@ class ModuleManager(object):
|
|||
self.have = self.read_current_from_device()
|
||||
if not self.should_update():
|
||||
return False
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.update_on_device()
|
||||
return True
|
||||
|
||||
def remove(self):
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.remove_from_device()
|
||||
if self.exists():
|
||||
|
@ -312,7 +291,7 @@ class ModuleManager(object):
|
|||
|
||||
def create(self):
|
||||
self._set_changed_options()
|
||||
if self.client.check_mode:
|
||||
if self.module.check_mode:
|
||||
return True
|
||||
self.create_on_device()
|
||||
return True
|
||||
|
@ -348,13 +327,13 @@ class ModuleManager(object):
|
|||
id=self.want.uuid
|
||||
)
|
||||
result = resource.attrs
|
||||
return ApiParameters(result)
|
||||
return ApiParameters(params=result)
|
||||
|
||||
|
||||
class ArgumentSpec(object):
|
||||
def __init__(self):
|
||||
self.supports_check_mode = True
|
||||
self.argument_spec = dict(
|
||||
argument_spec = dict(
|
||||
name=dict(required=True),
|
||||
description=dict(),
|
||||
state=dict(
|
||||
|
@ -362,27 +341,28 @@ class ArgumentSpec(object):
|
|||
choices=['absent', 'present']
|
||||
)
|
||||
)
|
||||
self.f5_product_name = 'bigiq'
|
||||
self.argument_spec = {}
|
||||
self.argument_spec.update(f5_argument_spec)
|
||||
self.argument_spec.update(argument_spec)
|
||||
|
||||
|
||||
def main():
|
||||
if not HAS_F5SDK:
|
||||
raise F5ModuleError("The python f5-sdk module is required")
|
||||
|
||||
spec = ArgumentSpec()
|
||||
|
||||
client = AnsibleF5Client(
|
||||
module = AnsibleModule(
|
||||
argument_spec=spec.argument_spec,
|
||||
supports_check_mode=spec.supports_check_mode,
|
||||
f5_product_name=spec.f5_product_name
|
||||
supports_check_mode=spec.supports_check_mode
|
||||
)
|
||||
if not HAS_F5SDK:
|
||||
module.fail_json(msg="The python f5-sdk module is required")
|
||||
|
||||
try:
|
||||
mm = ModuleManager(client)
|
||||
client = F5Client(**module.params)
|
||||
mm = ModuleManager(module=module, client=client)
|
||||
results = mm.exec_module()
|
||||
client.module.exit_json(**results)
|
||||
module.exit_json(**results)
|
||||
except F5ModuleError as e:
|
||||
client.module.fail_json(msg=str(e))
|
||||
module.fail_json(msg=str(e))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue