New module: Issue NITRO requests to a Netscaler instance (network/netscaler/netscaler_nitro_request) (#33091)
* Add netscaler_nitro_request module * Make changes as requested. * Fix whitepsace in EXAMPLES block. * Set module changed status according to operation performed.
This commit is contained in:
parent
2114278947
commit
34f965addd
2 changed files with 1256 additions and 0 deletions
909
lib/ansible/modules/network/netscaler/netscaler_nitro_request.py
Normal file
909
lib/ansible/modules/network/netscaler/netscaler_nitro_request.py
Normal 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
|
||||
|
||||
|
||||
version_added: "2.5.0"
|
||||
|
||||
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'
|
||||
|
||||
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 succesful. 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: string
|
||||
sample: Success
|
||||
|
||||
nitro_severity:
|
||||
description: A string describing the severity of the NITRO operation error or NONE.
|
||||
returned: always
|
||||
type: string
|
||||
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: string
|
||||
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 succesful.
|
||||
returned: when applicable
|
||||
type: string
|
||||
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()
|
|
@ -0,0 +1,347 @@
|
|||
|
||||
# Copyright (c) 2017 Citrix Systems
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from ansible.compat.tests.mock import patch, Mock, call
|
||||
from .netscaler_module import TestModule
|
||||
import copy
|
||||
import tempfile
|
||||
import json
|
||||
import sys
|
||||
import codecs
|
||||
|
||||
from ansible.modules.network.netscaler import netscaler_nitro_request
|
||||
|
||||
module_arguments = dict(
|
||||
nsip=None,
|
||||
nitro_user=None,
|
||||
nitro_pass=None,
|
||||
nitro_protocol=None,
|
||||
validate_certs=None,
|
||||
nitro_auth_token=None,
|
||||
resource=None,
|
||||
name=None,
|
||||
attributes=None,
|
||||
args=None,
|
||||
filter=None,
|
||||
operation=None,
|
||||
expected_nitro_errorcode=None,
|
||||
action=None,
|
||||
instance_ip=None,
|
||||
instance_name=None,
|
||||
instance_id=None,
|
||||
)
|
||||
|
||||
|
||||
class TestNetscalerNitroRequestModule(TestModule):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
class MockException(Exception):
|
||||
pass
|
||||
|
||||
cls.MockException = MockException
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def test_fail_on_conflicting_authentication_methods(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_user='nsroot',
|
||||
nitro_pass='nsroot',
|
||||
nitro_auth_token='##DDASKLFDJ',
|
||||
))
|
||||
mock_module_instance = Mock(params=args)
|
||||
expected_calls = [
|
||||
call.fail_json(
|
||||
changed=False,
|
||||
failed=True,
|
||||
msg='Cannot define both authentication token and username/password'
|
||||
)
|
||||
]
|
||||
module_mock = Mock(return_value=mock_module_instance)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', module_mock):
|
||||
netscaler_nitro_request.NitroAPICaller()
|
||||
mock_module_instance.assert_has_calls(expected_calls)
|
||||
|
||||
def test_nitro_user_pass_credentials(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_user='nsroot',
|
||||
nitro_pass='nsroot',
|
||||
))
|
||||
mock_module_instance = Mock(params=args)
|
||||
expected_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-NITRO-USER': 'nsroot',
|
||||
'X-NITRO-PASS': 'nsroot',
|
||||
}
|
||||
module_mock = Mock(return_value=mock_module_instance)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', module_mock):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
self.assertDictEqual(instance._headers, expected_headers)
|
||||
|
||||
def test_mas_login_headers(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_user='nsroot',
|
||||
nitro_pass='nsroot',
|
||||
operation='mas_login',
|
||||
))
|
||||
mock_module_instance = Mock(params=args)
|
||||
expected_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
module_mock = Mock(return_value=mock_module_instance)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', module_mock):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
self.assertDictEqual(instance._headers, expected_headers)
|
||||
|
||||
def test_mas_proxy_call_headers_instance_ip(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_auth_token='##ABDB',
|
||||
operation='add',
|
||||
instance_ip='192.168.1.1',
|
||||
))
|
||||
mock_module_instance = Mock(params=args)
|
||||
expected_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'_MPS_API_PROXY_MANAGED_INSTANCE_IP': args['instance_ip'],
|
||||
'Cookie': 'NITRO_AUTH_TOKEN=%s' % args['nitro_auth_token'],
|
||||
}
|
||||
module_mock = Mock(return_value=mock_module_instance)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', module_mock):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
self.assertDictEqual(instance._headers, expected_headers)
|
||||
|
||||
def test_mas_proxy_call_headers_instance_id(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_auth_token='##ABDB',
|
||||
operation='add',
|
||||
instance_id='myid',
|
||||
))
|
||||
mock_module_instance = Mock(params=args)
|
||||
expected_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'_MPS_API_PROXY_MANAGED_INSTANCE_ID': args['instance_id'],
|
||||
'Cookie': 'NITRO_AUTH_TOKEN=%s' % args['nitro_auth_token'],
|
||||
}
|
||||
module_mock = Mock(return_value=mock_module_instance)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', module_mock):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
self.assertDictEqual(instance._headers, expected_headers)
|
||||
|
||||
def test_mas_proxy_call_headers_instance_name(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_auth_token='##ABDB',
|
||||
operation='add',
|
||||
instance_name='myname',
|
||||
))
|
||||
mock_module_instance = Mock(params=args)
|
||||
expected_headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'_MPS_API_PROXY_MANAGED_INSTANCE_NAME': args['instance_name'],
|
||||
'Cookie': 'NITRO_AUTH_TOKEN=%s' % args['nitro_auth_token'],
|
||||
}
|
||||
module_mock = Mock(return_value=mock_module_instance)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', module_mock):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
self.assertDictEqual(instance._headers, expected_headers)
|
||||
|
||||
def test_edit_response_data_no_body_success_status(self):
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule'):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
r = None
|
||||
info = {
|
||||
'status': 200,
|
||||
}
|
||||
result = {}
|
||||
success_status = 200
|
||||
|
||||
expected_result = {
|
||||
'nitro_errorcode': 0,
|
||||
'nitro_message': 'Success',
|
||||
'nitro_severity': 'NONE',
|
||||
'http_response_body': '',
|
||||
'http_response_data': info,
|
||||
}
|
||||
instance.edit_response_data(r, info, result, success_status)
|
||||
self.assertDictEqual(result, expected_result)
|
||||
|
||||
def test_edit_response_data_no_body_fail_status(self):
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule'):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
r = None
|
||||
info = {
|
||||
'status': 201,
|
||||
}
|
||||
result = {}
|
||||
success_status = 200
|
||||
|
||||
expected_result = {
|
||||
'nitro_errorcode': -1,
|
||||
'nitro_message': 'HTTP status %s' % info['status'],
|
||||
'nitro_severity': 'ERROR',
|
||||
'http_response_body': '',
|
||||
'http_response_data': info,
|
||||
}
|
||||
instance.edit_response_data(r, info, result, success_status)
|
||||
self.assertDictEqual(result, expected_result)
|
||||
|
||||
def test_edit_response_data_actual_body_data(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_user='nsroot',
|
||||
nitro_pass='nsroot',
|
||||
nitro_auth_token='##DDASKLFDJ',
|
||||
))
|
||||
module_mock = Mock(params=args, from_json=json.loads)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', Mock(return_value=module_mock)):
|
||||
with tempfile.TemporaryFile() as r:
|
||||
actual_body = {
|
||||
'errorcode': 258,
|
||||
'message': 'Some error',
|
||||
'severity': 'ERROR',
|
||||
}
|
||||
r.write(codecs.encode(json.dumps(actual_body), 'utf-8'))
|
||||
r.seek(0)
|
||||
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
info = {
|
||||
'status': 200,
|
||||
}
|
||||
result = {}
|
||||
success_status = 200
|
||||
|
||||
expected_result = {
|
||||
'http_response_body': json.dumps(actual_body),
|
||||
'http_response_data': info,
|
||||
}
|
||||
nitro_data = {}
|
||||
for key, value in actual_body.items():
|
||||
nitro_data['nitro_%s' % key] = value
|
||||
expected_result.update(nitro_data)
|
||||
|
||||
instance.edit_response_data(r, info, result, success_status)
|
||||
self.assertDictEqual(result, expected_result)
|
||||
|
||||
def test_edit_response_data_actual_body_data_irrelevant(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_user='nsroot',
|
||||
nitro_pass='nsroot',
|
||||
nitro_auth_token='##DDASKLFDJ',
|
||||
))
|
||||
module_mock = Mock(params=args, from_json=json.loads)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', Mock(return_value=module_mock)):
|
||||
with tempfile.TemporaryFile() as r:
|
||||
actual_body = {}
|
||||
r.write(codecs.encode(json.dumps(actual_body), 'utf-8'))
|
||||
r.seek(0)
|
||||
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
info = {
|
||||
'status': 200,
|
||||
}
|
||||
result = {}
|
||||
success_status = 200
|
||||
|
||||
expected_result = {
|
||||
'http_response_body': json.dumps(actual_body),
|
||||
'http_response_data': info,
|
||||
'nitro_errorcode': 0,
|
||||
'nitro_message': 'Success',
|
||||
'nitro_severity': 'NONE',
|
||||
}
|
||||
|
||||
instance.edit_response_data(r, info, result, success_status)
|
||||
self.assertDictEqual(result, expected_result)
|
||||
|
||||
def test_edit_response_data_body_in_info(self):
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_user='nsroot',
|
||||
nitro_pass='nsroot',
|
||||
))
|
||||
module_mock = Mock(params=args, from_json=json.loads)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', Mock(return_value=module_mock)):
|
||||
body = {
|
||||
'errorcode': 258,
|
||||
'message': 'Numerical error 258',
|
||||
'severity': 'ERROR'
|
||||
}
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
r = None
|
||||
info = {
|
||||
'status': 200,
|
||||
'body': codecs.encode(json.dumps(body), 'utf-8'),
|
||||
}
|
||||
result = {}
|
||||
success_status = 200
|
||||
|
||||
expected_result = {
|
||||
'http_response_body': json.dumps(body),
|
||||
'http_response_data': info,
|
||||
}
|
||||
|
||||
nitro_data = {}
|
||||
for key, value in body.items():
|
||||
nitro_data['nitro_%s' % key] = value
|
||||
|
||||
expected_result.update(nitro_data)
|
||||
instance.edit_response_data(r, info, result, success_status)
|
||||
self.assertDictEqual(result, expected_result)
|
||||
|
||||
def test_handle_get_return_object(self):
|
||||
resource = 'lbvserver'
|
||||
args = copy.deepcopy(module_arguments)
|
||||
args.update(dict(
|
||||
nitro_user='nsroot',
|
||||
nitro_pass='nsroot',
|
||||
resource=resource,
|
||||
))
|
||||
resource_data = {
|
||||
'property1': 'value1',
|
||||
'property2': 'value2',
|
||||
}
|
||||
module_mock = Mock(params=args, from_json=json.loads)
|
||||
with patch('ansible.modules.network.netscaler.netscaler_nitro_request.AnsibleModule', Mock(return_value=module_mock)):
|
||||
instance = netscaler_nitro_request.NitroAPICaller()
|
||||
|
||||
data = {resource: resource_data}
|
||||
result = {
|
||||
'nitro_errorcode': 0,
|
||||
'http_response_body': json.dumps(data),
|
||||
}
|
||||
expected_result = {
|
||||
'nitro_object': resource_data
|
||||
}
|
||||
expected_result.update(result)
|
||||
instance.handle_get_return_object(result)
|
||||
self.assertDictEqual(result, expected_result)
|
Loading…
Reference in a new issue