From a86195623bd8bfaa45407817ad9097ce37b54259 Mon Sep 17 00:00:00 2001 From: John Westcott IV <32551173+john-westcott-iv@users.noreply.github.com> Date: Fri, 17 Jul 2020 00:39:37 -0400 Subject: [PATCH] Adding ODBC module (#606) * Adding ODBC module * Adding symink and fixing docs and argspec * Another sanity issue * Hopefully last fix for elements * Making changes suggested by felixfontein * Making changes suggested by Andersson007 * Removing defaults and added info in description * Fixing line too long * More cleanup suggested by felixfontein * Changing module call --- plugins/modules/database/misc/odbc.py | 157 ++++++++++++++++++ plugins/modules/odbc.py | 1 + tests/integration/targets/odbc/aliases | 5 + .../targets/odbc/defaults/main.yml | 27 +++ tests/integration/targets/odbc/meta/main.yml | 3 + .../targets/odbc/tasks/install_pyodbc.yml | 7 + tests/integration/targets/odbc/tasks/main.yml | 144 ++++++++++++++++ .../targets/odbc/tasks/negative_tests.yml | 19 +++ .../targets/odbc/tasks/no_pyodbc.yml | 11 ++ 9 files changed, 374 insertions(+) create mode 100644 plugins/modules/database/misc/odbc.py create mode 120000 plugins/modules/odbc.py create mode 100644 tests/integration/targets/odbc/aliases create mode 100644 tests/integration/targets/odbc/defaults/main.yml create mode 100644 tests/integration/targets/odbc/meta/main.yml create mode 100644 tests/integration/targets/odbc/tasks/install_pyodbc.yml create mode 100644 tests/integration/targets/odbc/tasks/main.yml create mode 100644 tests/integration/targets/odbc/tasks/negative_tests.yml create mode 100644 tests/integration/targets/odbc/tasks/no_pyodbc.yml diff --git a/plugins/modules/database/misc/odbc.py b/plugins/modules/database/misc/odbc.py new file mode 100644 index 0000000000..4ebda69747 --- /dev/null +++ b/plugins/modules/database/misc/odbc.py @@ -0,0 +1,157 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, John Westcott +# 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 + +DOCUMENTATION = ''' +--- +module: odbc +author: "John Westcott IV (@john-westcott-iv)" +version_added: "1.0.0" +short_description: Execute SQL via ODBC +description: + - Read/Write info via ODBC drivers. +options: + dsn: + description: + - The connection string passed into ODBC. + required: yes + type: str + query: + description: + - The SQL query to perform. + required: yes + type: str + params: + description: + - Parameters to pass to the SQL query. + type: list + elements: str + +requirements: + - "python >= 2.6" + - "pyodbc" + +notes: + - "Like the command module, this module always returns changed = yes whether or not the query would change the database." + - "To alter this behavior you can use C(changed_when): [yes or no]." + - "For details about return values (description and row_count) see U(https://github.com/mkleehammer/pyodbc/wiki/Cursor)." +''' + +EXAMPLES = ''' +- name: Set some values in the test db + community.general.odbc: + dsn: "DRIVER={ODBC Driver 13 for SQL Server};Server=db.ansible.com;Database=my_db;UID=admin;PWD=password;" + query: "Select * from table_a where column1 = ?" + params: + - "value1" + changed_when: no +''' + +RETURN = ''' +results: + description: List of lists of strings containing selected rows, likely empty for DDL statements. + returned: success + type: list + elements: list +description: + description: "List of dicts about the columns selected from the cursors, likely empty for DDL statements. See notes." + returned: success + type: list + elements: dict +row_count: + description: "The number of rows selected or modified according to the cursor defaults to -1. See notes." + returned: success + type: str +''' + +from ansible.module_utils.basic import AnsibleModule, missing_required_lib +from ansible.module_utils._text import to_native + +HAS_PYODBC = None +try: + import pyodbc + HAS_PYODBC = True +except ImportError as e: + HAS_PYODBC = False + + +def main(): + module = AnsibleModule( + argument_spec=dict( + dsn=dict(type='str', required=True, no_log=True), + query=dict(type='str', required=True), + params=dict(type='list', elements='str'), + ), + ) + + dsn = module.params.get('dsn') + query = module.params.get('query') + params = module.params.get('params') + + if not HAS_PYODBC: + module.fail_json(msg=missing_required_lib('pyodbc')) + + # Try to make a connection with the DSN + connection = None + try: + connection = pyodbc.connect(dsn) + except Exception as e: + module.fail_json(msg='Failed to connect to DSN: {0}'.format(to_native(e))) + + result = dict( + changed=True, + description=[], + row_count=-1, + results=[], + ) + + try: + cursor = connection.cursor() + + if params: + cursor.execute(query, params) + else: + cursor.execute(query) + cursor.commit() + try: + # Get the rows out into an 2d array + for row in cursor.fetchall(): + new_row = [] + for column in row: + new_row.append("{0}".format(column)) + result['results'].append(new_row) + + # Return additional information from the cursor + for row_description in cursor.description: + description = {} + description['name'] = row_description[0] + description['type'] = row_description[1].__name__ + description['display_size'] = row_description[2] + description['internal_size'] = row_description[3] + description['precision'] = row_description[4] + description['scale'] = row_description[5] + description['nullable'] = row_description[6] + result['description'].append(description) + + result['row_count'] = cursor.rowcount + except pyodbc.ProgrammingError as pe: + pass + except Exception as e: + module.fail_json(msg="Exception while reading rows: {0}".format(to_native(e))) + + cursor.close() + except Exception as e: + module.fail_json(msg="Failed to execute query: {0}".format(to_native(e))) + finally: + connection.close() + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/odbc.py b/plugins/modules/odbc.py new file mode 120000 index 0000000000..ee3d8312c6 --- /dev/null +++ b/plugins/modules/odbc.py @@ -0,0 +1 @@ +./database/misc/odbc.py \ No newline at end of file diff --git a/tests/integration/targets/odbc/aliases b/tests/integration/targets/odbc/aliases new file mode 100644 index 0000000000..bcab1c2f93 --- /dev/null +++ b/tests/integration/targets/odbc/aliases @@ -0,0 +1,5 @@ +destructive +shippable/posix/group1 +skip/osx +skip/rhel8.0 +skip/freebsd diff --git a/tests/integration/targets/odbc/defaults/main.yml b/tests/integration/targets/odbc/defaults/main.yml new file mode 100644 index 0000000000..f6efa75078 --- /dev/null +++ b/tests/integration/targets/odbc/defaults/main.yml @@ -0,0 +1,27 @@ +--- +# defaults file for test_postgresql_db +my_user: 'ansible_user' +my_pass: 'md5d5e044ccd9b4b8adc89e8fed2eb0db8a' +my_pass_decrypted: '6EjMk