From 8f7e9f73ce69a413cd8d650b50635f55674492d1 Mon Sep 17 00:00:00 2001 From: Ansible Core Team Date: Mon, 9 Mar 2020 09:40:31 +0000 Subject: [PATCH] Migrated to servicenow.servicenow --- lib/ansible/module_utils/service_now.py | 94 ----- .../modules/notification/snow_record.py | 333 ------------------ .../modules/notification/snow_record_find.py | 273 -------------- .../plugins/doc_fragments/service_now.py | 48 --- test/sanity/ignore.txt | 1 - 5 files changed, 749 deletions(-) delete mode 100644 lib/ansible/module_utils/service_now.py delete mode 100644 lib/ansible/modules/notification/snow_record.py delete mode 100644 lib/ansible/modules/notification/snow_record_find.py delete mode 100644 lib/ansible/plugins/doc_fragments/service_now.py diff --git a/lib/ansible/module_utils/service_now.py b/lib/ansible/module_utils/service_now.py deleted file mode 100644 index 7c7fa7839cd..00000000000 --- a/lib/ansible/module_utils/service_now.py +++ /dev/null @@ -1,94 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright: (c) 2019, Ansible Project -# Copyright: (c) 2017, Tim Rightnour -# 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), - ) diff --git a/lib/ansible/modules/notification/snow_record.py b/lib/ansible/modules/notification/snow_record.py deleted file mode 100644 index a967343bed6..00000000000 --- a/lib/ansible/modules/notification/snow_record.py +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# 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: 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() diff --git a/lib/ansible/modules/notification/snow_record_find.py b/lib/ansible/modules/notification/snow_record_find.py deleted file mode 100644 index f6abf1caa64..00000000000 --- a/lib/ansible/modules/notification/snow_record_find.py +++ /dev/null @@ -1,273 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# 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_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() diff --git a/lib/ansible/plugins/doc_fragments/service_now.py b/lib/ansible/plugins/doc_fragments/service_now.py deleted file mode 100644 index e890778da7b..00000000000 --- a/lib/ansible/plugins/doc_fragments/service_now.py +++ /dev/null @@ -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 -''' diff --git a/test/sanity/ignore.txt b/test/sanity/ignore.txt index 37c3182fdfa..e6bf7868ed8 100644 --- a/test/sanity/ignore.txt +++ b/test/sanity/ignore.txt @@ -3530,7 +3530,6 @@ lib/ansible/modules/network/vyos/vyos_vlan.py validate-modules:missing-suboption lib/ansible/modules/network/vyos/vyos_vlan.py validate-modules:parameter-list-no-elements lib/ansible/modules/network/vyos/vyos_vlan.py validate-modules:parameter-type-not-in-doc lib/ansible/modules/network/vyos/vyos_vlan.py validate-modules:undocumented-parameter -lib/ansible/modules/notification/snow_record_find.py validate-modules:parameter-list-no-elements lib/ansible/modules/packaging/language/pip.py pylint:blacklisted-name lib/ansible/modules/packaging/language/pip.py validate-modules:doc-elements-mismatch lib/ansible/modules/packaging/language/pip.py validate-modules:invalid-ansiblemodule-schema