mirror of https://github.com/ansible/ansible.git
Migrated to servicenow.servicenow
parent
7d65b28189
commit
ab5942a760
@ -1,94 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright: (c) 2019, Ansible Project
|
|
||||||
# Copyright: (c) 2017, Tim Rightnour <thegarbledone@gmail.com>
|
|
||||||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function
|
|
||||||
__metaclass__ = type
|
|
||||||
|
|
||||||
import traceback
|
|
||||||
from ansible.module_utils.basic import env_fallback, missing_required_lib
|
|
||||||
|
|
||||||
# Pull in pysnow
|
|
||||||
HAS_PYSNOW = False
|
|
||||||
PYSNOW_IMP_ERR = None
|
|
||||||
try:
|
|
||||||
import pysnow
|
|
||||||
HAS_PYSNOW = True
|
|
||||||
except ImportError:
|
|
||||||
PYSNOW_IMP_ERR = traceback.format_exc()
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceNowClient(object):
|
|
||||||
def __init__(self, module):
|
|
||||||
"""
|
|
||||||
Constructor
|
|
||||||
"""
|
|
||||||
if not HAS_PYSNOW:
|
|
||||||
module.fail_json(msg=missing_required_lib('pysnow'), exception=PYSNOW_IMP_ERR)
|
|
||||||
|
|
||||||
self.module = module
|
|
||||||
self.params = module.params
|
|
||||||
self.client_id = self.params['client_id']
|
|
||||||
self.client_secret = self.params['client_secret']
|
|
||||||
self.username = self.params['username']
|
|
||||||
self.password = self.params['password']
|
|
||||||
self.instance = self.params['instance']
|
|
||||||
self.session = {'token': None}
|
|
||||||
self.conn = None
|
|
||||||
|
|
||||||
def login(self):
|
|
||||||
result = dict(
|
|
||||||
changed=False
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.params['client_id'] is not None:
|
|
||||||
try:
|
|
||||||
self.conn = pysnow.OAuthClient(client_id=self.client_id,
|
|
||||||
client_secret=self.client_secret,
|
|
||||||
token_updater=self.updater,
|
|
||||||
instance=self.instance)
|
|
||||||
except Exception as detail:
|
|
||||||
self.module.fail_json(msg='Could not connect to ServiceNow: {0}'.format(str(detail)), **result)
|
|
||||||
if not self.session['token']:
|
|
||||||
# No previous token exists, Generate new.
|
|
||||||
try:
|
|
||||||
self.session['token'] = self.conn.generate_token(self.username, self.password)
|
|
||||||
except pysnow.exceptions.TokenCreateError as detail:
|
|
||||||
self.module.fail_json(msg='Unable to generate a new token: {0}'.format(str(detail)), **result)
|
|
||||||
|
|
||||||
self.conn.set_token(self.session['token'])
|
|
||||||
elif self.username is not None:
|
|
||||||
try:
|
|
||||||
self.conn = pysnow.Client(instance=self.instance,
|
|
||||||
user=self.username,
|
|
||||||
password=self.password)
|
|
||||||
except Exception as detail:
|
|
||||||
self.module.fail_json(msg='Could not connect to ServiceNow: {0}'.format(str(detail)), **result)
|
|
||||||
else:
|
|
||||||
snow_error = "Must specify username/password. Also client_id/client_secret if using OAuth."
|
|
||||||
self.module.fail_json(msg=snow_error, **result)
|
|
||||||
|
|
||||||
def updater(self, new_token):
|
|
||||||
self.session['token'] = new_token
|
|
||||||
self.conn = pysnow.OAuthClient(client_id=self.client_id,
|
|
||||||
client_secret=self.client_secret,
|
|
||||||
token_updater=self.updater,
|
|
||||||
instance=self.instance)
|
|
||||||
try:
|
|
||||||
self.conn.set_token(self.session['token'])
|
|
||||||
except pysnow.exceptions.MissingToken:
|
|
||||||
snow_error = "Token is missing"
|
|
||||||
self.module.fail_json(msg=snow_error)
|
|
||||||
except Exception as detail:
|
|
||||||
self.module.fail_json(msg='Could not refresh token: {0}'.format(str(detail)))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def snow_argument_spec():
|
|
||||||
return dict(
|
|
||||||
instance=dict(type='str', required=False, fallback=(env_fallback, ['SN_INSTANCE'])),
|
|
||||||
username=dict(type='str', required=False, fallback=(env_fallback, ['SN_USERNAME'])),
|
|
||||||
password=dict(type='str', required=False, no_log=True, fallback=(env_fallback, ['SN_PASSWORD'])),
|
|
||||||
client_id=dict(type='str', no_log=True),
|
|
||||||
client_secret=dict(type='str', no_log=True),
|
|
||||||
)
|
|
@ -1,333 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2017, Tim Rightnour <thegarbledone@gmail.com>
|
|
||||||
# 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: snow_record
|
|
||||||
short_description: Manage records in ServiceNow
|
|
||||||
version_added: "2.5"
|
|
||||||
description:
|
|
||||||
- Creates, deletes and updates a single record in ServiceNow.
|
|
||||||
options:
|
|
||||||
table:
|
|
||||||
description:
|
|
||||||
- Table to query for records.
|
|
||||||
required: false
|
|
||||||
default: incident
|
|
||||||
type: str
|
|
||||||
state:
|
|
||||||
description:
|
|
||||||
- If C(present) is supplied with a C(number) argument, the module will attempt to update the record with the supplied data.
|
|
||||||
- If no such record exists, a new one will be created.
|
|
||||||
- C(absent) will delete a record.
|
|
||||||
choices: [ present, absent ]
|
|
||||||
required: true
|
|
||||||
type: str
|
|
||||||
data:
|
|
||||||
description:
|
|
||||||
- key, value pairs of data to load into the record. See Examples.
|
|
||||||
- Required for C(state:present).
|
|
||||||
type: dict
|
|
||||||
number:
|
|
||||||
description:
|
|
||||||
- Record number to update.
|
|
||||||
- Required for C(state:absent).
|
|
||||||
required: false
|
|
||||||
type: str
|
|
||||||
lookup_field:
|
|
||||||
description:
|
|
||||||
- Changes the field that C(number) uses to find records.
|
|
||||||
required: false
|
|
||||||
default: number
|
|
||||||
type: str
|
|
||||||
attachment:
|
|
||||||
description:
|
|
||||||
- Attach a file to the record.
|
|
||||||
required: false
|
|
||||||
type: str
|
|
||||||
requirements:
|
|
||||||
- python pysnow (pysnow)
|
|
||||||
author:
|
|
||||||
- Tim Rightnour (@garbled1)
|
|
||||||
extends_documentation_fragment: service_now.documentation
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Grab a user record
|
|
||||||
snow_record:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
instance: dev99999
|
|
||||||
state: present
|
|
||||||
number: 62826bf03710200044e0bfc8bcbe5df1
|
|
||||||
table: sys_user
|
|
||||||
lookup_field: sys_id
|
|
||||||
|
|
||||||
- name: Grab a user record using OAuth
|
|
||||||
snow_record:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
client_id: "1234567890abcdef1234567890abcdef"
|
|
||||||
client_secret: "Password1!"
|
|
||||||
instance: dev99999
|
|
||||||
state: present
|
|
||||||
number: 62826bf03710200044e0bfc8bcbe5df1
|
|
||||||
table: sys_user
|
|
||||||
lookup_field: sys_id
|
|
||||||
|
|
||||||
- name: Create an incident
|
|
||||||
snow_record:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
instance: dev99999
|
|
||||||
state: present
|
|
||||||
data:
|
|
||||||
short_description: "This is a test incident opened by Ansible"
|
|
||||||
severity: 3
|
|
||||||
priority: 2
|
|
||||||
register: new_incident
|
|
||||||
|
|
||||||
- name: Delete the record we just made
|
|
||||||
snow_record:
|
|
||||||
username: admin
|
|
||||||
password: xxxxxxx
|
|
||||||
instance: dev99999
|
|
||||||
state: absent
|
|
||||||
number: "{{new_incident['record']['number']}}"
|
|
||||||
|
|
||||||
- name: Delete a non-existant record
|
|
||||||
snow_record:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
instance: dev99999
|
|
||||||
state: absent
|
|
||||||
number: 9872354
|
|
||||||
failed_when: false
|
|
||||||
|
|
||||||
- name: Update an incident
|
|
||||||
snow_record:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
instance: dev99999
|
|
||||||
state: present
|
|
||||||
number: INC0000055
|
|
||||||
data:
|
|
||||||
work_notes : "Been working all day on this thing."
|
|
||||||
|
|
||||||
- name: Attach a file to an incident
|
|
||||||
snow_record:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
instance: dev99999
|
|
||||||
state: present
|
|
||||||
number: INC0000055
|
|
||||||
attachment: README.md
|
|
||||||
tags: attach
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
record:
|
|
||||||
description: Record data from Service Now
|
|
||||||
type: dict
|
|
||||||
returned: when supported
|
|
||||||
attached_file:
|
|
||||||
description: Details of the file that was attached via C(attachment)
|
|
||||||
type: dict
|
|
||||||
returned: when supported
|
|
||||||
'''
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils._text import to_bytes, to_native
|
|
||||||
from ansible.module_utils.service_now import ServiceNowClient
|
|
||||||
|
|
||||||
try:
|
|
||||||
# This is being handled by ServiceNowClient
|
|
||||||
import pysnow
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def run_module():
|
|
||||||
# define the available arguments/parameters that a user can pass to
|
|
||||||
# the module
|
|
||||||
module_args = ServiceNowClient.snow_argument_spec()
|
|
||||||
module_args.update(
|
|
||||||
table=dict(type='str', required=False, default='incident'),
|
|
||||||
state=dict(choices=['present', 'absent'],
|
|
||||||
type='str', required=True),
|
|
||||||
number=dict(default=None, required=False, type='str'),
|
|
||||||
data=dict(default=None, required=False, type='dict'),
|
|
||||||
lookup_field=dict(default='number', required=False, type='str'),
|
|
||||||
attachment=dict(default=None, required=False, type='str')
|
|
||||||
)
|
|
||||||
module_required_together = [
|
|
||||||
['client_id', 'client_secret']
|
|
||||||
]
|
|
||||||
module_required_if = [
|
|
||||||
['state', 'absent', ['number']],
|
|
||||||
]
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=module_args,
|
|
||||||
supports_check_mode=True,
|
|
||||||
required_together=module_required_together,
|
|
||||||
required_if=module_required_if
|
|
||||||
)
|
|
||||||
|
|
||||||
# Connect to ServiceNow
|
|
||||||
service_now_client = ServiceNowClient(module)
|
|
||||||
service_now_client.login()
|
|
||||||
conn = service_now_client.conn
|
|
||||||
|
|
||||||
params = module.params
|
|
||||||
instance = params['instance']
|
|
||||||
table = params['table']
|
|
||||||
state = params['state']
|
|
||||||
number = params['number']
|
|
||||||
data = params['data']
|
|
||||||
lookup_field = params['lookup_field']
|
|
||||||
|
|
||||||
result = dict(
|
|
||||||
changed=False,
|
|
||||||
instance=instance,
|
|
||||||
table=table,
|
|
||||||
number=number,
|
|
||||||
lookup_field=lookup_field
|
|
||||||
)
|
|
||||||
|
|
||||||
# check for attachments
|
|
||||||
if params['attachment'] is not None:
|
|
||||||
attach = params['attachment']
|
|
||||||
b_attach = to_bytes(attach, errors='surrogate_or_strict')
|
|
||||||
if not os.path.exists(b_attach):
|
|
||||||
module.fail_json(msg="Attachment {0} not found".format(attach))
|
|
||||||
result['attachment'] = attach
|
|
||||||
else:
|
|
||||||
attach = None
|
|
||||||
|
|
||||||
# Deal with check mode
|
|
||||||
if module.check_mode:
|
|
||||||
|
|
||||||
# if we are in check mode and have no number, we would have created
|
|
||||||
# a record. We can only partially simulate this
|
|
||||||
if number is None:
|
|
||||||
result['record'] = dict(data)
|
|
||||||
result['changed'] = True
|
|
||||||
|
|
||||||
# do we want to check if the record is non-existent?
|
|
||||||
elif state == 'absent':
|
|
||||||
try:
|
|
||||||
record = conn.query(table=table, query={lookup_field: number})
|
|
||||||
res = record.get_one()
|
|
||||||
result['record'] = dict(Success=True)
|
|
||||||
result['changed'] = True
|
|
||||||
except pysnow.exceptions.NoResults:
|
|
||||||
result['record'] = None
|
|
||||||
except Exception as detail:
|
|
||||||
module.fail_json(msg="Unknown failure in query record: {0}".format(to_native(detail)), **result)
|
|
||||||
|
|
||||||
# Let's simulate modification
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
record = conn.query(table=table, query={lookup_field: number})
|
|
||||||
res = record.get_one()
|
|
||||||
for key, value in data.items():
|
|
||||||
res[key] = value
|
|
||||||
result['changed'] = True
|
|
||||||
result['record'] = res
|
|
||||||
except pysnow.exceptions.NoResults:
|
|
||||||
snow_error = "Record does not exist"
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
except Exception as detail:
|
|
||||||
module.fail_json(msg="Unknown failure in query record: {0}".format(to_native(detail)), **result)
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
# now for the real thing: (non-check mode)
|
|
||||||
|
|
||||||
# are we creating a new record?
|
|
||||||
if state == 'present' and number is None:
|
|
||||||
try:
|
|
||||||
record = conn.insert(table=table, payload=dict(data))
|
|
||||||
except pysnow.exceptions.UnexpectedResponseFormat as e:
|
|
||||||
snow_error = "Failed to create record: {0}, details: {1}".format(e.error_summary, e.error_details)
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
except pysnow.legacy_exceptions.UnexpectedResponse as e:
|
|
||||||
module.fail_json(msg="Failed to create record due to %s" % to_native(e), **result)
|
|
||||||
result['record'] = record
|
|
||||||
result['changed'] = True
|
|
||||||
|
|
||||||
# we are deleting a record
|
|
||||||
elif state == 'absent':
|
|
||||||
try:
|
|
||||||
record = conn.query(table=table, query={lookup_field: number})
|
|
||||||
res = record.delete()
|
|
||||||
except pysnow.exceptions.NoResults:
|
|
||||||
res = dict(Success=True)
|
|
||||||
except pysnow.exceptions.MultipleResults:
|
|
||||||
snow_error = "Multiple record match"
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
except pysnow.exceptions.UnexpectedResponseFormat as e:
|
|
||||||
snow_error = "Failed to delete record: {0}, details: {1}".format(e.error_summary, e.error_details)
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
except pysnow.legacy_exceptions.UnexpectedResponse as e:
|
|
||||||
module.fail_json(msg="Failed to delete record due to %s" % to_native(e), **result)
|
|
||||||
except Exception as detail:
|
|
||||||
snow_error = "Failed to delete record: {0}".format(to_native(detail))
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
result['record'] = res
|
|
||||||
result['changed'] = True
|
|
||||||
|
|
||||||
# We want to update a record
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
record = conn.query(table=table, query={lookup_field: number})
|
|
||||||
if data is not None:
|
|
||||||
res = record.update(dict(data))
|
|
||||||
result['record'] = res
|
|
||||||
result['changed'] = True
|
|
||||||
else:
|
|
||||||
res = record.get_one()
|
|
||||||
result['record'] = res
|
|
||||||
if attach is not None:
|
|
||||||
res = record.attach(b_attach)
|
|
||||||
result['changed'] = True
|
|
||||||
result['attached_file'] = res
|
|
||||||
|
|
||||||
except pysnow.exceptions.MultipleResults:
|
|
||||||
snow_error = "Multiple record match"
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
except pysnow.exceptions.NoResults:
|
|
||||||
snow_error = "Record does not exist"
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
except pysnow.exceptions.UnexpectedResponseFormat as e:
|
|
||||||
snow_error = "Failed to update record: {0}, details: {1}".format(e.error_summary, e.error_details)
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
except pysnow.legacy_exceptions.UnexpectedResponse as e:
|
|
||||||
module.fail_json(msg="Failed to update record due to %s" % to_native(e), **result)
|
|
||||||
except Exception as detail:
|
|
||||||
snow_error = "Failed to update record: {0}".format(to_native(detail))
|
|
||||||
module.fail_json(msg=snow_error, **result)
|
|
||||||
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
run_module()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,273 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Copyright: (c) 2017, Tim Rightnour <thegarbledone@gmail.com>
|
|
||||||
# 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: snow_record_find
|
|
||||||
short_description: Search for multiple records from ServiceNow
|
|
||||||
version_added: "2.9"
|
|
||||||
description:
|
|
||||||
- Gets multiple records from a specified table from ServiceNow based on a query dictionary.
|
|
||||||
options:
|
|
||||||
table:
|
|
||||||
description:
|
|
||||||
- Table to query for records.
|
|
||||||
type: str
|
|
||||||
required: false
|
|
||||||
default: incident
|
|
||||||
query:
|
|
||||||
description:
|
|
||||||
- Dict to query for records.
|
|
||||||
type: dict
|
|
||||||
required: true
|
|
||||||
max_records:
|
|
||||||
description:
|
|
||||||
- Maximum number of records to return.
|
|
||||||
type: int
|
|
||||||
required: false
|
|
||||||
default: 20
|
|
||||||
order_by:
|
|
||||||
description:
|
|
||||||
- Field to sort the results on.
|
|
||||||
- Can prefix with "-" or "+" to change descending or ascending sort order.
|
|
||||||
type: str
|
|
||||||
default: "-created_on"
|
|
||||||
required: false
|
|
||||||
return_fields:
|
|
||||||
description:
|
|
||||||
- Fields of the record to return in the json.
|
|
||||||
- By default, all fields will be returned.
|
|
||||||
type: list
|
|
||||||
required: false
|
|
||||||
requirements:
|
|
||||||
- python pysnow (pysnow)
|
|
||||||
author:
|
|
||||||
- Tim Rightnour (@garbled1)
|
|
||||||
extends_documentation_fragment: service_now.documentation
|
|
||||||
'''
|
|
||||||
|
|
||||||
EXAMPLES = '''
|
|
||||||
- name: Search for incident assigned to group, return specific fields
|
|
||||||
snow_record_find:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
instance: dev99999
|
|
||||||
table: incident
|
|
||||||
query:
|
|
||||||
assignment_group: d625dccec0a8016700a222a0f7900d06
|
|
||||||
return_fields:
|
|
||||||
- number
|
|
||||||
- opened_at
|
|
||||||
|
|
||||||
- name: Using OAuth, search for incident assigned to group, return specific fields
|
|
||||||
snow_record_find:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
client_id: "1234567890abcdef1234567890abcdef"
|
|
||||||
client_secret: "Password1!"
|
|
||||||
instance: dev99999
|
|
||||||
table: incident
|
|
||||||
query:
|
|
||||||
assignment_group: d625dccec0a8016700a222a0f7900d06
|
|
||||||
return_fields:
|
|
||||||
- number
|
|
||||||
- opened_at
|
|
||||||
|
|
||||||
- name: Find open standard changes with my template
|
|
||||||
snow_record_find:
|
|
||||||
username: ansible_test
|
|
||||||
password: my_password
|
|
||||||
instance: dev99999
|
|
||||||
table: change_request
|
|
||||||
query:
|
|
||||||
AND:
|
|
||||||
equals:
|
|
||||||
active: "True"
|
|
||||||
type: "standard"
|
|
||||||
u_change_stage: "80"
|
|
||||||
contains:
|
|
||||||
u_template: "MY-Template"
|
|
||||||
return_fields:
|
|
||||||
- sys_id
|
|
||||||
- number
|
|
||||||
- sys_created_on
|
|
||||||
- sys_updated_on
|
|
||||||
- u_template
|
|
||||||
- active
|
|
||||||
- type
|
|
||||||
- u_change_stage
|
|
||||||
- sys_created_by
|
|
||||||
- description
|
|
||||||
- short_description
|
|
||||||
'''
|
|
||||||
|
|
||||||
RETURN = '''
|
|
||||||
record:
|
|
||||||
description: The full contents of the matching ServiceNow records as a list of records.
|
|
||||||
type: dict
|
|
||||||
returned: always
|
|
||||||
'''
|
|
||||||
|
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
|
||||||
from ansible.module_utils.service_now import ServiceNowClient
|
|
||||||
from ansible.module_utils._text import to_native
|
|
||||||
|
|
||||||
try:
|
|
||||||
# This is being managed by ServiceNowClient
|
|
||||||
import pysnow
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BuildQuery(object):
|
|
||||||
'''
|
|
||||||
This is a BuildQuery manipulation class that constructs
|
|
||||||
a pysnow.QueryBuilder object based on data input.
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, module):
|
|
||||||
self.module = module
|
|
||||||
self.logic_operators = ["AND", "OR", "NQ"]
|
|
||||||
self.condition_operator = {
|
|
||||||
'equals': self._condition_closure,
|
|
||||||
'not_equals': self._condition_closure,
|
|
||||||
'contains': self._condition_closure,
|
|
||||||
'not_contains': self._condition_closure,
|
|
||||||
'starts_with': self._condition_closure,
|
|
||||||
'ends_with': self._condition_closure,
|
|
||||||
'greater_than': self._condition_closure,
|
|
||||||
'less_than': self._condition_closure,
|
|
||||||
}
|
|
||||||
self.accepted_cond_ops = self.condition_operator.keys()
|
|
||||||
self.append_operator = False
|
|
||||||
self.simple_query = True
|
|
||||||
self.data = module.params['query']
|
|
||||||
|
|
||||||
def _condition_closure(self, cond, query_field, query_value):
|
|
||||||
self.qb.field(query_field)
|
|
||||||
getattr(self.qb, cond)(query_value)
|
|
||||||
|
|
||||||
def _iterate_fields(self, data, logic_op, cond_op):
|
|
||||||
if isinstance(data, dict):
|
|
||||||
for query_field, query_value in data.items():
|
|
||||||
if self.append_operator:
|
|
||||||
getattr(self.qb, logic_op)()
|
|
||||||
self.condition_operator[cond_op](cond_op, query_field, query_value)
|
|
||||||
self.append_operator = True
|
|
||||||
else:
|
|
||||||
self.module.fail_json(msg='Query is not in a supported format')
|
|
||||||
|
|
||||||
def _iterate_conditions(self, data, logic_op):
|
|
||||||
if isinstance(data, dict):
|
|
||||||
for cond_op, fields in data.items():
|
|
||||||
if (cond_op in self.accepted_cond_ops):
|
|
||||||
self._iterate_fields(fields, logic_op, cond_op)
|
|
||||||
else:
|
|
||||||
self.module.fail_json(msg='Supported conditions: {0}'.format(str(self.condition_operator.keys())))
|
|
||||||
else:
|
|
||||||
self.module.fail_json(msg='Supported conditions: {0}'.format(str(self.condition_operator.keys())))
|
|
||||||
|
|
||||||
def _iterate_operators(self, data):
|
|
||||||
if isinstance(data, dict):
|
|
||||||
for logic_op, cond_op in data.items():
|
|
||||||
if (logic_op in self.logic_operators):
|
|
||||||
self.simple_query = False
|
|
||||||
self._iterate_conditions(cond_op, logic_op)
|
|
||||||
elif self.simple_query:
|
|
||||||
self.condition_operator['equals']('equals', logic_op, cond_op)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.module.fail_json(msg='Query is not in a supported format')
|
|
||||||
else:
|
|
||||||
self.module.fail_json(msg='Supported operators: {0}'.format(str(self.logic_operators)))
|
|
||||||
|
|
||||||
def build_query(self):
|
|
||||||
self.qb = pysnow.QueryBuilder()
|
|
||||||
self._iterate_operators(self.data)
|
|
||||||
return (self.qb)
|
|
||||||
|
|
||||||
|
|
||||||
def run_module():
|
|
||||||
# define the available arguments/parameters that a user can pass to
|
|
||||||
# the module
|
|
||||||
module_args = ServiceNowClient.snow_argument_spec()
|
|
||||||
module_args.update(
|
|
||||||
table=dict(type='str', required=False, default='incident'),
|
|
||||||
query=dict(type='dict', required=True),
|
|
||||||
max_records=dict(default=20, type='int', required=False),
|
|
||||||
order_by=dict(default='-created_on', type='str', required=False),
|
|
||||||
return_fields=dict(default=None, type='list', required=False)
|
|
||||||
)
|
|
||||||
module_required_together = [
|
|
||||||
['client_id', 'client_secret']
|
|
||||||
]
|
|
||||||
|
|
||||||
module = AnsibleModule(
|
|
||||||
argument_spec=module_args,
|
|
||||||
supports_check_mode=True,
|
|
||||||
required_together=module_required_together
|
|
||||||
)
|
|
||||||
|
|
||||||
# Connect to ServiceNow
|
|
||||||
service_now_client = ServiceNowClient(module)
|
|
||||||
service_now_client.login()
|
|
||||||
conn = service_now_client.conn
|
|
||||||
|
|
||||||
params = module.params
|
|
||||||
instance = params['instance']
|
|
||||||
table = params['table']
|
|
||||||
query = params['query']
|
|
||||||
max_records = params['max_records']
|
|
||||||
return_fields = params['return_fields']
|
|
||||||
|
|
||||||
result = dict(
|
|
||||||
changed=False,
|
|
||||||
instance=instance,
|
|
||||||
table=table,
|
|
||||||
query=query,
|
|
||||||
max_records=max_records,
|
|
||||||
return_fields=return_fields
|
|
||||||
)
|
|
||||||
|
|
||||||
# Do the lookup
|
|
||||||
try:
|
|
||||||
bq = BuildQuery(module)
|
|
||||||
qb = bq.build_query()
|
|
||||||
record = conn.query(table=module.params['table'],
|
|
||||||
query=qb)
|
|
||||||
if module.params['return_fields'] is not None:
|
|
||||||
res = record.get_multiple(fields=module.params['return_fields'],
|
|
||||||
limit=module.params['max_records'],
|
|
||||||
order_by=[module.params['order_by']])
|
|
||||||
else:
|
|
||||||
res = record.get_multiple(limit=module.params['max_records'],
|
|
||||||
order_by=[module.params['order_by']])
|
|
||||||
except Exception as detail:
|
|
||||||
module.fail_json(msg='Failed to find record: {0}'.format(to_native(detail)), **result)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result['record'] = list(res)
|
|
||||||
except pysnow.exceptions.NoResults:
|
|
||||||
result['record'] = []
|
|
||||||
|
|
||||||
module.exit_json(**result)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
run_module()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
@ -1,48 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Copyright: (c) 2019, Ansible Project
|
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
class ModuleDocFragment(object):
|
|
||||||
# Parameters for Service Now modules
|
|
||||||
DOCUMENTATION = r'''
|
|
||||||
options:
|
|
||||||
instance:
|
|
||||||
description:
|
|
||||||
- The ServiceNow instance name, without the domain, service-now.com.
|
|
||||||
- If the value is not specified in the task, the value of environment variable C(SN_INSTANCE) will be used instead.
|
|
||||||
- Environment variable support added in Ansible 2.9.
|
|
||||||
required: false
|
|
||||||
type: str
|
|
||||||
username:
|
|
||||||
description:
|
|
||||||
- Name of user for connection to ServiceNow.
|
|
||||||
- Required whether using Basic or OAuth authentication.
|
|
||||||
- If the value is not specified in the task, the value of environment variable C(SN_USERNAME) will be used instead.
|
|
||||||
- Environment variable support added in Ansible 2.9.
|
|
||||||
required: false
|
|
||||||
type: str
|
|
||||||
password:
|
|
||||||
description:
|
|
||||||
- Password for username.
|
|
||||||
- Required whether using Basic or OAuth authentication.
|
|
||||||
- If the value is not specified in the task, the value of environment variable C(SN_PASSWORD) will be used instead.
|
|
||||||
- Environment variable support added in Ansible 2.9.
|
|
||||||
required: false
|
|
||||||
type: str
|
|
||||||
client_id:
|
|
||||||
description:
|
|
||||||
- Client ID generated by ServiceNow.
|
|
||||||
required: false
|
|
||||||
version_added: "2.9"
|
|
||||||
type: str
|
|
||||||
client_secret:
|
|
||||||
description:
|
|
||||||
- Client Secret associated with client id.
|
|
||||||
required: false
|
|
||||||
version_added: "2.9"
|
|
||||||
type: str
|
|
||||||
'''
|
|
Loading…
Reference in New Issue