1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-04-18 09:51:41 +00:00

Initial commit

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

View file

@ -0,0 +1,288 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_cs_action
short_description: Manage content switching actions
description:
- Manage content switching actions
- This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
name:
description:
- >-
Name for the content switching action. Must begin with an ASCII alphanumeric or underscore C(_)
character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon
C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Can be changed after the content
switching action is created.
targetlbvserver:
description:
- "Name of the load balancing virtual server to which the content is switched."
targetvserver:
description:
- "Name of the VPN virtual server to which the content is switched."
targetvserverexpr:
description:
- "Information about this content switching action."
comment:
description:
- "Comments associated with this cs action."
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
# lb_vserver_1 must have been already created with the netscaler_lb_vserver module
- name: Configure netscaler content switching action
delegate_to: localhost
netscaler_cs_action:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
validate_certs: no
state: present
name: action-1
targetlbvserver: lb_vserver_1
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: "['message 1', 'message 2']"
msg:
description: Message detailing the failure reason
returned: failure
type: str
sample: "Action does not exist"
diff:
description: List of differences between the actual configured object and the configuration specified in the module
returned: failure
type: dict
sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }"
'''
import json
try:
from nssrc.com.citrix.netscaler.nitro.resource.config.cs.csaction import csaction
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import (
ConfigProxy,
get_nitro_client,
netscaler_common_arguments,
log, loglines,
ensure_feature_is_enabled,
get_immutables_intersection
)
def action_exists(client, module):
if csaction.count_filtered(client, 'name:%s' % module.params['name']) > 0:
return True
else:
return False
def action_identical(client, module, csaction_proxy):
if len(diff_list(client, module, csaction_proxy)) == 0:
return True
else:
return False
def diff_list(client, module, csaction_proxy):
action_list = csaction.get_filtered(client, 'name:%s' % module.params['name'])
diff_list = csaction_proxy.diff_object(action_list[0])
if False and 'targetvserverexpr' in diff_list:
json_value = json.loads(action_list[0].targetvserverexpr)
if json_value == module.params['targetvserverexpr']:
del diff_list['targetvserverexpr']
return diff_list
def main():
module_specific_arguments = dict(
name=dict(type='str'),
targetlbvserver=dict(type='str'),
targetvserverexpr=dict(type='str'),
comment=dict(type='str'),
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
readwrite_attrs = [
'name',
'targetlbvserver',
'targetvserverexpr',
'comment',
]
readonly_attrs = [
'hits',
'referencecount',
'undefhits',
'builtin',
]
immutable_attrs = [
'name',
'targetvserverexpr',
]
transforms = {
}
# Instantiate config proxy
csaction_proxy = ConfigProxy(
actual=csaction(),
client=client,
attribute_values_dict=module.params,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
immutable_attrs=immutable_attrs,
transforms=transforms,
)
try:
ensure_feature_is_enabled(client, 'CS')
# Apply appropriate state
if module.params['state'] == 'present':
log('Applying actions for state present')
if not action_exists(client, module):
if not module.check_mode:
csaction_proxy.add()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not action_identical(client, module, csaction_proxy):
# Check if we try to change value of immutable attributes
immutables_changed = get_immutables_intersection(csaction_proxy, diff_list(client, module, csaction_proxy).keys())
if immutables_changed != []:
module.fail_json(
msg='Cannot update immutable attributes %s' % (immutables_changed,),
diff=diff_list(client, module, csaction_proxy),
**module_result
)
if not module.check_mode:
csaction_proxy.update()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
log('Sanity checks for state present')
if not module.check_mode:
if not action_exists(client, module):
module.fail_json(msg='Content switching action does not exist', **module_result)
if not action_identical(client, module, csaction_proxy):
module.fail_json(
msg='Content switching action differs from configured',
diff=diff_list(client, module, csaction_proxy),
**module_result
)
elif module.params['state'] == 'absent':
log('Applying actions for state absent')
if action_exists(client, module):
if not module.check_mode:
csaction_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state absent')
if action_exists(client, module):
module.fail_json(msg='Content switching action still exists', **module_result)
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,287 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_cs_policy
short_description: Manage content switching policy
description:
- Manage content switching policy.
- "This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance."
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
policyname:
description:
- >-
Name for the content switching policy. Must begin with an ASCII alphanumeric or underscore C(_)
character, and must contain only ASCII alphanumeric, underscore, hash C(#), period C(.), space C( ), colon
C(:), at sign C(@), equal sign C(=), and hyphen C(-) characters. Cannot be changed after a policy is
created.
- "The following requirement applies only to the NetScaler CLI:"
- >-
If the name includes one or more spaces, enclose the name in double or single quotation marks (for
example, my policy or my policy).
- "Minimum length = 1"
url:
description:
- >-
URL string that is matched with the URL of a request. Can contain a wildcard character. Specify the
string value in the following format: C([[prefix] [*]] [.suffix]).
- "Minimum length = 1"
- "Maximum length = 208"
rule:
description:
- >-
Expression, or name of a named expression, against which traffic is evaluated. Written in the classic
or default syntax.
- "Note:"
- >-
Maximum length of a string literal in the expression is 255 characters. A longer string can be split
into smaller strings of up to 255 characters each, and the smaller strings concatenated with the +
operator. For example, you can create a 500-character string as follows: '"<string of 255
characters>" + "<string of 245 characters>"'
domain:
description:
- "The domain name. The string value can range to 63 characters."
- "Minimum length = 1"
action:
description:
- >-
Content switching action that names the target load balancing virtual server to which the traffic is
switched.
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
- name: Create url cs policy
delegate_to: localhost
netscaler_cs_policy:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
validate_certs: no
state: present
policyname: policy_1
url: /example/
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: ['message 1', 'message 2']
msg:
description: Message detailing the failure reason
returned: failure
type: str
sample: "Could not load nitro python sdk"
diff:
description: List of differences between the actual configured object and the configuration specified in the module
returned: failure
type: dict
sample: { 'url': 'difference. ours: (str) example1 other: (str) /example1' }
'''
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import ConfigProxy, get_nitro_client, netscaler_common_arguments, log, loglines, ensure_feature_is_enabled
try:
from nssrc.com.citrix.netscaler.nitro.resource.config.cs.cspolicy import cspolicy
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
def policy_exists(client, module):
log('Checking if policy exists')
if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) > 0:
return True
else:
return False
def policy_identical(client, module, cspolicy_proxy):
log('Checking if defined policy is identical to configured')
if cspolicy.count_filtered(client, 'policyname:%s' % module.params['policyname']) == 0:
return False
policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname'])
diff_dict = cspolicy_proxy.diff_object(policy_list[0])
if 'ip' in diff_dict:
del diff_dict['ip']
if len(diff_dict) == 0:
return True
else:
return False
def diff_list(client, module, cspolicy_proxy):
policy_list = cspolicy.get_filtered(client, 'policyname:%s' % module.params['policyname'])
return cspolicy_proxy.diff_object(policy_list[0])
def main():
module_specific_arguments = dict(
policyname=dict(type='str'),
url=dict(type='str'),
rule=dict(type='str'),
domain=dict(type='str'),
action=dict(type='str'),
)
hand_inserted_arguments = dict(
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
argument_spec.update(hand_inserted_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
readwrite_attrs = [
'policyname',
'url',
'rule',
'domain',
'action',
]
readonly_attrs = [
'vstype',
'hits',
'bindhits',
'labelname',
'labeltype',
'priority',
'activepolicy',
'cspolicytype',
]
transforms = {
}
# Instantiate config proxy
cspolicy_proxy = ConfigProxy(
actual=cspolicy(),
client=client,
attribute_values_dict=module.params,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
transforms=transforms,
)
try:
ensure_feature_is_enabled(client, 'CS')
# Apply appropriate state
if module.params['state'] == 'present':
log('Sanity checks for state present')
if not policy_exists(client, module):
if not module.check_mode:
cspolicy_proxy.add()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not policy_identical(client, module, cspolicy_proxy):
if not module.check_mode:
cspolicy_proxy.update()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state present')
if not policy_exists(client, module):
module.fail_json(msg='Policy does not exist', **module_result)
if not policy_identical(client, module, cspolicy_proxy):
module.fail_json(msg='Policy differs from configured', diff=diff_list(client, module, cspolicy_proxy), **module_result)
elif module.params['state'] == 'absent':
log('Applying actions for state absent')
if policy_exists(client, module):
if not module.check_mode:
cspolicy_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state absent')
if policy_exists(client, module):
module.fail_json(msg='Policy still exists', **module_result)
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,696 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_gslb_service
short_description: Manage gslb service entities in Netscaler.
description:
- Manage gslb service entities in Netscaler.
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
servicename:
description:
- >-
Name for the GSLB service. Must begin with an ASCII alphanumeric or underscore C(_) character, and
must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, colon C(:), at C(@),
equals C(=), and hyphen C(-) characters. Can be changed after the GSLB service is created.
- >-
- "Minimum length = 1"
cnameentry:
description:
- "Canonical name of the GSLB service. Used in CNAME-based GSLB."
- "Minimum length = 1"
servername:
description:
- "Name of the server hosting the GSLB service."
- "Minimum length = 1"
servicetype:
choices:
- 'HTTP'
- 'FTP'
- 'TCP'
- 'UDP'
- 'SSL'
- 'SSL_BRIDGE'
- 'SSL_TCP'
- 'NNTP'
- 'ANY'
- 'SIP_UDP'
- 'SIP_TCP'
- 'SIP_SSL'
- 'RADIUS'
- 'RDP'
- 'RTSP'
- 'MYSQL'
- 'MSSQL'
- 'ORACLE'
description:
- "Type of service to create."
port:
description:
- "Port on which the load balancing entity represented by this GSLB service listens."
- "Minimum value = 1"
- "Range 1 - 65535"
- "* in CLI is represented as 65535 in NITRO API"
publicip:
description:
- >-
The public IP address that a NAT device translates to the GSLB service's private IP address.
Optional.
publicport:
description:
- >-
The public port associated with the GSLB service's public IP address. The port is mapped to the
service's private port number. Applicable to the local GSLB service. Optional.
maxclient:
description:
- >-
The maximum number of open connections that the service can support at any given time. A GSLB service
whose connection count reaches the maximum is not considered when a GSLB decision is made, until the
connection count drops below the maximum.
- "Minimum value = C(0)"
- "Maximum value = C(4294967294)"
healthmonitor:
description:
- "Monitor the health of the GSLB service."
type: bool
sitename:
description:
- "Name of the GSLB site to which the service belongs."
- "Minimum length = 1"
cip:
choices:
- 'enabled'
- 'disabled'
description:
- >-
In the request that is forwarded to the GSLB service, insert a header that stores the client's IP
address. Client IP header insertion is used in connection-proxy based site persistence.
cipheader:
description:
- >-
Name for the HTTP header that stores the client's IP address. Used with the Client IP option. If
client IP header insertion is enabled on the service and a name is not specified for the header, the
NetScaler appliance uses the name specified by the cipHeader parameter in the set ns param command
or, in the GUI, the Client IP Header parameter in the Configure HTTP Parameters dialog box.
- "Minimum length = 1"
sitepersistence:
choices:
- 'ConnectionProxy'
- 'HTTPRedirect'
- 'NONE'
description:
- "Use cookie-based site persistence. Applicable only to C(HTTP) and C(SSL) GSLB services."
siteprefix:
description:
- >-
The site's prefix string. When the service is bound to a GSLB virtual server, a GSLB site domain is
generated internally for each bound service-domain pair by concatenating the site prefix of the
service and the name of the domain. If the special string NONE is specified, the site-prefix string
is unset. When implementing HTTP redirect site persistence, the NetScaler appliance redirects GSLB
requests to GSLB services by using their site domains.
clttimeout:
description:
- >-
Idle time, in seconds, after which a client connection is terminated. Applicable if connection proxy
based site persistence is used.
- "Minimum value = 0"
- "Maximum value = 31536000"
maxbandwidth:
description:
- >-
Integer specifying the maximum bandwidth allowed for the service. A GSLB service whose bandwidth
reaches the maximum is not considered when a GSLB decision is made, until its bandwidth consumption
drops below the maximum.
downstateflush:
choices:
- 'enabled'
- 'disabled'
description:
- >-
Flush all active transactions associated with the GSLB service when its state transitions from UP to
DOWN. Do not enable this option for services that must complete their transactions. Applicable if
connection proxy based site persistence is used.
maxaaausers:
description:
- >-
Maximum number of SSL VPN users that can be logged on concurrently to the VPN virtual server that is
represented by this GSLB service. A GSLB service whose user count reaches the maximum is not
considered when a GSLB decision is made, until the count drops below the maximum.
- "Minimum value = C(0)"
- "Maximum value = C(65535)"
monthreshold:
description:
- >-
Monitoring threshold value for the GSLB service. If the sum of the weights of the monitors that are
bound to this GSLB service and are in the UP state is not equal to or greater than this threshold
value, the service is marked as DOWN.
- "Minimum value = C(0)"
- "Maximum value = C(65535)"
hashid:
description:
- "Unique hash identifier for the GSLB service, used by hash based load balancing methods."
- "Minimum value = C(1)"
comment:
description:
- "Any comments that you might want to associate with the GSLB service."
appflowlog:
choices:
- 'enabled'
- 'disabled'
description:
- "Enable logging appflow flow information."
ipaddress:
description:
- >-
IP address for the GSLB service. Should represent a load balancing, content switching, or VPN virtual
server on the NetScaler appliance, or the IP address of another load balancing device.
monitor_bindings:
description:
- Bind monitors to this gslb service
suboptions:
weight:
description:
- Weight to assign to the monitor-service binding.
- A larger number specifies a greater weight.
- Contributes to the monitoring threshold, which determines the state of the service.
- Minimum value = C(1)
- Maximum value = C(100)
monitor_name:
description:
- Monitor name.
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
- name: Setup gslb service 2
delegate_to: localhost
register: result
check_mode: "{{ check_mode }}"
netscaler_gslb_service:
operation: present
servicename: gslb-service-2
cnameentry: example.com
sitename: gslb-site-1
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: "['message 1', 'message 2']"
msg:
description: Message detailing the failure reason
returned: failure
type: str
sample: "Action does not exist"
diff:
description: List of differences between the actual configured object and the configuration specified in the module
returned: failure
type: dict
sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }"
'''
import copy
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import (
ConfigProxy,
get_nitro_client,
netscaler_common_arguments,
log,
loglines,
ensure_feature_is_enabled,
monkey_patch_nitro_api,
get_immutables_intersection,
)
try:
monkey_patch_nitro_api()
from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice import gslbservice
from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbservice_lbmonitor_binding import gslbservice_lbmonitor_binding
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
def gslb_service_exists(client, module):
if gslbservice.count_filtered(client, 'servicename:%s' % module.params['servicename']) > 0:
return True
else:
return False
def gslb_service_identical(client, module, gslb_service_proxy):
gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename'])
diff_dict = gslb_service_proxy.diff_object(gslb_service_list[0])
# Ignore ip attribute missing
if 'ip' in diff_dict:
del diff_dict['ip']
if len(diff_dict) == 0:
return True
else:
return False
def get_actual_monitor_bindings(client, module):
log('get_actual_monitor_bindings')
# Get actual monitor bindings and index them by monitor_name
actual_monitor_bindings = {}
if gslbservice_lbmonitor_binding.count(client, servicename=module.params['servicename']) != 0:
# Get all monitor bindings associated with the named gslb vserver
fetched_bindings = gslbservice_lbmonitor_binding.get(client, servicename=module.params['servicename'])
# index by monitor name
for binding in fetched_bindings:
# complete_missing_attributes(binding, gslbservice_lbmonitor_binding_rw_attrs, fill_value=None)
actual_monitor_bindings[binding.monitor_name] = binding
return actual_monitor_bindings
def get_configured_monitor_bindings(client, module):
log('get_configured_monitor_bindings')
configured_monitor_proxys = {}
gslbservice_lbmonitor_binding_rw_attrs = [
'weight',
'servicename',
'monitor_name',
]
# Get configured monitor bindings and index them by monitor_name
if module.params['monitor_bindings'] is not None:
for configured_monitor_bindings in module.params['monitor_bindings']:
binding_values = copy.deepcopy(configured_monitor_bindings)
binding_values['servicename'] = module.params['servicename']
proxy = ConfigProxy(
actual=gslbservice_lbmonitor_binding(),
client=client,
attribute_values_dict=binding_values,
readwrite_attrs=gslbservice_lbmonitor_binding_rw_attrs,
readonly_attrs=[],
)
configured_monitor_proxys[configured_monitor_bindings['monitor_name']] = proxy
return configured_monitor_proxys
def monitor_bindings_identical(client, module):
log('monitor_bindings_identical')
actual_bindings = get_actual_monitor_bindings(client, module)
configured_proxys = get_configured_monitor_bindings(client, module)
actual_keyset = set(actual_bindings.keys())
configured_keyset = set(configured_proxys.keys())
symmetric_difference = actual_keyset ^ configured_keyset
if len(symmetric_difference) != 0:
log('Symmetric difference %s' % symmetric_difference)
return False
# Item for item equality test
for key, proxy in configured_proxys.items():
if not proxy.has_equal_attributes(actual_bindings[key]):
log('monitor binding difference %s' % proxy.diff_object(actual_bindings[key]))
return False
# Fallthrough to True result
return True
def sync_monitor_bindings(client, module):
log('sync_monitor_bindings')
actual_monitor_bindings = get_actual_monitor_bindings(client, module)
configured_monitor_proxys = get_configured_monitor_bindings(client, module)
# Delete actual bindings not in configured bindings
for monitor_name, actual_binding in actual_monitor_bindings.items():
if monitor_name not in configured_monitor_proxys.keys():
log('Deleting absent binding for monitor %s' % monitor_name)
log('dir is %s' % dir(actual_binding))
gslbservice_lbmonitor_binding.delete(client, actual_binding)
# Delete and re-add actual bindings that differ from configured
for proxy_key, binding_proxy in configured_monitor_proxys.items():
if proxy_key in actual_monitor_bindings:
actual_binding = actual_monitor_bindings[proxy_key]
if not binding_proxy.has_equal_attributes(actual_binding):
log('Deleting differing binding for monitor %s' % actual_binding.monitor_name)
log('dir %s' % dir(actual_binding))
log('attribute monitor_name %s' % getattr(actual_binding, 'monitor_name'))
log('attribute monitorname %s' % getattr(actual_binding, 'monitorname', None))
gslbservice_lbmonitor_binding.delete(client, actual_binding)
log('Adding anew binding for monitor %s' % binding_proxy.monitor_name)
binding_proxy.add()
# Add configured monitors that are missing from actual
for proxy_key, binding_proxy in configured_monitor_proxys.items():
if proxy_key not in actual_monitor_bindings.keys():
log('Adding monitor binding for monitor %s' % binding_proxy.monitor_name)
binding_proxy.add()
def diff_list(client, module, gslb_service_proxy):
gslb_service_list = gslbservice.get_filtered(client, 'servicename:%s' % module.params['servicename'])
diff_list = gslb_service_proxy.diff_object(gslb_service_list[0])
if 'ip' in diff_list:
del diff_list['ip']
return diff_list
def all_identical(client, module, gslb_service_proxy):
return gslb_service_identical(client, module, gslb_service_proxy) and monitor_bindings_identical(client, module)
def main():
module_specific_arguments = dict(
servicename=dict(type='str'),
cnameentry=dict(type='str'),
servername=dict(type='str'),
servicetype=dict(
type='str',
choices=[
'HTTP',
'FTP',
'TCP',
'UDP',
'SSL',
'SSL_BRIDGE',
'SSL_TCP',
'NNTP',
'ANY',
'SIP_UDP',
'SIP_TCP',
'SIP_SSL',
'RADIUS',
'RDP',
'RTSP',
'MYSQL',
'MSSQL',
'ORACLE',
]
),
port=dict(type='int'),
publicip=dict(type='str'),
publicport=dict(type='int'),
maxclient=dict(type='float'),
healthmonitor=dict(type='bool'),
sitename=dict(type='str'),
cip=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
cipheader=dict(type='str'),
sitepersistence=dict(
type='str',
choices=[
'ConnectionProxy',
'HTTPRedirect',
'NONE',
]
),
siteprefix=dict(type='str'),
clttimeout=dict(type='float'),
maxbandwidth=dict(type='float'),
downstateflush=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
maxaaausers=dict(type='float'),
monthreshold=dict(type='float'),
hashid=dict(type='float'),
comment=dict(type='str'),
appflowlog=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
ipaddress=dict(type='str'),
)
hand_inserted_arguments = dict(
monitor_bindings=dict(type='list'),
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
argument_spec.update(hand_inserted_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
readwrite_attrs = [
'servicename',
'cnameentry',
'ip',
'servername',
'servicetype',
'port',
'publicip',
'publicport',
'maxclient',
'healthmonitor',
'sitename',
'cip',
'cipheader',
'sitepersistence',
'siteprefix',
'clttimeout',
'maxbandwidth',
'downstateflush',
'maxaaausers',
'monthreshold',
'hashid',
'comment',
'appflowlog',
'ipaddress',
]
readonly_attrs = [
'gslb',
'svrstate',
'svreffgslbstate',
'gslbthreshold',
'gslbsvcstats',
'monstate',
'preferredlocation',
'monitor_state',
'statechangetimesec',
'tickssincelaststatechange',
'threshold',
'clmonowner',
'clmonview',
'__count',
]
immutable_attrs = [
'servicename',
'cnameentry',
'ip',
'servername',
'servicetype',
'port',
'sitename',
'state',
'cipheader',
'cookietimeout',
'clttimeout',
'svrtimeout',
'viewip',
'monitor_name_svc',
'newname',
]
transforms = {
'healthmonitor': ['bool_yes_no'],
'cip': [lambda v: v.upper()],
'downstateflush': [lambda v: v.upper()],
'appflowlog': [lambda v: v.upper()],
}
# params = copy.deepcopy(module.params)
module.params['ip'] = module.params['ipaddress']
# Instantiate config proxy
gslb_service_proxy = ConfigProxy(
actual=gslbservice(),
client=client,
attribute_values_dict=module.params,
transforms=transforms,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
immutable_attrs=immutable_attrs,
)
try:
ensure_feature_is_enabled(client, 'GSLB')
# Apply appropriate state
if module.params['state'] == 'present':
if not gslb_service_exists(client, module):
if not module.check_mode:
gslb_service_proxy.add()
sync_monitor_bindings(client, module)
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not all_identical(client, module, gslb_service_proxy):
# Check if we try to change value of immutable attributes
immutables_changed = get_immutables_intersection(gslb_service_proxy, diff_list(client, module, gslb_service_proxy).keys())
if immutables_changed != []:
module.fail_json(
msg='Cannot update immutable attributes %s' % (immutables_changed,),
diff=diff_list(client, module, gslb_service_proxy),
**module_result
)
# Update main configuration object
if not gslb_service_identical(client, module, gslb_service_proxy):
if not module.check_mode:
gslb_service_proxy.update()
# Update monitor bindigns
if not monitor_bindings_identical(client, module):
if not module.check_mode:
sync_monitor_bindings(client, module)
# Fallthrough to save and change status update
module_result['changed'] = True
if module.params['save_config']:
client.save_config()
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
if not gslb_service_exists(client, module):
module.fail_json(msg='GSLB service does not exist', **module_result)
if not gslb_service_identical(client, module, gslb_service_proxy):
module.fail_json(
msg='GSLB service differs from configured',
diff=diff_list(client, module, gslb_service_proxy),
**module_result
)
if not monitor_bindings_identical(client, module):
module.fail_json(
msg='Monitor bindings differ from configured',
diff=diff_list(client, module, gslb_service_proxy),
**module_result
)
elif module.params['state'] == 'absent':
if gslb_service_exists(client, module):
if not module.check_mode:
gslb_service_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
if gslb_service_exists(client, module):
module.fail_json(msg='GSLB service still exists', **module_result)
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,424 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_gslb_site
short_description: Manage gslb site entities in Netscaler.
description:
- Manage gslb site entities in Netscaler.
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
sitename:
description:
- >-
Name for the GSLB site. Must begin with an ASCII alphanumeric or underscore C(_) character, and must
contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals
C(=), and hyphen C(-) characters. Cannot be changed after the virtual server is created.
- "Minimum length = 1"
sitetype:
choices:
- 'REMOTE'
- 'LOCAL'
description:
- >-
Type of site to create. If the type is not specified, the appliance automatically detects and sets
the type on the basis of the IP address being assigned to the site. If the specified site IP address
is owned by the appliance (for example, a MIP address or SNIP address), the site is a local site.
Otherwise, it is a remote site.
siteipaddress:
description:
- >-
IP address for the GSLB site. The GSLB site uses this IP address to communicate with other GSLB
sites. For a local site, use any IP address that is owned by the appliance (for example, a SNIP or
MIP address, or the IP address of the ADNS service).
- "Minimum length = 1"
publicip:
description:
- >-
Public IP address for the local site. Required only if the appliance is deployed in a private address
space and the site has a public IP address hosted on an external firewall or a NAT device.
- "Minimum length = 1"
metricexchange:
choices:
- 'enabled'
- 'disabled'
description:
- >-
Exchange metrics with other sites. Metrics are exchanged by using Metric Exchange Protocol (MEP). The
appliances in the GSLB setup exchange health information once every second.
- >-
If you disable metrics exchange, you can use only static load balancing methods (such as round robin,
static proximity, or the hash-based methods), and if you disable metrics exchange when a dynamic load
balancing method (such as least connection) is in operation, the appliance falls back to round robin.
Also, if you disable metrics exchange, you must use a monitor to determine the state of GSLB
services. Otherwise, the service is marked as DOWN.
nwmetricexchange:
choices:
- 'enabled'
- 'disabled'
description:
- >-
Exchange, with other GSLB sites, network metrics such as round-trip time (RTT), learned from
communications with various local DNS (LDNS) servers used by clients. RTT information is used in the
dynamic RTT load balancing method, and is exchanged every 5 seconds.
sessionexchange:
choices:
- 'enabled'
- 'disabled'
description:
- "Exchange persistent session entries with other GSLB sites every five seconds."
triggermonitor:
choices:
- 'ALWAYS'
- 'MEPDOWN'
- 'MEPDOWN_SVCDOWN'
description:
- >-
Specify the conditions under which the GSLB service must be monitored by a monitor, if one is bound.
Available settings function as follows:
- "* C(ALWAYS) - Monitor the GSLB service at all times."
- >-
* C(MEPDOWN) - Monitor the GSLB service only when the exchange of metrics through the Metrics Exchange
Protocol (MEP) is disabled.
- "C(MEPDOWN_SVCDOWN) - Monitor the service in either of the following situations:"
- "* The exchange of metrics through MEP is disabled."
- >-
* The exchange of metrics through MEP is enabled but the status of the service, learned through
metrics exchange, is DOWN.
parentsite:
description:
- "Parent site of the GSLB site, in a parent-child topology."
clip:
description:
- >-
Cluster IP address. Specify this parameter to connect to the remote cluster site for GSLB auto-sync.
Note: The cluster IP address is defined when creating the cluster.
publicclip:
description:
- >-
IP address to be used to globally access the remote cluster when it is deployed behind a NAT. It can
be same as the normal cluster IP address.
naptrreplacementsuffix:
description:
- >-
The naptr replacement suffix configured here will be used to construct the naptr replacement field in
NAPTR record.
- "Minimum length = 1"
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
- name: Setup gslb site
delegate_to: localhost
netscaler_gslb_site:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
sitename: gslb-site-1
siteipaddress: 192.168.1.1
sitetype: LOCAL
publicip: 192.168.1.1
metricexchange: enabled
nwmetricexchange: enabled
sessionexchange: enabled
triggermonitor: ALWAYS
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: "['message 1', 'message 2']"
msg:
description: Message detailing the failure reason
returned: failure
type: str
sample: "Action does not exist"
diff:
description: List of differences between the actual configured object and the configuration specified in the module
returned: failure
type: dict
sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }"
'''
try:
from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbsite import gslbsite
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import (
ConfigProxy,
get_nitro_client,
netscaler_common_arguments,
log,
loglines,
ensure_feature_is_enabled,
get_immutables_intersection,
)
def gslb_site_exists(client, module):
if gslbsite.count_filtered(client, 'sitename:%s' % module.params['sitename']) > 0:
return True
else:
return False
def gslb_site_identical(client, module, gslb_site_proxy):
gslb_site_list = gslbsite.get_filtered(client, 'sitename:%s' % module.params['sitename'])
diff_dict = gslb_site_proxy.diff_object(gslb_site_list[0])
if len(diff_dict) == 0:
return True
else:
return False
def diff_list(client, module, gslb_site_proxy):
gslb_site_list = gslbsite.get_filtered(client, 'sitename:%s' % module.params['sitename'])
return gslb_site_proxy.diff_object(gslb_site_list[0])
def main():
module_specific_arguments = dict(
sitename=dict(type='str'),
sitetype=dict(
type='str',
choices=[
'REMOTE',
'LOCAL',
]
),
siteipaddress=dict(type='str'),
publicip=dict(type='str'),
metricexchange=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
nwmetricexchange=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
sessionexchange=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
triggermonitor=dict(
type='str',
choices=[
'ALWAYS',
'MEPDOWN',
'MEPDOWN_SVCDOWN',
]
),
parentsite=dict(type='str'),
clip=dict(type='str'),
publicclip=dict(type='str'),
naptrreplacementsuffix=dict(type='str'),
)
hand_inserted_arguments = dict(
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
argument_spec.update(hand_inserted_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
readwrite_attrs = [
'sitename',
'sitetype',
'siteipaddress',
'publicip',
'metricexchange',
'nwmetricexchange',
'sessionexchange',
'triggermonitor',
'parentsite',
'clip',
'publicclip',
'naptrreplacementsuffix',
]
readonly_attrs = [
'status',
'persistencemepstatus',
'version',
'__count',
]
immutable_attrs = [
'sitename',
'sitetype',
'siteipaddress',
'publicip',
'parentsite',
'clip',
'publicclip',
]
transforms = {
'metricexchange': [lambda v: v.upper()],
'nwmetricexchange': [lambda v: v.upper()],
'sessionexchange': [lambda v: v.upper()],
}
# Instantiate config proxy
gslb_site_proxy = ConfigProxy(
actual=gslbsite(),
client=client,
attribute_values_dict=module.params,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
immutable_attrs=immutable_attrs,
transforms=transforms,
)
try:
ensure_feature_is_enabled(client, 'GSLB')
# Apply appropriate state
if module.params['state'] == 'present':
log('Applying actions for state present')
if not gslb_site_exists(client, module):
if not module.check_mode:
gslb_site_proxy.add()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not gslb_site_identical(client, module, gslb_site_proxy):
# Check if we try to change value of immutable attributes
immutables_changed = get_immutables_intersection(gslb_site_proxy, diff_list(client, module, gslb_site_proxy).keys())
if immutables_changed != []:
module.fail_json(
msg='Cannot update immutable attributes %s' % (immutables_changed,),
diff=diff_list(client, module, gslb_site_proxy),
**module_result
)
if not module.check_mode:
gslb_site_proxy.update()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state present')
if not gslb_site_exists(client, module):
module.fail_json(msg='GSLB site does not exist', **module_result)
if not gslb_site_identical(client, module, gslb_site_proxy):
module.fail_json(msg='GSLB site differs from configured', diff=diff_list(client, module, gslb_site_proxy), **module_result)
elif module.params['state'] == 'absent':
log('Applying actions for state absent')
if gslb_site_exists(client, module):
if not module.check_mode:
gslb_site_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state absent')
if gslb_site_exists(client, module):
module.fail_json(msg='GSLB site still exists', **module_result)
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,955 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_gslb_vserver
short_description: Configure gslb vserver entities in Netscaler.
description:
- Configure gslb vserver entities in Netscaler.
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
name:
description:
- >-
Name for the GSLB virtual server. Must begin with an ASCII alphanumeric or underscore C(_) character,
and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space, colon C(:), at C(@),
equals C(=), and hyphen C(-) characters. Can be changed after the virtual server is created.
- "Minimum length = 1"
servicetype:
choices:
- 'HTTP'
- 'FTP'
- 'TCP'
- 'UDP'
- 'SSL'
- 'SSL_BRIDGE'
- 'SSL_TCP'
- 'NNTP'
- 'ANY'
- 'SIP_UDP'
- 'SIP_TCP'
- 'SIP_SSL'
- 'RADIUS'
- 'RDP'
- 'RTSP'
- 'MYSQL'
- 'MSSQL'
- 'ORACLE'
description:
- "Protocol used by services bound to the virtual server."
- >-
dnsrecordtype:
choices:
- 'A'
- 'AAAA'
- 'CNAME'
- 'NAPTR'
description:
- "DNS record type to associate with the GSLB virtual server's domain name."
- "Default value: A"
- "Possible values = A, AAAA, CNAME, NAPTR"
lbmethod:
choices:
- 'ROUNDROBIN'
- 'LEASTCONNECTION'
- 'LEASTRESPONSETIME'
- 'SOURCEIPHASH'
- 'LEASTBANDWIDTH'
- 'LEASTPACKETS'
- 'STATICPROXIMITY'
- 'RTT'
- 'CUSTOMLOAD'
description:
- "Load balancing method for the GSLB virtual server."
- "Default value: LEASTCONNECTION"
- >-
Possible values = ROUNDROBIN, LEASTCONNECTION, LEASTRESPONSETIME, SOURCEIPHASH, LEASTBANDWIDTH,
LEASTPACKETS, STATICPROXIMITY, RTT, CUSTOMLOAD
backuplbmethod:
choices:
- 'ROUNDROBIN'
- 'LEASTCONNECTION'
- 'LEASTRESPONSETIME'
- 'SOURCEIPHASH'
- 'LEASTBANDWIDTH'
- 'LEASTPACKETS'
- 'STATICPROXIMITY'
- 'RTT'
- 'CUSTOMLOAD'
description:
- >-
Backup load balancing method. Becomes operational if the primary load balancing method fails or
cannot be used. Valid only if the primary method is based on either round-trip time (RTT) or static
proximity.
netmask:
description:
- "IPv4 network mask for use in the SOURCEIPHASH load balancing method."
- "Minimum length = 1"
v6netmasklen:
description:
- >-
Number of bits to consider, in an IPv6 source IP address, for creating the hash that is required by
the C(SOURCEIPHASH) load balancing method.
- "Default value: C(128)"
- "Minimum value = C(1)"
- "Maximum value = C(128)"
tolerance:
description:
- >-
Site selection tolerance, in milliseconds, for implementing the RTT load balancing method. If a
site's RTT deviates from the lowest RTT by more than the specified tolerance, the site is not
considered when the NetScaler appliance makes a GSLB decision. The appliance implements the round
robin method of global server load balancing between sites whose RTT values are within the specified
tolerance. If the tolerance is 0 (zero), the appliance always sends clients the IP address of the
site with the lowest RTT.
- "Minimum value = C(0)"
- "Maximum value = C(100)"
persistencetype:
choices:
- 'SOURCEIP'
- 'NONE'
description:
- "Use source IP address based persistence for the virtual server."
- >-
After the load balancing method selects a service for the first packet, the IP address received in
response to the DNS query is used for subsequent requests from the same client.
persistenceid:
description:
- >-
The persistence ID for the GSLB virtual server. The ID is a positive integer that enables GSLB sites
to identify the GSLB virtual server, and is required if source IP address based or spill over based
persistence is enabled on the virtual server.
- "Minimum value = C(0)"
- "Maximum value = C(65535)"
persistmask:
description:
- >-
The optional IPv4 network mask applied to IPv4 addresses to establish source IP address based
persistence.
- "Minimum length = 1"
v6persistmasklen:
description:
- >-
Number of bits to consider in an IPv6 source IP address when creating source IP address based
persistence sessions.
- "Default value: C(128)"
- "Minimum value = C(1)"
- "Maximum value = C(128)"
timeout:
description:
- "Idle time, in minutes, after which a persistence entry is cleared."
- "Default value: C(2)"
- "Minimum value = C(2)"
- "Maximum value = C(1440)"
mir:
choices:
- 'enabled'
- 'disabled'
description:
- "Include multiple IP addresses in the DNS responses sent to clients."
disableprimaryondown:
choices:
- 'enabled'
- 'disabled'
description:
- >-
Continue to direct traffic to the backup chain even after the primary GSLB virtual server returns to
the UP state. Used when spillover is configured for the virtual server.
dynamicweight:
choices:
- 'SERVICECOUNT'
- 'SERVICEWEIGHT'
- 'DISABLED'
description:
- >-
Specify if the appliance should consider the service count, service weights, or ignore both when
using weight-based load balancing methods. The state of the number of services bound to the virtual
server help the appliance to select the service.
considereffectivestate:
choices:
- 'NONE'
- 'STATE_ONLY'
description:
- >-
If the primary state of all bound GSLB services is DOWN, consider the effective states of all the
GSLB services, obtained through the Metrics Exchange Protocol (MEP), when determining the state of
the GSLB virtual server. To consider the effective state, set the parameter to STATE_ONLY. To
disregard the effective state, set the parameter to NONE.
- >-
The effective state of a GSLB service is the ability of the corresponding virtual server to serve
traffic. The effective state of the load balancing virtual server, which is transferred to the GSLB
service, is UP even if only one virtual server in the backup chain of virtual servers is in the UP
state.
comment:
description:
- "Any comments that you might want to associate with the GSLB virtual server."
somethod:
choices:
- 'CONNECTION'
- 'DYNAMICCONNECTION'
- 'BANDWIDTH'
- 'HEALTH'
- 'NONE'
description:
- "Type of threshold that, when exceeded, triggers spillover. Available settings function as follows:"
- "* C(CONNECTION) - Spillover occurs when the number of client connections exceeds the threshold."
- >-
* C(DYNAMICCONNECTION) - Spillover occurs when the number of client connections at the GSLB virtual
server exceeds the sum of the maximum client (Max Clients) settings for bound GSLB services. Do not
specify a spillover threshold for this setting, because the threshold is implied by the Max Clients
settings of the bound GSLB services.
- >-
* C(BANDWIDTH) - Spillover occurs when the bandwidth consumed by the GSLB virtual server's incoming and
outgoing traffic exceeds the threshold.
- >-
* C(HEALTH) - Spillover occurs when the percentage of weights of the GSLB services that are UP drops
below the threshold. For example, if services gslbSvc1, gslbSvc2, and gslbSvc3 are bound to a virtual
server, with weights 1, 2, and 3, and the spillover threshold is 50%, spillover occurs if gslbSvc1
and gslbSvc3 or gslbSvc2 and gslbSvc3 transition to DOWN.
- "* C(NONE) - Spillover does not occur."
sopersistence:
choices:
- 'enabled'
- 'disabled'
description:
- >-
If spillover occurs, maintain source IP address based persistence for both primary and backup GSLB
virtual servers.
sopersistencetimeout:
description:
- "Timeout for spillover persistence, in minutes."
- "Default value: C(2)"
- "Minimum value = C(2)"
- "Maximum value = C(1440)"
sothreshold:
description:
- >-
Threshold at which spillover occurs. Specify an integer for the CONNECTION spillover method, a
bandwidth value in kilobits per second for the BANDWIDTH method (do not enter the units), or a
percentage for the HEALTH method (do not enter the percentage symbol).
- "Minimum value = C(1)"
- "Maximum value = C(4294967287)"
sobackupaction:
choices:
- 'DROP'
- 'ACCEPT'
- 'REDIRECT'
description:
- >-
Action to be performed if spillover is to take effect, but no backup chain to spillover is usable or
exists.
appflowlog:
choices:
- 'enabled'
- 'disabled'
description:
- "Enable logging appflow flow information."
domain_bindings:
description:
- >-
List of bindings for domains for this glsb vserver.
suboptions:
cookietimeout:
description:
- Timeout, in minutes, for the GSLB site cookie.
domainname:
description:
- Domain name for which to change the time to live (TTL) and/or backup service IP address.
ttl:
description:
- Time to live (TTL) for the domain.
sitedomainttl:
description:
- >-
TTL, in seconds, for all internally created site domains (created when a site prefix is
configured on a GSLB service) that are associated with this virtual server.
- Minimum value = C(1)
service_bindings:
description:
- List of bindings for gslb services bound to this gslb virtual server.
suboptions:
servicename:
description:
- Name of the GSLB service for which to change the weight.
weight:
description:
- Weight to assign to the GSLB service.
disabled:
description:
- When set to C(yes) the GSLB Vserver state will be set to C(disabled).
- When set to C(no) the GSLB Vserver state will be set to C(enabled).
- >-
Note that due to limitations of the underlying NITRO API a C(disabled) state change alone
does not cause the module result to report a changed status.
type: bool
default: false
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
'''
RETURN = '''
'''
import copy
try:
from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver import gslbvserver
from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_gslbservice_binding import gslbvserver_gslbservice_binding
from nssrc.com.citrix.netscaler.nitro.resource.config.gslb.gslbvserver_domain_binding import gslbvserver_domain_binding
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import (
ConfigProxy,
get_nitro_client,
netscaler_common_arguments,
log,
loglines,
ensure_feature_is_enabled,
get_immutables_intersection,
complete_missing_attributes
)
gslbvserver_domain_binding_rw_attrs = [
'name',
'domainname',
'backupipflag',
'cookietimeout',
'backupip',
'ttl',
'sitedomainttl',
'cookie_domainflag',
]
gslbvserver_gslbservice_binding_rw_attrs = [
'name',
'servicename',
'weight',
]
def get_actual_domain_bindings(client, module):
log('get_actual_domain_bindings')
# Get actual domain bindings and index them by domainname
actual_domain_bindings = {}
if gslbvserver_domain_binding.count(client, name=module.params['name']) != 0:
# Get all domain bindings associated with the named gslb vserver
fetched_domain_bindings = gslbvserver_domain_binding.get(client, name=module.params['name'])
# index by domainname
for binding in fetched_domain_bindings:
complete_missing_attributes(binding, gslbvserver_domain_binding_rw_attrs, fill_value=None)
actual_domain_bindings[binding.domainname] = binding
return actual_domain_bindings
def get_configured_domain_bindings_proxys(client, module):
log('get_configured_domain_bindings_proxys')
configured_domain_proxys = {}
# Get configured domain bindings and index them by domainname
if module.params['domain_bindings'] is not None:
for configured_domain_binding in module.params['domain_bindings']:
binding_values = copy.deepcopy(configured_domain_binding)
binding_values['name'] = module.params['name']
gslbvserver_domain_binding_proxy = ConfigProxy(
actual=gslbvserver_domain_binding(),
client=client,
attribute_values_dict=binding_values,
readwrite_attrs=gslbvserver_domain_binding_rw_attrs,
readonly_attrs=[],
)
configured_domain_proxys[configured_domain_binding['domainname']] = gslbvserver_domain_binding_proxy
return configured_domain_proxys
def sync_domain_bindings(client, module):
log('sync_domain_bindings')
actual_domain_bindings = get_actual_domain_bindings(client, module)
configured_domain_proxys = get_configured_domain_bindings_proxys(client, module)
# Delete actual bindings not in configured bindings
for domainname, actual_domain_binding in actual_domain_bindings.items():
if domainname not in configured_domain_proxys.keys():
log('Deleting absent binding for domain %s' % domainname)
gslbvserver_domain_binding.delete(client, actual_domain_binding)
# Delete actual bindings that differ from configured
for proxy_key, binding_proxy in configured_domain_proxys.items():
if proxy_key in actual_domain_bindings:
actual_binding = actual_domain_bindings[proxy_key]
if not binding_proxy.has_equal_attributes(actual_binding):
log('Deleting differing binding for domain %s' % binding_proxy.domainname)
gslbvserver_domain_binding.delete(client, actual_binding)
log('Adding anew binding for domain %s' % binding_proxy.domainname)
binding_proxy.add()
# Add configured domains that are missing from actual
for proxy_key, binding_proxy in configured_domain_proxys.items():
if proxy_key not in actual_domain_bindings.keys():
log('Adding domain binding for domain %s' % binding_proxy.domainname)
binding_proxy.add()
def domain_bindings_identical(client, module):
log('domain_bindings_identical')
actual_domain_bindings = get_actual_domain_bindings(client, module)
configured_domain_proxys = get_configured_domain_bindings_proxys(client, module)
actual_keyset = set(actual_domain_bindings.keys())
configured_keyset = set(configured_domain_proxys.keys())
symmetric_difference = actual_keyset ^ configured_keyset
log('symmetric difference %s' % symmetric_difference)
if len(symmetric_difference) != 0:
return False
# Item for item equality test
for key, proxy in configured_domain_proxys.items():
diff = proxy.diff_object(actual_domain_bindings[key])
if 'backupipflag' in diff:
del diff['backupipflag']
if not len(diff) == 0:
return False
# Fallthrough to True result
return True
def get_actual_service_bindings(client, module):
log('get_actual_service_bindings')
# Get actual domain bindings and index them by domainname
actual_bindings = {}
if gslbvserver_gslbservice_binding.count(client, name=module.params['name']) != 0:
# Get all service bindings associated with the named gslb vserver
fetched_bindings = gslbvserver_gslbservice_binding.get(client, name=module.params['name'])
# index by servicename
for binding in fetched_bindings:
complete_missing_attributes(binding, gslbvserver_gslbservice_binding_rw_attrs, fill_value=None)
actual_bindings[binding.servicename] = binding
return actual_bindings
def get_configured_service_bindings(client, module):
log('get_configured_service_bindings_proxys')
configured_proxys = {}
# Get configured domain bindings and index them by domainname
if module.params['service_bindings'] is not None:
for configured_binding in module.params['service_bindings']:
binding_values = copy.deepcopy(configured_binding)
binding_values['name'] = module.params['name']
gslbvserver_service_binding_proxy = ConfigProxy(
actual=gslbvserver_gslbservice_binding(),
client=client,
attribute_values_dict=binding_values,
readwrite_attrs=gslbvserver_gslbservice_binding_rw_attrs,
readonly_attrs=[],
)
configured_proxys[configured_binding['servicename']] = gslbvserver_service_binding_proxy
return configured_proxys
def sync_service_bindings(client, module):
actual = get_actual_service_bindings(client, module)
configured = get_configured_service_bindings(client, module)
# Delete extraneous
extraneous_service_bindings = list(set(actual.keys()) - set(configured.keys()))
for servicename in extraneous_service_bindings:
log('Deleting missing binding from service %s' % servicename)
binding = actual[servicename]
binding.name = module.params['name']
gslbvserver_gslbservice_binding.delete(client, binding)
# Recreate different
common_service_bindings = list(set(actual.keys()) & set(configured.keys()))
for servicename in common_service_bindings:
proxy = configured[servicename]
binding = actual[servicename]
if not proxy.has_equal_attributes(actual):
log('Recreating differing service binding %s' % servicename)
gslbvserver_gslbservice_binding.delete(client, binding)
proxy.add()
# Add missing
missing_service_bindings = list(set(configured.keys()) - set(actual.keys()))
for servicename in missing_service_bindings:
proxy = configured[servicename]
log('Adding missing service binding %s' % servicename)
proxy.add()
def service_bindings_identical(client, module):
actual_bindings = get_actual_service_bindings(client, module)
configured_proxys = get_configured_service_bindings(client, module)
actual_keyset = set(actual_bindings.keys())
configured_keyset = set(configured_proxys.keys())
symmetric_difference = actual_keyset ^ configured_keyset
if len(symmetric_difference) != 0:
return False
# Item for item equality test
for key, proxy in configured_proxys.items():
if key in actual_bindings.keys():
if not proxy.has_equal_attributes(actual_bindings[key]):
return False
# Fallthrough to True result
return True
def gslb_vserver_exists(client, module):
if gslbvserver.count_filtered(client, 'name:%s' % module.params['name']) > 0:
return True
else:
return False
def gslb_vserver_identical(client, module, gslb_vserver_proxy):
gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name'])
diff_dict = gslb_vserver_proxy.diff_object(gslb_vserver_list[0])
if len(diff_dict) != 0:
return False
else:
return True
def all_identical(client, module, gslb_vserver_proxy):
return (
gslb_vserver_identical(client, module, gslb_vserver_proxy) and
domain_bindings_identical(client, module) and
service_bindings_identical(client, module)
)
def diff_list(client, module, gslb_vserver_proxy):
gslb_vserver_list = gslbvserver.get_filtered(client, 'name:%s' % module.params['name'])
return gslb_vserver_proxy.diff_object(gslb_vserver_list[0])
def do_state_change(client, module, gslb_vserver_proxy):
if module.params['disabled']:
log('Disabling glsb_vserver')
result = gslbvserver.disable(client, gslb_vserver_proxy.actual)
else:
log('Enabling gslbvserver')
result = gslbvserver.enable(client, gslb_vserver_proxy.actual)
return result
def main():
module_specific_arguments = dict(
name=dict(type='str'),
servicetype=dict(
type='str',
choices=[
'HTTP',
'FTP',
'TCP',
'UDP',
'SSL',
'SSL_BRIDGE',
'SSL_TCP',
'NNTP',
'ANY',
'SIP_UDP',
'SIP_TCP',
'SIP_SSL',
'RADIUS',
'RDP',
'RTSP',
'MYSQL',
'MSSQL',
'ORACLE',
]
),
dnsrecordtype=dict(
type='str',
choices=[
'A',
'AAAA',
'CNAME',
'NAPTR',
]
),
lbmethod=dict(
type='str',
choices=[
'ROUNDROBIN',
'LEASTCONNECTION',
'LEASTRESPONSETIME',
'SOURCEIPHASH',
'LEASTBANDWIDTH',
'LEASTPACKETS',
'STATICPROXIMITY',
'RTT',
'CUSTOMLOAD',
]
),
backuplbmethod=dict(
type='str',
choices=[
'ROUNDROBIN',
'LEASTCONNECTION',
'LEASTRESPONSETIME',
'SOURCEIPHASH',
'LEASTBANDWIDTH',
'LEASTPACKETS',
'STATICPROXIMITY',
'RTT',
'CUSTOMLOAD',
]
),
netmask=dict(type='str'),
v6netmasklen=dict(type='float'),
tolerance=dict(type='float'),
persistencetype=dict(
type='str',
choices=[
'SOURCEIP',
'NONE',
]
),
persistenceid=dict(type='float'),
persistmask=dict(type='str'),
v6persistmasklen=dict(type='float'),
timeout=dict(type='float'),
mir=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
disableprimaryondown=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
dynamicweight=dict(
type='str',
choices=[
'SERVICECOUNT',
'SERVICEWEIGHT',
'DISABLED',
]
),
considereffectivestate=dict(
type='str',
choices=[
'NONE',
'STATE_ONLY',
]
),
comment=dict(type='str'),
somethod=dict(
type='str',
choices=[
'CONNECTION',
'DYNAMICCONNECTION',
'BANDWIDTH',
'HEALTH',
'NONE',
]
),
sopersistence=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
sopersistencetimeout=dict(type='float'),
sothreshold=dict(type='float'),
sobackupaction=dict(
type='str',
choices=[
'DROP',
'ACCEPT',
'REDIRECT',
]
),
appflowlog=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
domainname=dict(type='str'),
cookie_domain=dict(type='str'),
)
hand_inserted_arguments = dict(
domain_bindings=dict(type='list'),
service_bindings=dict(type='list'),
disabled=dict(
type='bool',
default=False,
),
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
argument_spec.update(hand_inserted_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
readwrite_attrs = [
'name',
'servicetype',
'dnsrecordtype',
'lbmethod',
'backuplbmethod',
'netmask',
'v6netmasklen',
'tolerance',
'persistencetype',
'persistenceid',
'persistmask',
'v6persistmasklen',
'timeout',
'mir',
'disableprimaryondown',
'dynamicweight',
'considereffectivestate',
'comment',
'somethod',
'sopersistence',
'sopersistencetimeout',
'sothreshold',
'sobackupaction',
'appflowlog',
'cookie_domain',
]
readonly_attrs = [
'curstate',
'status',
'lbrrreason',
'iscname',
'sitepersistence',
'totalservices',
'activeservices',
'statechangetimesec',
'statechangetimemsec',
'tickssincelaststatechange',
'health',
'policyname',
'priority',
'gotopriorityexpression',
'type',
'vsvrbindsvcip',
'vsvrbindsvcport',
'__count',
]
immutable_attrs = [
'name',
'servicetype',
]
transforms = {
'mir': [lambda v: v.upper()],
'disableprimaryondown': [lambda v: v.upper()],
'sopersistence': [lambda v: v.upper()],
'appflowlog': [lambda v: v.upper()],
}
# Instantiate config proxy
gslb_vserver_proxy = ConfigProxy(
actual=gslbvserver(),
client=client,
attribute_values_dict=module.params,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
immutable_attrs=immutable_attrs,
transforms=transforms,
)
try:
ensure_feature_is_enabled(client, 'GSLB')
# Apply appropriate state
if module.params['state'] == 'present':
log('Applying state present')
if not gslb_vserver_exists(client, module):
log('Creating object')
if not module.check_mode:
gslb_vserver_proxy.add()
sync_domain_bindings(client, module)
sync_service_bindings(client, module)
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not all_identical(client, module, gslb_vserver_proxy):
log('Entering update actions')
# Check if we try to change value of immutable attributes
if not gslb_vserver_identical(client, module, gslb_vserver_proxy):
log('Updating gslb vserver')
immutables_changed = get_immutables_intersection(gslb_vserver_proxy, diff_list(client, module, gslb_vserver_proxy).keys())
if immutables_changed != []:
module.fail_json(
msg='Cannot update immutable attributes %s' % (immutables_changed,),
diff=diff_list(client, module, gslb_vserver_proxy),
**module_result
)
if not module.check_mode:
gslb_vserver_proxy.update()
# Update domain bindings
if not domain_bindings_identical(client, module):
if not module.check_mode:
sync_domain_bindings(client, module)
# Update service bindings
if not service_bindings_identical(client, module):
if not module.check_mode:
sync_service_bindings(client, module)
module_result['changed'] = True
if not module.check_mode:
if module.params['save_config']:
client.save_config()
else:
module_result['changed'] = False
if not module.check_mode:
res = do_state_change(client, module, gslb_vserver_proxy)
if res.errorcode != 0:
msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message)
module.fail_json(msg=msg, **module_result)
# Sanity check for state
if not module.check_mode:
if not gslb_vserver_exists(client, module):
module.fail_json(msg='GSLB Vserver does not exist', **module_result)
if not gslb_vserver_identical(client, module, gslb_vserver_proxy):
module.fail_json(msg='GSLB Vserver differs from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result)
if not domain_bindings_identical(client, module):
module.fail_json(msg='Domain bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result)
if not service_bindings_identical(client, module):
module.fail_json(msg='Service bindings differ from configured', diff=diff_list(client, module, gslb_vserver_proxy), **module_result)
elif module.params['state'] == 'absent':
if gslb_vserver_exists(client, module):
if not module.check_mode:
gslb_vserver_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
if gslb_vserver_exists(client, module):
module.fail_json(msg='GSLB Vserver still exists', **module_result)
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,909 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_nitro_request
short_description: Issue Nitro API requests to a Netscaler instance.
description:
- Issue Nitro API requests to a Netscaler instance.
- This is intended to be a short hand for using the uri Ansible module to issue the raw HTTP requests directly.
- It provides consistent return values and has no other dependencies apart from the base Ansible runtime environment.
- This module is intended to run either on the Ansible control node or a bastion (jumpserver) with access to the actual Netscaler instance
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
nsip:
description:
- The IP address of the Netscaler or MAS instance where the Nitro API calls will be made.
- "The port can be specified with the colon C(:). E.g. C(192.168.1.1:555)."
nitro_user:
description:
- The username with which to authenticate to the Netscaler node.
required: true
nitro_pass:
description:
- The password with which to authenticate to the Netscaler node.
required: true
nitro_protocol:
choices: [ 'http', 'https' ]
default: http
description:
- Which protocol to use when accessing the Nitro API objects.
validate_certs:
description:
- If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates.
default: 'yes'
type: bool
nitro_auth_token:
description:
- The authentication token provided by the C(mas_login) operation. It is required when issuing Nitro API calls through a MAS proxy.
resource:
description:
- The type of resource we are operating on.
- It is required for all I(operation) values except C(mas_login) and C(save_config).
name:
description:
- The name of the resource we are operating on.
- "It is required for the following I(operation) values: C(update), C(get), C(delete)."
attributes:
description:
- The attributes of the Nitro object we are operating on.
- "It is required for the following I(operation) values: C(add), C(update), C(action)."
args:
description:
- A dictionary which defines the key arguments by which we will select the Nitro object to operate on.
- "It is required for the following I(operation) values: C(get_by_args), C('delete_by_args')."
filter:
description:
- A dictionary which defines the filter with which to refine the Nitro objects returned by the C(get_filtered) I(operation).
operation:
description:
- Define the Nitro operation that we want to perform.
choices:
- add
- update
- get
- get_by_args
- get_filtered
- get_all
- delete
- delete_by_args
- count
- mas_login
- save_config
- action
expected_nitro_errorcode:
description:
- A list of numeric values that signify that the operation was successful.
default: [0]
required: true
action:
description:
- The action to perform when the I(operation) value is set to C(action).
- Some common values for this parameter are C(enable), C(disable), C(rename).
instance_ip:
description:
- The IP address of the target Netscaler instance when issuing a Nitro request through a MAS proxy.
instance_name:
description:
- The name of the target Netscaler instance when issuing a Nitro request through a MAS proxy.
instance_id:
description:
- The id of the target Netscaler instance when issuing a Nitro request through a MAS proxy.
'''
EXAMPLES = '''
- name: Add a server
delegate_to: localhost
netscaler_nitro_request:
nsip: "{{ nsip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: add
resource: server
name: test-server-1
attributes:
name: test-server-1
ipaddress: 192.168.1.1
- name: Update server
delegate_to: localhost
netscaler_nitro_request:
nsip: "{{ nsip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: update
resource: server
name: test-server-1
attributes:
name: test-server-1
ipaddress: 192.168.1.2
- name: Get server
delegate_to: localhost
register: result
netscaler_nitro_request:
nsip: "{{ nsip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: get
resource: server
name: test-server-1
- name: Delete server
delegate_to: localhost
register: result
netscaler_nitro_request:
nsip: "{{ nsip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: delete
resource: server
name: test-server-1
- name: Rename server
delegate_to: localhost
netscaler_nitro_request:
nsip: "{{ nsip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: action
action: rename
resource: server
attributes:
name: test-server-1
newname: test-server-2
- name: Get server by args
delegate_to: localhost
register: result
netscaler_nitro_request:
nsip: "{{ nsip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: get_by_args
resource: server
args:
name: test-server-1
- name: Get server by filter
delegate_to: localhost
register: result
netscaler_nitro_request:
nsip: "{{ nsip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: get_filtered
resource: server
filter:
ipaddress: 192.168.1.2
# Doing a NITRO request through MAS.
# Requires to have an authentication token from the mas_login and used as the nitro_auth_token parameter
# Also nsip is the MAS address and the target Netscaler IP must be defined with instance_ip
# The rest of the task arguments remain the same as when issuing the NITRO request directly to a Netscaler instance.
- name: Do mas login
delegate_to: localhost
register: login_result
netscaler_nitro_request:
nsip: "{{ mas_ip }}"
nitro_user: "{{ nitro_user }}"
nitro_pass: "{{ nitro_pass }}"
operation: mas_login
- name: Add resource through MAS proxy
delegate_to: localhost
netscaler_nitro_request:
nsip: "{{ mas_ip }}"
nitro_auth_token: "{{ login_result.nitro_auth_token }}"
instance_ip: "{{ nsip }}"
operation: add
resource: server
name: test-server-1
attributes:
name: test-server-1
ipaddress: 192.168.1.7
'''
RETURN = '''
nitro_errorcode:
description: A numeric value containing the return code of the NITRO operation. When 0 the operation is successful. Any non zero value indicates an error.
returned: always
type: int
sample: 0
nitro_message:
description: A string containing a human readable explanation for the NITRO operation result.
returned: always
type: str
sample: Success
nitro_severity:
description: A string describing the severity of the NITRO operation error or NONE.
returned: always
type: str
sample: NONE
http_response_data:
description: A dictionary that contains all the HTTP response's data.
returned: always
type: dict
sample: "status: 200"
http_response_body:
description: A string with the actual HTTP response body content if existent. If there is no HTTP response body it is an empty string.
returned: always
type: str
sample: "{ errorcode: 0, message: Done, severity: NONE }"
nitro_object:
description: The object returned from the NITRO operation. This is applicable to the various get operations which return an object.
returned: when applicable
type: list
sample:
-
ipaddress: "192.168.1.8"
ipv6address: "NO"
maxbandwidth: "0"
name: "test-server-1"
port: 0
sp: "OFF"
state: "ENABLED"
nitro_auth_token:
description: The token returned by the C(mas_login) operation when successful.
returned: when applicable
type: str
sample: "##E8D7D74DDBD907EE579E8BB8FF4529655F22227C1C82A34BFC93C9539D66"
'''
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.basic import env_fallback
from ansible.module_utils.basic import AnsibleModule
import codecs
class NitroAPICaller(object):
_argument_spec = dict(
nsip=dict(
fallback=(env_fallback, ['NETSCALER_NSIP']),
),
nitro_user=dict(
fallback=(env_fallback, ['NETSCALER_NITRO_USER']),
),
nitro_pass=dict(
fallback=(env_fallback, ['NETSCALER_NITRO_PASS']),
no_log=True
),
nitro_protocol=dict(
choices=['http', 'https'],
fallback=(env_fallback, ['NETSCALER_NITRO_PROTOCOL']),
default='http'
),
validate_certs=dict(
default=True,
type='bool'
),
nitro_auth_token=dict(
type='str',
no_log=True
),
resource=dict(type='str'),
name=dict(type='str'),
attributes=dict(type='dict'),
args=dict(type='dict'),
filter=dict(type='dict'),
operation=dict(
type='str',
required=True,
choices=[
'add',
'update',
'get',
'get_by_args',
'get_filtered',
'get_all',
'delete',
'delete_by_args',
'count',
'mas_login',
# Actions
'save_config',
# Generic action handler
'action',
]
),
expected_nitro_errorcode=dict(
type='list',
default=[0],
),
action=dict(type='str'),
instance_ip=dict(type='str'),
instance_name=dict(type='str'),
instance_id=dict(type='str'),
)
def __init__(self):
self._module = AnsibleModule(
argument_spec=self._argument_spec,
supports_check_mode=False,
)
self._module_result = dict(
failed=False,
)
# Prepare the http headers according to module arguments
self._headers = {}
self._headers['Content-Type'] = 'application/json'
# Check for conflicting authentication methods
have_token = self._module.params['nitro_auth_token'] is not None
have_userpass = None not in (self._module.params['nitro_user'], self._module.params['nitro_pass'])
login_operation = self._module.params['operation'] == 'mas_login'
if have_token and have_userpass:
self.fail_module(msg='Cannot define both authentication token and username/password')
if have_token:
self._headers['Cookie'] = "NITRO_AUTH_TOKEN=%s" % self._module.params['nitro_auth_token']
if have_userpass and not login_operation:
self._headers['X-NITRO-USER'] = self._module.params['nitro_user']
self._headers['X-NITRO-PASS'] = self._module.params['nitro_pass']
# Do header manipulation when doing a MAS proxy call
if self._module.params['instance_ip'] is not None:
self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_IP'] = self._module.params['instance_ip']
elif self._module.params['instance_name'] is not None:
self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_NAME'] = self._module.params['instance_name']
elif self._module.params['instance_id'] is not None:
self._headers['_MPS_API_PROXY_MANAGED_INSTANCE_ID'] = self._module.params['instance_id']
def edit_response_data(self, r, info, result, success_status):
# Search for body in both http body and http data
if r is not None:
result['http_response_body'] = codecs.decode(r.read(), 'utf-8')
elif 'body' in info:
result['http_response_body'] = codecs.decode(info['body'], 'utf-8')
del info['body']
else:
result['http_response_body'] = ''
result['http_response_data'] = info
# Update the nitro_* parameters according to expected success_status
# Use explicit return values from http response or deduce from http status code
# Nitro return code in http data
result['nitro_errorcode'] = None
result['nitro_message'] = None
result['nitro_severity'] = None
if result['http_response_body'] != '':
try:
data = self._module.from_json(result['http_response_body'])
except ValueError:
data = {}
result['nitro_errorcode'] = data.get('errorcode')
result['nitro_message'] = data.get('message')
result['nitro_severity'] = data.get('severity')
# If we do not have the nitro errorcode from body deduce it from the http status
if result['nitro_errorcode'] is None:
# HTTP status failed
if result['http_response_data'].get('status') != success_status:
result['nitro_errorcode'] = -1
result['nitro_message'] = result['http_response_data'].get('msg', 'HTTP status %s' % result['http_response_data']['status'])
result['nitro_severity'] = 'ERROR'
# HTTP status succeeded
else:
result['nitro_errorcode'] = 0
result['nitro_message'] = 'Success'
result['nitro_severity'] = 'NONE'
def handle_get_return_object(self, result):
result['nitro_object'] = []
if result['nitro_errorcode'] == 0:
if result['http_response_body'] != '':
data = self._module.from_json(result['http_response_body'])
if self._module.params['resource'] in data:
result['nitro_object'] = data[self._module.params['resource']]
else:
del result['nitro_object']
def fail_module(self, msg, **kwargs):
self._module_result['failed'] = True
self._module_result['changed'] = False
self._module_result.update(kwargs)
self._module_result['msg'] = msg
self._module.fail_json(**self._module_result)
def main(self):
if self._module.params['operation'] == 'add':
result = self.add()
if self._module.params['operation'] == 'update':
result = self.update()
if self._module.params['operation'] == 'delete':
result = self.delete()
if self._module.params['operation'] == 'delete_by_args':
result = self.delete_by_args()
if self._module.params['operation'] == 'get':
result = self.get()
if self._module.params['operation'] == 'get_by_args':
result = self.get_by_args()
if self._module.params['operation'] == 'get_filtered':
result = self.get_filtered()
if self._module.params['operation'] == 'get_all':
result = self.get_all()
if self._module.params['operation'] == 'count':
result = self.count()
if self._module.params['operation'] == 'mas_login':
result = self.mas_login()
if self._module.params['operation'] == 'action':
result = self.action()
if self._module.params['operation'] == 'save_config':
result = self.save_config()
if result['nitro_errorcode'] not in self._module.params['expected_nitro_errorcode']:
self.fail_module(msg='NITRO Failure', **result)
self._module_result.update(result)
self._module.exit_json(**self._module_result)
def exit_module(self):
self._module.exit_json()
def add(self):
# Check if required attributes are present
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['attributes'] is None:
self.fail_module(msg='NITRO resource attributes are undefined.')
url = '%s://%s/nitro/v1/config/%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
)
data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']})
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
data=data,
method='POST',
)
result = {}
self.edit_response_data(r, info, result, success_status=201)
if result['nitro_errorcode'] == 0:
self._module_result['changed'] = True
else:
self._module_result['changed'] = False
return result
def update(self):
# Check if required attributes are arguments present
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['name'] is None:
self.fail_module(msg='NITRO resource name is undefined.')
if self._module.params['attributes'] is None:
self.fail_module(msg='NITRO resource attributes are undefined.')
url = '%s://%s/nitro/v1/config/%s/%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
self._module.params['name'],
)
data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']})
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
data=data,
method='PUT',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
if result['nitro_errorcode'] == 0:
self._module_result['changed'] = True
else:
self._module_result['changed'] = False
return result
def get(self):
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['name'] is None:
self.fail_module(msg='NITRO resource name is undefined.')
url = '%s://%s/nitro/v1/config/%s/%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
self._module.params['name'],
)
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
method='GET',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
self.handle_get_return_object(result)
self._module_result['changed'] = False
return result
def get_by_args(self):
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['args'] is None:
self.fail_module(msg='NITRO args is undefined.')
url = '%s://%s/nitro/v1/config/%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
)
args_dict = self._module.params['args']
args = ','.join(['%s:%s' % (k, args_dict[k]) for k in args_dict])
args = 'args=' + args
url = '?'.join([url, args])
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
method='GET',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
self.handle_get_return_object(result)
self._module_result['changed'] = False
return result
def get_filtered(self):
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['filter'] is None:
self.fail_module(msg='NITRO filter is undefined.')
keys = list(self._module.params['filter'].keys())
filter_key = keys[0]
filter_value = self._module.params['filter'][filter_key]
filter_str = '%s:%s' % (filter_key, filter_value)
url = '%s://%s/nitro/v1/config/%s?filter=%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
filter_str,
)
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
method='GET',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
self.handle_get_return_object(result)
self._module_result['changed'] = False
return result
def get_all(self):
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
url = '%s://%s/nitro/v1/config/%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
)
print('headers %s' % self._headers)
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
method='GET',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
self.handle_get_return_object(result)
self._module_result['changed'] = False
return result
def delete(self):
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['name'] is None:
self.fail_module(msg='NITRO resource is undefined.')
# Deletion by name takes precedence over deletion by attributes
url = '%s://%s/nitro/v1/config/%s/%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
self._module.params['name'],
)
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
method='DELETE',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
if result['nitro_errorcode'] == 0:
self._module_result['changed'] = True
else:
self._module_result['changed'] = False
return result
def delete_by_args(self):
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['args'] is None:
self.fail_module(msg='NITRO args is undefined.')
url = '%s://%s/nitro/v1/config/%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
)
args_dict = self._module.params['args']
args = ','.join(['%s:%s' % (k, args_dict[k]) for k in args_dict])
args = 'args=' + args
url = '?'.join([url, args])
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
method='DELETE',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
if result['nitro_errorcode'] == 0:
self._module_result['changed'] = True
else:
self._module_result['changed'] = False
return result
def count(self):
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
url = '%s://%s/nitro/v1/config/%s?count=yes' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
)
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
method='GET',
)
result = {}
self.edit_response_data(r, info, result)
if result['http_response_body'] != '':
data = self._module.from_json(result['http_response_body'])
result['nitro_errorcode'] = data['errorcode']
result['nitro_message'] = data['message']
result['nitro_severity'] = data['severity']
if self._module.params['resource'] in data:
result['nitro_count'] = data[self._module.params['resource']][0]['__count']
self._module_result['changed'] = False
return result
def action(self):
# Check if required attributes are present
if self._module.params['resource'] is None:
self.fail_module(msg='NITRO resource is undefined.')
if self._module.params['attributes'] is None:
self.fail_module(msg='NITRO resource attributes are undefined.')
if self._module.params['action'] is None:
self.fail_module(msg='NITRO action is undefined.')
url = '%s://%s/nitro/v1/config/%s?action=%s' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
self._module.params['resource'],
self._module.params['action'],
)
data = self._module.jsonify({self._module.params['resource']: self._module.params['attributes']})
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
data=data,
method='POST',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
if result['nitro_errorcode'] == 0:
self._module_result['changed'] = True
else:
self._module_result['changed'] = False
return result
def mas_login(self):
url = '%s://%s/nitro/v1/config/login' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
)
login_credentials = {
'login': {
'username': self._module.params['nitro_user'],
'password': self._module.params['nitro_pass'],
}
}
data = 'object=\n%s' % self._module.jsonify(login_credentials)
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
data=data,
method='POST',
)
print(r, info)
result = {}
self.edit_response_data(r, info, result, success_status=200)
if result['nitro_errorcode'] == 0:
body_data = self._module.from_json(result['http_response_body'])
result['nitro_auth_token'] = body_data['login'][0]['sessionid']
self._module_result['changed'] = False
return result
def save_config(self):
url = '%s://%s/nitro/v1/config/nsconfig?action=save' % (
self._module.params['nitro_protocol'],
self._module.params['nsip'],
)
data = self._module.jsonify(
{
'nsconfig': {},
}
)
r, info = fetch_url(
self._module,
url=url,
headers=self._headers,
data=data,
method='POST',
)
result = {}
self.edit_response_data(r, info, result, success_status=200)
self._module_result['changed'] = False
return result
def main():
nitro_api_caller = NitroAPICaller()
nitro_api_caller.main()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,177 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_save_config
short_description: Save Netscaler configuration.
description:
- This module unconditionally saves the configuration on the target netscaler node.
- This module does not support check mode.
- This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance.
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
nsip:
description:
- The ip address of the netscaler appliance where the nitro API calls will be made.
- "The port can be specified with the colon (:). E.g. C(192.168.1.1:555)."
required: True
nitro_user:
description:
- The username with which to authenticate to the netscaler node.
required: True
nitro_pass:
description:
- The password with which to authenticate to the netscaler node.
required: True
nitro_protocol:
choices: [ 'http', 'https' ]
default: http
description:
- Which protocol to use when accessing the nitro API objects.
validate_certs:
description:
- If C(no), SSL certificates will not be validated. This should only be used on personally controlled sites using self-signed certificates.
required: false
default: 'yes'
type: bool
nitro_timeout:
description:
- Time in seconds until a timeout error is thrown when establishing a new session with Netscaler.
default: 310
requirements:
- nitro python sdk
'''
EXAMPLES = '''
---
- name: Save netscaler configuration
delegate_to: localhost
netscaler_save_config:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
- name: Setup server without saving configuration
delegate_to: localhost
notify: Save configuration
netscaler_server:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
save_config: no
name: server-1
ipaddress: 192.168.1.1
# Under playbook's handlers
- name: Save configuration
delegate_to: localhost
netscaler_save_config:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: ['message 1', 'message 2']
msg:
description: Message detailing the failure reason
returned: failure
type: str
sample: "Action does not exist"
'''
import copy
try:
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import get_nitro_client, log, loglines, netscaler_common_arguments
def main():
argument_spec = copy.deepcopy(netscaler_common_arguments)
# Delete common arguments irrelevant to this module
del argument_spec['state']
del argument_spec['save_config']
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=False,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
try:
log('Saving configuration')
client.save_config()
except nitro_exception as e:
msg = "nitro exception errorcode=" + str(e.errorcode) + ",message=" + e.message
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,401 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_server
short_description: Manage server configuration
description:
- Manage server entities configuration.
- This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance.
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
name:
description:
- "Name for the server."
- >-
Must begin with an ASCII alphabetic or underscore C(_) character, and must contain only ASCII
alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals C(=), and hyphen C(-)
characters.
- "Can be changed after the name is created."
- "Minimum length = 1"
ipaddress:
description:
- >-
IPv4 or IPv6 address of the server. If you create an IP address based server, you can specify the
name of the server, instead of its IP address, when creating a service. Note: If you do not create a
server entry, the server IP address that you enter when you create a service becomes the name of the
server.
domain:
description:
- "Domain name of the server. For a domain based configuration, you must create the server first."
- "Minimum length = 1"
translationip:
description:
- "IP address used to transform the server's DNS-resolved IP address."
translationmask:
description:
- "The netmask of the translation ip."
domainresolveretry:
description:
- >-
Time, in seconds, for which the NetScaler appliance must wait, after DNS resolution fails, before
sending the next DNS query to resolve the domain name.
- "Minimum value = C(5)"
- "Maximum value = C(20939)"
default: 5
ipv6address:
description:
- >-
Support IPv6 addressing mode. If you configure a server with the IPv6 addressing mode, you cannot use
the server in the IPv4 addressing mode.
default: false
type: bool
comment:
description:
- "Any information about the server."
td:
description:
- >-
Integer value that uniquely identifies the traffic domain in which you want to configure the entity.
If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID
of 0.
- "Minimum value = C(0)"
- "Maximum value = C(4094)"
graceful:
description:
- >-
Shut down gracefully, without accepting any new connections, and disabling each service when all of
its connections are closed.
- This option is meaningful only when setting the I(disabled) option to C(true)
type: bool
delay:
description:
- Time, in seconds, after which all the services configured on the server are disabled.
- This option is meaningful only when setting the I(disabled) option to C(true)
disabled:
description:
- When set to C(true) the server state will be set to C(disabled).
- When set to C(false) the server state will be set to C(enabled).
- >-
Note that due to limitations of the underlying NITRO API a C(disabled) state change alone
does not cause the module result to report a changed status.
type: bool
default: false
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
- name: Setup server
delegate_to: localhost
netscaler_server:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
state: present
name: server-1
ipaddress: 192.168.1.1
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: ['message 1', 'message 2']
msg:
description: Message detailing the failure reason
returned: failure
type: str
sample: "Action does not exist"
diff:
description: List of differences between the actual configured object and the configuration specified in the module
returned: failure
type: dict
sample: { 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }
'''
try:
from nssrc.com.citrix.netscaler.nitro.resource.config.basic.server import server
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import ConfigProxy, get_nitro_client, netscaler_common_arguments, log, loglines, \
get_immutables_intersection
def server_exists(client, module):
log('Checking if server exists')
if server.count_filtered(client, 'name:%s' % module.params['name']) > 0:
return True
else:
return False
def server_identical(client, module, server_proxy):
log('Checking if configured server is identical')
if server.count_filtered(client, 'name:%s' % module.params['name']) == 0:
return False
diff = diff_list(client, module, server_proxy)
# Remove options that are not present in nitro server object
# These are special options relevant to the disabled action
for option in ['graceful', 'delay']:
if option in diff:
del diff[option]
if diff == {}:
return True
else:
return False
def diff_list(client, module, server_proxy):
ret_val = server_proxy.diff_object(server.get_filtered(client, 'name:%s' % module.params['name'])[0]),
return ret_val[0]
def do_state_change(client, module, server_proxy):
if module.params['disabled']:
log('Disabling server')
result = server.disable(client, server_proxy.actual)
else:
log('Enabling server')
result = server.enable(client, server_proxy.actual)
return result
def main():
module_specific_arguments = dict(
name=dict(type='str'),
ipaddress=dict(type='str'),
domain=dict(type='str'),
translationip=dict(type='str'),
translationmask=dict(type='str'),
domainresolveretry=dict(type='int'),
ipv6address=dict(
type='bool',
default=False
),
comment=dict(type='str'),
td=dict(type='float'),
graceful=dict(type='bool'),
delay=dict(type='float')
)
hand_inserted_arguments = dict(
disabled=dict(
type='bool',
default=False,
),
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
argument_spec.update(hand_inserted_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
# Instantiate Server Config object
readwrite_attrs = [
'name',
'ipaddress',
'domain',
'translationip',
'translationmask',
'domainresolveretry',
'ipv6address',
'graceful',
'delay',
'comment',
'td',
]
readonly_attrs = [
'statechangetimesec',
'tickssincelaststatechange',
'autoscale',
'customserverid',
'monthreshold',
'maxclient',
'maxreq',
'maxbandwidth',
'usip',
'cka',
'tcpb',
'cmp',
'clttimeout',
'svrtimeout',
'cipheader',
'cip',
'cacheable',
'sc',
'sp',
'downstateflush',
'appflowlog',
'boundtd',
'__count',
]
immutable_attrs = [
'name',
'domain',
'ipv6address',
'td',
]
transforms = {
'graceful': ['bool_yes_no'],
'ipv6address': ['bool_yes_no'],
}
server_proxy = ConfigProxy(
actual=server(),
client=client,
attribute_values_dict=module.params,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
immutable_attrs=immutable_attrs,
transforms=transforms,
)
try:
# Apply appropriate state
if module.params['state'] == 'present':
log('Applying actions for state present')
if not server_exists(client, module):
if not module.check_mode:
server_proxy.add()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not server_identical(client, module, server_proxy):
# Check if we try to change value of immutable attributes
immutables_changed = get_immutables_intersection(server_proxy, diff_list(client, module, server_proxy).keys())
if immutables_changed != []:
msg = 'Cannot update immutable attributes %s' % (immutables_changed,)
module.fail_json(msg=msg, diff=diff_list(client, module, server_proxy), **module_result)
if not module.check_mode:
server_proxy.update()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
if not module.check_mode:
res = do_state_change(client, module, server_proxy)
if res.errorcode != 0:
msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message)
module.fail_json(msg=msg, **module_result)
# Sanity check for result
log('Sanity checks for state present')
if not module.check_mode:
if not server_exists(client, module):
module.fail_json(msg='Server does not seem to exist', **module_result)
if not server_identical(client, module, server_proxy):
module.fail_json(
msg='Server is not configured according to parameters given',
diff=diff_list(client, module, server_proxy),
**module_result
)
elif module.params['state'] == 'absent':
log('Applying actions for state absent')
if server_exists(client, module):
if not module.check_mode:
server_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for result
log('Sanity checks for state absent')
if not module.check_mode:
if server_exists(client, module):
module.fail_json(msg='Server seems to be present', **module_result)
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,964 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_service
short_description: Manage service configuration in Netscaler
description:
- Manage service configuration in Netscaler.
- This module allows the creation, deletion and modification of Netscaler services.
- This module is intended to run either on the ansible control node or a bastion (jumpserver) with access to the actual netscaler instance.
- This module supports check mode.
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
name:
description:
- >-
Name for the service. Must begin with an ASCII alphabetic or underscore C(_) character, and must
contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ), colon C(:), at C(@), equals
C(=), and hyphen C(-) characters. Cannot be changed after the service has been created.
- "Minimum length = 1"
ip:
description:
- "IP to assign to the service."
- "Minimum length = 1"
servername:
description:
- "Name of the server that hosts the service."
- "Minimum length = 1"
servicetype:
choices:
- 'HTTP'
- 'FTP'
- 'TCP'
- 'UDP'
- 'SSL'
- 'SSL_BRIDGE'
- 'SSL_TCP'
- 'DTLS'
- 'NNTP'
- 'RPCSVR'
- 'DNS'
- 'ADNS'
- 'SNMP'
- 'RTSP'
- 'DHCPRA'
- 'ANY'
- 'SIP_UDP'
- 'SIP_TCP'
- 'SIP_SSL'
- 'DNS_TCP'
- 'ADNS_TCP'
- 'MYSQL'
- 'MSSQL'
- 'ORACLE'
- 'RADIUS'
- 'RADIUSListener'
- 'RDP'
- 'DIAMETER'
- 'SSL_DIAMETER'
- 'TFTP'
- 'SMPP'
- 'PPTP'
- 'GRE'
- 'SYSLOGTCP'
- 'SYSLOGUDP'
- 'FIX'
- 'SSL_FIX'
description:
- "Protocol in which data is exchanged with the service."
port:
description:
- "Port number of the service."
- "Range 1 - 65535"
- "* in CLI is represented as 65535 in NITRO API"
cleartextport:
description:
- >-
Port to which clear text data must be sent after the appliance decrypts incoming SSL traffic.
Applicable to transparent SSL services.
- "Minimum value = 1"
cachetype:
choices:
- 'TRANSPARENT'
- 'REVERSE'
- 'FORWARD'
description:
- "Cache type supported by the cache server."
maxclient:
description:
- "Maximum number of simultaneous open connections to the service."
- "Minimum value = 0"
- "Maximum value = 4294967294"
healthmonitor:
description:
- "Monitor the health of this service"
default: yes
type: bool
maxreq:
description:
- "Maximum number of requests that can be sent on a persistent connection to the service."
- "Note: Connection requests beyond this value are rejected."
- "Minimum value = 0"
- "Maximum value = 65535"
cacheable:
description:
- "Use the transparent cache redirection virtual server to forward requests to the cache server."
- "Note: Do not specify this parameter if you set the Cache Type parameter."
default: no
type: bool
cip:
choices:
- 'enabled'
- 'disabled'
description:
- >-
Before forwarding a request to the service, insert an HTTP header with the client's IPv4 or IPv6
address as its value. Used if the server needs the client's IP address for security, accounting, or
other purposes, and setting the Use Source IP parameter is not a viable option.
cipheader:
description:
- >-
Name for the HTTP header whose value must be set to the IP address of the client. Used with the
Client IP parameter. If you set the Client IP parameter, and you do not specify a name for the
header, the appliance uses the header name specified for the global Client IP Header parameter (the
cipHeader parameter in the set ns param CLI command or the Client IP Header parameter in the
Configure HTTP Parameters dialog box at System > Settings > Change HTTP parameters). If the global
Client IP Header parameter is not specified, the appliance inserts a header with the name
"client-ip.".
- "Minimum length = 1"
usip:
description:
- >-
Use the client's IP address as the source IP address when initiating a connection to the server. When
creating a service, if you do not set this parameter, the service inherits the global Use Source IP
setting (available in the enable ns mode and disable ns mode CLI commands, or in the System >
Settings > Configure modes > Configure Modes dialog box). However, you can override this setting
after you create the service.
type: bool
pathmonitor:
description:
- "Path monitoring for clustering."
pathmonitorindv:
description:
- "Individual Path monitoring decisions."
useproxyport:
description:
- >-
Use the proxy port as the source port when initiating connections with the server. With the NO
setting, the client-side connection port is used as the source port for the server-side connection.
- "Note: This parameter is available only when the Use Source IP (USIP) parameter is set to YES."
type: bool
sp:
description:
- "Enable surge protection for the service."
type: bool
rtspsessionidremap:
description:
- "Enable RTSP session ID mapping for the service."
default: off
type: bool
clttimeout:
description:
- "Time, in seconds, after which to terminate an idle client connection."
- "Minimum value = 0"
- "Maximum value = 31536000"
svrtimeout:
description:
- "Time, in seconds, after which to terminate an idle server connection."
- "Minimum value = 0"
- "Maximum value = 31536000"
customserverid:
description:
- >-
Unique identifier for the service. Used when the persistency type for the virtual server is set to
Custom Server ID.
default: 'None'
serverid:
description:
- "The identifier for the service. This is used when the persistency type is set to Custom Server ID."
cka:
description:
- "Enable client keep-alive for the service."
type: bool
tcpb:
description:
- "Enable TCP buffering for the service."
type: bool
cmp:
description:
- "Enable compression for the service."
type: bool
maxbandwidth:
description:
- "Maximum bandwidth, in Kbps, allocated to the service."
- "Minimum value = 0"
- "Maximum value = 4294967287"
accessdown:
description:
- >-
Use Layer 2 mode to bridge the packets sent to this service if it is marked as DOWN. If the service
is DOWN, and this parameter is disabled, the packets are dropped.
default: no
type: bool
monthreshold:
description:
- >-
Minimum sum of weights of the monitors that are bound to this service. Used to determine whether to
mark a service as UP or DOWN.
- "Minimum value = 0"
- "Maximum value = 65535"
downstateflush:
choices:
- 'enabled'
- 'disabled'
description:
- >-
Flush all active transactions associated with a service whose state transitions from UP to DOWN. Do
not enable this option for applications that must complete their transactions.
tcpprofilename:
description:
- "Name of the TCP profile that contains TCP configuration settings for the service."
- "Minimum length = 1"
- "Maximum length = 127"
httpprofilename:
description:
- "Name of the HTTP profile that contains HTTP configuration settings for the service."
- "Minimum length = 1"
- "Maximum length = 127"
hashid:
description:
- >-
A numerical identifier that can be used by hash based load balancing methods. Must be unique for each
service.
- "Minimum value = 1"
comment:
description:
- "Any information about the service."
appflowlog:
choices:
- 'enabled'
- 'disabled'
description:
- "Enable logging of AppFlow information."
netprofile:
description:
- "Network profile to use for the service."
- "Minimum length = 1"
- "Maximum length = 127"
td:
description:
- >-
Integer value that uniquely identifies the traffic domain in which you want to configure the entity.
If you do not specify an ID, the entity becomes part of the default traffic domain, which has an ID
of 0.
- "Minimum value = 0"
- "Maximum value = 4094"
processlocal:
choices:
- 'enabled'
- 'disabled'
description:
- >-
By turning on this option packets destined to a service in a cluster will not under go any steering.
Turn this option for single packet request response mode or when the upstream device is performing a
proper RSS for connection based distribution.
dnsprofilename:
description:
- >-
Name of the DNS profile to be associated with the service. DNS profile properties will applied to the
transactions processed by a service. This parameter is valid only for ADNS and ADNS-TCP services.
- "Minimum length = 1"
- "Maximum length = 127"
ipaddress:
description:
- "The new IP address of the service."
graceful:
description:
- >-
Shut down gracefully, not accepting any new connections, and disabling the service when all of its
connections are closed.
default: no
type: bool
monitor_bindings:
description:
- A list of load balancing monitors to bind to this service.
- Each monitor entry is a dictionary which may contain the following options.
- Note that if not using the built in monitors they must first be setup.
suboptions:
monitorname:
description:
- Name of the monitor.
weight:
description:
- Weight to assign to the binding between the monitor and service.
dup_state:
choices:
- 'enabled'
- 'disabled'
description:
- State of the monitor.
- The state setting for a monitor of a given type affects all monitors of that type.
- For example, if an HTTP monitor is enabled, all HTTP monitors on the appliance are (or remain) enabled.
- If an HTTP monitor is disabled, all HTTP monitors on the appliance are disabled.
dup_weight:
description:
- Weight to assign to the binding between the monitor and service.
disabled:
description:
- When set to C(yes) the service state will be set to DISABLED.
- When set to C(no) the service state will be set to ENABLED.
- >-
Note that due to limitations of the underlying NITRO API a C(disabled) state change alone
does not cause the module result to report a changed status.
type: bool
default: false
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
# Monitor monitor-1 must have been already setup
- name: Setup http service
gather_facts: False
delegate_to: localhost
netscaler_service:
nsip: 172.18.0.2
nitro_user: nsroot
nitro_pass: nsroot
state: present
name: service-http-1
servicetype: HTTP
ipaddress: 10.78.0.1
port: 80
monitor_bindings:
- monitor-1
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: "['message 1', 'message 2']"
diff:
description: A dictionary with a list of differences between the actual configured object and the configuration specified in the module
returned: failure
type: dict
sample: "{ 'clttimeout': 'difference. ours: (float) 10.0 other: (float) 20.0' }"
'''
import copy
try:
from nssrc.com.citrix.netscaler.nitro.resource.config.basic.service import service
from nssrc.com.citrix.netscaler.nitro.resource.config.basic.service_lbmonitor_binding import service_lbmonitor_binding
from nssrc.com.citrix.netscaler.nitro.resource.config.lb.lbmonitor_service_binding import lbmonitor_service_binding
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import (ConfigProxy, get_nitro_client, netscaler_common_arguments, log, loglines,
get_immutables_intersection)
def service_exists(client, module):
if service.count_filtered(client, 'name:%s' % module.params['name']) > 0:
return True
else:
return False
def service_identical(client, module, service_proxy):
service_list = service.get_filtered(client, 'name:%s' % module.params['name'])
diff_dict = service_proxy.diff_object(service_list[0])
# the actual ip address is stored in the ipaddress attribute
# of the retrieved object
if 'ip' in diff_dict:
del diff_dict['ip']
if 'graceful' in diff_dict:
del diff_dict['graceful']
if len(diff_dict) == 0:
return True
else:
return False
def diff(client, module, service_proxy):
service_list = service.get_filtered(client, 'name:%s' % module.params['name'])
diff_object = service_proxy.diff_object(service_list[0])
if 'ip' in diff_object:
del diff_object['ip']
return diff_object
def get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs):
bindings = {}
if module.params['monitor_bindings'] is not None:
for binding in module.params['monitor_bindings']:
attribute_values_dict = copy.deepcopy(binding)
# attribute_values_dict['servicename'] = module.params['name']
attribute_values_dict['servicegroupname'] = module.params['name']
binding_proxy = ConfigProxy(
actual=lbmonitor_service_binding(),
client=client,
attribute_values_dict=attribute_values_dict,
readwrite_attrs=monitor_bindings_rw_attrs,
)
key = binding_proxy.monitorname
bindings[key] = binding_proxy
return bindings
def get_actual_monitor_bindings(client, module):
bindings = {}
if service_lbmonitor_binding.count(client, module.params['name']) == 0:
return bindings
# Fallthrough to rest of execution
for binding in service_lbmonitor_binding.get(client, module.params['name']):
# Excluding default monitors since we cannot operate on them
if binding.monitor_name in ('tcp-default', 'ping-default'):
continue
key = binding.monitor_name
actual = lbmonitor_service_binding()
actual.weight = binding.weight
actual.monitorname = binding.monitor_name
actual.dup_weight = binding.dup_weight
actual.servicename = module.params['name']
bindings[key] = actual
return bindings
def monitor_bindings_identical(client, module, monitor_bindings_rw_attrs):
configured_proxys = get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs)
actual_bindings = get_actual_monitor_bindings(client, module)
configured_key_set = set(configured_proxys.keys())
actual_key_set = set(actual_bindings.keys())
symmetrical_diff = configured_key_set ^ actual_key_set
if len(symmetrical_diff) > 0:
return False
# Compare key to key
for monitor_name in configured_key_set:
proxy = configured_proxys[monitor_name]
actual = actual_bindings[monitor_name]
diff_dict = proxy.diff_object(actual)
if 'servicegroupname' in diff_dict:
if proxy.servicegroupname == actual.servicename:
del diff_dict['servicegroupname']
if len(diff_dict) > 0:
return False
# Fallthrought to success
return True
def sync_monitor_bindings(client, module, monitor_bindings_rw_attrs):
configured_proxys = get_configured_monitor_bindings(client, module, monitor_bindings_rw_attrs)
actual_bindings = get_actual_monitor_bindings(client, module)
configured_keyset = set(configured_proxys.keys())
actual_keyset = set(actual_bindings.keys())
# Delete extra
delete_keys = list(actual_keyset - configured_keyset)
for monitor_name in delete_keys:
log('Deleting binding for monitor %s' % monitor_name)
lbmonitor_service_binding.delete(client, actual_bindings[monitor_name])
# Delete and re-add modified
common_keyset = list(configured_keyset & actual_keyset)
for monitor_name in common_keyset:
proxy = configured_proxys[monitor_name]
actual = actual_bindings[monitor_name]
if not proxy.has_equal_attributes(actual):
log('Deleting and re adding binding for monitor %s' % monitor_name)
lbmonitor_service_binding.delete(client, actual)
proxy.add()
# Add new
new_keys = list(configured_keyset - actual_keyset)
for monitor_name in new_keys:
log('Adding binding for monitor %s' % monitor_name)
configured_proxys[monitor_name].add()
def all_identical(client, module, service_proxy, monitor_bindings_rw_attrs):
return service_identical(client, module, service_proxy) and monitor_bindings_identical(client, module, monitor_bindings_rw_attrs)
def do_state_change(client, module, service_proxy):
if module.params['disabled']:
log('Disabling service')
result = service.disable(client, service_proxy.actual)
else:
log('Enabling service')
result = service.enable(client, service_proxy.actual)
return result
def main():
module_specific_arguments = dict(
name=dict(type='str'),
ip=dict(type='str'),
servername=dict(type='str'),
servicetype=dict(
type='str',
choices=[
'HTTP',
'FTP',
'TCP',
'UDP',
'SSL',
'SSL_BRIDGE',
'SSL_TCP',
'DTLS',
'NNTP',
'RPCSVR',
'DNS',
'ADNS',
'SNMP',
'RTSP',
'DHCPRA',
'ANY',
'SIP_UDP',
'SIP_TCP',
'SIP_SSL',
'DNS_TCP',
'ADNS_TCP',
'MYSQL',
'MSSQL',
'ORACLE',
'RADIUS',
'RADIUSListener',
'RDP',
'DIAMETER',
'SSL_DIAMETER',
'TFTP',
'SMPP',
'PPTP',
'GRE',
'SYSLOGTCP',
'SYSLOGUDP',
'FIX',
'SSL_FIX'
]
),
port=dict(type='int'),
cleartextport=dict(type='int'),
cachetype=dict(
type='str',
choices=[
'TRANSPARENT',
'REVERSE',
'FORWARD',
]
),
maxclient=dict(type='float'),
healthmonitor=dict(
type='bool',
default=True,
),
maxreq=dict(type='float'),
cacheable=dict(
type='bool',
default=False,
),
cip=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
cipheader=dict(type='str'),
usip=dict(type='bool'),
useproxyport=dict(type='bool'),
sp=dict(type='bool'),
rtspsessionidremap=dict(
type='bool',
default=False,
),
clttimeout=dict(type='float'),
svrtimeout=dict(type='float'),
customserverid=dict(
type='str',
default='None',
),
cka=dict(type='bool'),
tcpb=dict(type='bool'),
cmp=dict(type='bool'),
maxbandwidth=dict(type='float'),
accessdown=dict(
type='bool',
default=False
),
monthreshold=dict(type='float'),
downstateflush=dict(
type='str',
choices=[
'enabled',
'disabled',
],
),
tcpprofilename=dict(type='str'),
httpprofilename=dict(type='str'),
hashid=dict(type='float'),
comment=dict(type='str'),
appflowlog=dict(
type='str',
choices=[
'enabled',
'disabled',
],
),
netprofile=dict(type='str'),
processlocal=dict(
type='str',
choices=[
'enabled',
'disabled',
],
),
dnsprofilename=dict(type='str'),
ipaddress=dict(type='str'),
graceful=dict(
type='bool',
default=False,
),
)
hand_inserted_arguments = dict(
monitor_bindings=dict(type='list'),
disabled=dict(
type='bool',
default=False,
),
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
argument_spec.update(hand_inserted_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
# Fallthrough to rest of execution
# Instantiate Service Config object
readwrite_attrs = [
'name',
'ip',
'servername',
'servicetype',
'port',
'cleartextport',
'cachetype',
'maxclient',
'healthmonitor',
'maxreq',
'cacheable',
'cip',
'cipheader',
'usip',
'useproxyport',
'sp',
'rtspsessionidremap',
'clttimeout',
'svrtimeout',
'customserverid',
'cka',
'tcpb',
'cmp',
'maxbandwidth',
'accessdown',
'monthreshold',
'downstateflush',
'tcpprofilename',
'httpprofilename',
'hashid',
'comment',
'appflowlog',
'netprofile',
'processlocal',
'dnsprofilename',
'ipaddress',
'graceful',
]
readonly_attrs = [
'numofconnections',
'policyname',
'serviceconftype',
'serviceconftype2',
'value',
'gslb',
'dup_state',
'publicip',
'publicport',
'svrstate',
'monitor_state',
'monstatcode',
'lastresponse',
'responsetime',
'riseapbrstatsmsgcode2',
'monstatparam1',
'monstatparam2',
'monstatparam3',
'statechangetimesec',
'statechangetimemsec',
'tickssincelaststatechange',
'stateupdatereason',
'clmonowner',
'clmonview',
'serviceipstr',
'oracleserverversion',
]
immutable_attrs = [
'name',
'ip',
'servername',
'servicetype',
'port',
'cleartextport',
'cachetype',
'cipheader',
'serverid',
'state',
'td',
'monitor_name_svc',
'riseapbrstatsmsgcode',
'all',
'Internal',
'newname',
]
transforms = {
'pathmonitorindv': ['bool_yes_no'],
'cacheable': ['bool_yes_no'],
'cka': ['bool_yes_no'],
'pathmonitor': ['bool_yes_no'],
'tcpb': ['bool_yes_no'],
'sp': ['bool_on_off'],
'graceful': ['bool_yes_no'],
'usip': ['bool_yes_no'],
'healthmonitor': ['bool_yes_no'],
'useproxyport': ['bool_yes_no'],
'rtspsessionidremap': ['bool_on_off'],
'accessdown': ['bool_yes_no'],
'cmp': ['bool_yes_no'],
'cip': [lambda v: v.upper()],
'downstateflush': [lambda v: v.upper()],
'appflowlog': [lambda v: v.upper()],
'processlocal': [lambda v: v.upper()],
}
monitor_bindings_rw_attrs = [
'servicename',
'servicegroupname',
'dup_state',
'dup_weight',
'monitorname',
'weight',
]
# Translate module arguments to correspondign config object attributes
if module.params['ip'] is None:
module.params['ip'] = module.params['ipaddress']
service_proxy = ConfigProxy(
actual=service(),
client=client,
attribute_values_dict=module.params,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
immutable_attrs=immutable_attrs,
transforms=transforms,
)
try:
# Apply appropriate state
if module.params['state'] == 'present':
log('Applying actions for state present')
if not service_exists(client, module):
if not module.check_mode:
service_proxy.add()
sync_monitor_bindings(client, module, monitor_bindings_rw_attrs)
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not all_identical(client, module, service_proxy, monitor_bindings_rw_attrs):
# Check if we try to change value of immutable attributes
diff_dict = diff(client, module, service_proxy)
immutables_changed = get_immutables_intersection(service_proxy, diff_dict.keys())
if immutables_changed != []:
msg = 'Cannot update immutable attributes %s. Must delete and recreate entity.' % (immutables_changed,)
module.fail_json(msg=msg, diff=diff_dict, **module_result)
# Service sync
if not service_identical(client, module, service_proxy):
if not module.check_mode:
service_proxy.update()
# Monitor bindings sync
if not monitor_bindings_identical(client, module, monitor_bindings_rw_attrs):
if not module.check_mode:
sync_monitor_bindings(client, module, monitor_bindings_rw_attrs)
module_result['changed'] = True
if not module.check_mode:
if module.params['save_config']:
client.save_config()
else:
module_result['changed'] = False
if not module.check_mode:
res = do_state_change(client, module, service_proxy)
if res.errorcode != 0:
msg = 'Error when setting disabled state. errorcode: %s message: %s' % (res.errorcode, res.message)
module.fail_json(msg=msg, **module_result)
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state present')
if not service_exists(client, module):
module.fail_json(msg='Service does not exist', **module_result)
if not service_identical(client, module, service_proxy):
module.fail_json(msg='Service differs from configured', diff=diff(client, module, service_proxy), **module_result)
if not monitor_bindings_identical(client, module, monitor_bindings_rw_attrs):
module.fail_json(msg='Monitor bindings are not identical', **module_result)
elif module.params['state'] == 'absent':
log('Applying actions for state absent')
if service_exists(client, module):
if not module.check_mode:
service_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state absent')
if service_exists(client, module):
module.fail_json(msg='Service still exists', **module_result)
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,372 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2017 Citrix Systems
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: netscaler_ssl_certkey
short_description: Manage ssl certificate keys.
description:
- Manage ssl certificate keys.
author: George Nikolopoulos (@giorgos-nikolopoulos)
options:
certkey:
description:
- >-
Name for the certificate and private-key pair. Must begin with an ASCII alphanumeric or underscore
C(_) character, and must contain only ASCII alphanumeric, underscore C(_), hash C(#), period C(.), space C( ),
colon C(:), at C(@), equals C(=), and hyphen C(-) characters. Cannot be changed after the certificate-key
pair is created.
- "The following requirement applies only to the NetScaler CLI:"
- >-
If the name includes one or more spaces, enclose the name in double or single quotation marks (for
example, "my cert" or 'my cert').
- "Minimum length = 1"
cert:
description:
- >-
Name of and, optionally, path to the X509 certificate file that is used to form the certificate-key
pair. The certificate file should be present on the appliance's hard-disk drive or solid-state drive.
Storing a certificate in any location other than the default might cause inconsistency in a high
availability setup. /nsconfig/ssl/ is the default path.
- "Minimum length = 1"
key:
description:
- >-
Name of and, optionally, path to the private-key file that is used to form the certificate-key pair.
The certificate file should be present on the appliance's hard-disk drive or solid-state drive.
Storing a certificate in any location other than the default might cause inconsistency in a high
availability setup. /nsconfig/ssl/ is the default path.
- "Minimum length = 1"
password:
description:
- >-
Passphrase that was used to encrypt the private-key. Use this option to load encrypted private-keys
in PEM format.
inform:
choices:
- 'DER'
- 'PEM'
- 'PFX'
description:
- >-
Input format of the certificate and the private-key files. The three formats supported by the
appliance are:
- "PEM - Privacy Enhanced Mail"
- "DER - Distinguished Encoding Rule"
- "PFX - Personal Information Exchange."
passplain:
description:
- >-
Pass phrase used to encrypt the private-key. Required when adding an encrypted private-key in PEM
format.
- "Minimum length = 1"
expirymonitor:
choices:
- 'enabled'
- 'disabled'
description:
- "Issue an alert when the certificate is about to expire."
notificationperiod:
description:
- >-
Time, in number of days, before certificate expiration, at which to generate an alert that the
certificate is about to expire.
- "Minimum value = C(10)"
- "Maximum value = C(100)"
extends_documentation_fragment:
- community.general.netscaler
requirements:
- nitro python sdk
'''
EXAMPLES = '''
- name: Setup ssl certkey
delegate_to: localhost
netscaler_ssl_certkey:
nitro_user: nsroot
nitro_pass: nsroot
nsip: 172.18.0.2
certkey: certirificate_1
cert: server.crt
key: server.key
expirymonitor: enabled
notificationperiod: 30
inform: PEM
password: False
passplain: somesecret
'''
RETURN = '''
loglines:
description: list of logged messages by the module
returned: always
type: list
sample: "['message 1', 'message 2']"
msg:
description: Message detailing the failure reason
returned: failure
type: str
sample: "Action does not exist"
diff:
description: List of differences between the actual configured object and the configuration specified in the module
returned: failure
type: dict
sample: "{ 'targetlbvserver': 'difference. ours: (str) server1 other: (str) server2' }"
'''
try:
from nssrc.com.citrix.netscaler.nitro.resource.config.ssl.sslcertkey import sslcertkey
from nssrc.com.citrix.netscaler.nitro.exception.nitro_exception import nitro_exception
PYTHON_SDK_IMPORTED = True
except ImportError as e:
PYTHON_SDK_IMPORTED = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.general.plugins.module_utils.network.netscaler.netscaler import ConfigProxy, get_nitro_client, netscaler_common_arguments, log, loglines, \
get_immutables_intersection
def key_exists(client, module):
log('Checking if key exists')
log('certkey is %s' % module.params['certkey'])
all_certificates = sslcertkey.get(client)
certkeys = [item.certkey for item in all_certificates]
if module.params['certkey'] in certkeys:
return True
else:
return False
def key_identical(client, module, sslcertkey_proxy):
log('Checking if configured key is identical')
sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey'])
diff_dict = sslcertkey_proxy.diff_object(sslcertkey_list[0])
if 'password' in diff_dict:
del diff_dict['password']
if 'passplain' in diff_dict:
del diff_dict['passplain']
if len(diff_dict) == 0:
return True
else:
return False
def diff_list(client, module, sslcertkey_proxy):
sslcertkey_list = sslcertkey.get_filtered(client, 'certkey:%s' % module.params['certkey'])
return sslcertkey_proxy.diff_object(sslcertkey_list[0])
def main():
module_specific_arguments = dict(
certkey=dict(type='str'),
cert=dict(type='str'),
key=dict(type='str'),
password=dict(type='bool'),
inform=dict(
type='str',
choices=[
'DER',
'PEM',
'PFX',
]
),
passplain=dict(
type='str',
no_log=True,
),
expirymonitor=dict(
type='str',
choices=[
'enabled',
'disabled',
]
),
notificationperiod=dict(type='float'),
)
argument_spec = dict()
argument_spec.update(netscaler_common_arguments)
argument_spec.update(module_specific_arguments)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
)
module_result = dict(
changed=False,
failed=False,
loglines=loglines,
)
# Fail the module if imports failed
if not PYTHON_SDK_IMPORTED:
module.fail_json(msg='Could not load nitro python sdk')
# Fallthrough to rest of execution
client = get_nitro_client(module)
try:
client.login()
except nitro_exception as e:
msg = "nitro exception during login. errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg)
except Exception as e:
if str(type(e)) == "<class 'requests.exceptions.ConnectionError'>":
module.fail_json(msg='Connection error %s' % str(e))
elif str(type(e)) == "<class 'requests.exceptions.SSLError'>":
module.fail_json(msg='SSL Error %s' % str(e))
else:
module.fail_json(msg='Unexpected error during login %s' % str(e))
readwrite_attrs = [
'certkey',
'cert',
'key',
'password',
'inform',
'passplain',
'expirymonitor',
'notificationperiod',
]
readonly_attrs = [
'signaturealg',
'certificatetype',
'serial',
'issuer',
'clientcertnotbefore',
'clientcertnotafter',
'daystoexpiration',
'subject',
'publickey',
'publickeysize',
'version',
'priority',
'status',
'passcrypt',
'data',
'servicename',
]
immutable_attrs = [
'certkey',
'cert',
'key',
'password',
'inform',
'passplain',
]
transforms = {
'expirymonitor': [lambda v: v.upper()],
}
# Instantiate config proxy
sslcertkey_proxy = ConfigProxy(
actual=sslcertkey(),
client=client,
attribute_values_dict=module.params,
readwrite_attrs=readwrite_attrs,
readonly_attrs=readonly_attrs,
immutable_attrs=immutable_attrs,
transforms=transforms,
)
try:
if module.params['state'] == 'present':
log('Applying actions for state present')
if not key_exists(client, module):
if not module.check_mode:
log('Adding certificate key')
sslcertkey_proxy.add()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
elif not key_identical(client, module, sslcertkey_proxy):
# Check if we try to change value of immutable attributes
immutables_changed = get_immutables_intersection(sslcertkey_proxy, diff_list(client, module, sslcertkey_proxy).keys())
if immutables_changed != []:
module.fail_json(
msg='Cannot update immutable attributes %s' % (immutables_changed,),
diff=diff_list(client, module, sslcertkey_proxy),
**module_result
)
if not module.check_mode:
sslcertkey_proxy.update()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state present')
if not key_exists(client, module):
module.fail_json(msg='SSL certkey does not exist')
if not key_identical(client, module, sslcertkey_proxy):
module.fail_json(msg='SSL certkey differs from configured', diff=diff_list(client, module, sslcertkey_proxy))
elif module.params['state'] == 'absent':
log('Applying actions for state absent')
if key_exists(client, module):
if not module.check_mode:
sslcertkey_proxy.delete()
if module.params['save_config']:
client.save_config()
module_result['changed'] = True
else:
module_result['changed'] = False
# Sanity check for state
if not module.check_mode:
log('Sanity checks for state absent')
if key_exists(client, module):
module.fail_json(msg='SSL certkey still exists')
except nitro_exception as e:
msg = "nitro exception errorcode=%s, message=%s" % (str(e.errorcode), e.message)
module.fail_json(msg=msg, **module_result)
client.logout()
module.exit_json(**module_result)
if __name__ == "__main__":
main()