diff --git a/lib/ansible/modules/notification/snow_record.py b/lib/ansible/modules/notification/snow_record.py new file mode 100755 index 00000000000..f0493ec4c94 --- /dev/null +++ b/lib/ansible/modules/notification/snow_record.py @@ -0,0 +1,331 @@ +#!/usr/bin/python +# Copyright (c) 2017 Tim Rightnour +# 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: Create/Delete/Update records in ServiceNow + +version_added: "2.5" + +description: + - Creates/Deletes/Updates a single record in ServiceNow + +options: + instance: + description: + - The service now instance name + required: true + username: + description: + - User to connect to ServiceNow as + required: true + password: + description: + - Password for username + required: true + table: + description: + - Table to query for records + required: false + default: incident + 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 + data: + description: + - key, value pairs of data to load into the record. + See Examples. Required for C(state:present) + number: + description: + - Record number to update. Required for C(state:absent) + required: false + lookup_field: + description: + - Changes the field that C(number) uses to find records + required: false + default: number + attachment: + description: + - Attach a file to the record + required: false + +requirements: + - python pysnow (pysnow) + +author: + - Tim Rightnour (@garbled1) +''' + +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: 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 + +# Pull in pysnow +HAS_PYSNOW = False +try: + import pysnow + HAS_PYSNOW = True + +except ImportError: + pass + + +def run_module(): + # define the available arguments/parameters that a user can pass to + # the module + module_args = dict( + instance=dict(default=None, type='str', required=True), + username=dict(default=None, type='str', required=True, no_log=True), + password=dict(default=None, type='str', required=True, no_log=True), + 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, requried=False, type='dict'), + lookup_field=dict(default='number', required=False, type='str'), + attachment=dict(default=None, required=False, type='str') + ) + module_required_if = [ + ['state', 'absent', ['number']], + ] + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=module_required_if + ) + + # check for pysnow + if not HAS_PYSNOW: + module.fail_json(msg='pysnow module required') + + params = module.params + instance = params['instance'] + username = params['username'] + password = params['password'] + 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 + + # Connect to ServiceNow + try: + conn = pysnow.Client(instance=instance, user=username, + password=password) + except Exception as detail: + module.fail_json(msg='Could not connect to ServiceNow: {0}'.format(str(detail)), **result) + + # 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(str(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(str(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.UnexpectedResponse 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) + 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.UnexpectedResponse 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 Exception as detail: + snow_error = "Failed to delete record: {0}".format(str(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.UnexpectedResponse 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 Exception as detail: + snow_error = "Failed to update record: {0}".format(str(detail)) + module.fail_json(msg=snow_error, **result) + + module.exit_json(**result) + + +def main(): + run_module() + +if __name__ == '__main__': + main()