From 09dfd42d50b3477cf78aaec05467c640f822a3bd Mon Sep 17 00:00:00 2001
From: Dariusz Owczarek <Dariusz.Owczarek@sabre.com>
Date: Mon, 29 Dec 2014 16:50:43 +0100
Subject: [PATCH] new vertica modules

---
 database/vertica/__init__.py              |   0
 database/vertica/vertica_configuration.py | 198 +++++++++++
 database/vertica/vertica_facts.py         | 276 +++++++++++++++
 database/vertica/vertica_role.py          | 246 ++++++++++++++
 database/vertica/vertica_schema.py        | 320 ++++++++++++++++++
 database/vertica/vertica_user.py          | 388 ++++++++++++++++++++++
 6 files changed, 1428 insertions(+)
 create mode 100644 database/vertica/__init__.py
 create mode 100644 database/vertica/vertica_configuration.py
 create mode 100644 database/vertica/vertica_facts.py
 create mode 100644 database/vertica/vertica_role.py
 create mode 100644 database/vertica/vertica_schema.py
 create mode 100644 database/vertica/vertica_user.py

diff --git a/database/vertica/__init__.py b/database/vertica/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/database/vertica/vertica_configuration.py b/database/vertica/vertica_configuration.py
new file mode 100644
index 00000000000..6ee5ebe5f7f
--- /dev/null
+++ b/database/vertica/vertica_configuration.py
@@ -0,0 +1,198 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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/>.
+
+DOCUMENTATION = """
+---
+module: vertica_configuration
+version_added: '1.0'
+short_description: Updates Vertica configuration parameters.
+description:
+  Updates Vertica configuration parameters.
+options:
+  name:
+    description:
+      Name of the parameter to update.
+    required: true
+    default: null
+  value:
+    description:
+      Value of the parameter to be set.
+    required: true
+    default: null
+  db:
+    description:
+      Name of the Vertica database.
+    required: false
+    default: null
+  cluster:
+    description:
+      Name of the Vertica cluster.
+    required: false
+    default: localhost
+  port:
+    description:
+      Vertica cluster port to connect to.
+    required: false
+    default: 5433
+  login_user:
+    description:
+      The username used to authenticate with.
+    required: false
+    default: dbadmin
+  login_password:
+    description:
+      The password used to authenticate with.
+    required: false
+    default: null
+notes:
+  The default authentication assumes that you are either logging in as or sudo'ing
+  to the C(dbadmin) account on the host.
+  This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
+  that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
+  Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
+  to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
+  and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
+  to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
+requirements: [ 'unixODBC', 'pyodbc' ]
+author: Dariusz Owczarek
+"""
+
+EXAMPLES = """
+Examples:
+
+- name: updating load_balance_policy
+  vertica_configuration: name=failovertostandbyafter value='8 hours'
+"""
+
+try:
+    import pyodbc
+except ImportError:
+    pyodbc_found = False
+else:
+    pyodbc_found = True
+
+class NotSupportedError(Exception):
+    pass
+
+class CannotDropError(Exception):
+    pass
+
+# module specific functions
+
+def get_configuration_facts(cursor, parameter_name=''):
+    facts = {}
+    cursor.execute("""
+        select c.parameter_name, c.current_value, c.default_value
+        from configuration_parameters c
+        where c.node_name = 'ALL'
+        and (? = '' or c.parameter_name ilike ?)
+    """, parameter_name, parameter_name)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            facts[row.parameter_name.lower()] = {
+                'parameter_name': row.parameter_name,
+                'current_value': row.current_value,
+                'default_value': row.default_value}
+    return facts
+    
+def check(configuration_facts, parameter_name, current_value):
+    parameter_key = parameter_name.lower()
+    if current_value and current_value.lower() != configuration_facts[parameter_key]['current_value'].lower():
+        return False
+    return True
+
+def present(configuration_facts, cursor, parameter_name, current_value):
+    parameter_key = parameter_name.lower()
+    changed = False
+    if current_value and current_value.lower() != configuration_facts[parameter_key]['current_value'].lower():
+        cursor.execute("select set_config_parameter('{0}', '{1}')".format(parameter_name, current_value))
+        changed = True
+    if changed:
+        configuration_facts.update(get_configuration_facts(cursor, parameter_name))
+    return changed
+
+# module logic
+
+def main():
+
+    module = AnsibleModule(
+        argument_spec=dict(
+            parameter=dict(required=True, aliases=['name']),
+            value=dict(default=None),
+            db=dict(default=None),
+            cluster=dict(default='localhost'),
+            port=dict(default='5433'),
+            login_user=dict(default='dbadmin'),
+            login_password=dict(default=None),
+        ), supports_check_mode = True)
+
+    if not pyodbc_found:
+        module.fail_json(msg="The python pyodbc module is required.")
+
+    parameter_name = module.params['parameter']
+    current_value = module.params['value']
+    db = ''
+    if module.params['db']:
+        db = module.params['db']
+
+    changed = False
+
+    try:
+        dsn = (
+            "Driver=Vertica;"
+            "Server={0};"
+            "Port={1};"
+            "Database={2};"
+            "User={3};"
+            "Password={4};"
+            "ConnectionLoadBalance={5}"
+            ).format(module.params['cluster'], module.params['port'], db,
+                module.params['login_user'], module.params['login_password'], 'true')
+        db_conn = pyodbc.connect(dsn, autocommit=True)
+        cursor = db_conn.cursor()
+    except Exception, e:
+        module.fail_json(msg="Unable to connect to database: {0}.".format(e))
+
+    try:
+        configuration_facts = get_configuration_facts(cursor)
+        if module.check_mode:
+            changed = not check(configuration_facts, parameter_name, current_value)
+        else:
+            try:
+                changed = present(configuration_facts, cursor, parameter_name, current_value)
+            except pyodbc.Error, e:
+                module.fail_json(msg=str(e))
+    except NotSupportedError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_configuration': configuration_facts})
+    except CannotDropError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_configuration': configuration_facts})
+    except SystemExit:
+        # avoid catching this on python 2.4
+        raise
+    except Exception, e:
+        module.fail_json(msg=e)
+
+    module.exit_json(changed=changed, parameter=parameter_name, ansible_facts={'vertica_configuration': configuration_facts})
+
+# import ansible utilities
+from ansible.module_utils.basic import *
+if __name__ == '__main__':
+    main()
diff --git a/database/vertica/vertica_facts.py b/database/vertica/vertica_facts.py
new file mode 100644
index 00000000000..2334cbaa227
--- /dev/null
+++ b/database/vertica/vertica_facts.py
@@ -0,0 +1,276 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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/>.
+
+DOCUMENTATION = """
+---
+module: vertica_facts
+version_added: '1.0'
+short_description: Gathers Vertica database facts.
+description:
+  Gathers Vertica database facts.
+options:
+  cluster:
+    description:
+      Name of the cluster running the schema.
+    required: false
+    default: localhost
+  port:
+    description:
+      Database port to connect to.
+    required: false
+    default: 5433
+  db:
+    description:
+      Name of the database running the schema.
+    required: false
+    default: null
+  login_user:
+    description:
+      The username used to authenticate with.
+    required: false
+    default: dbadmin
+  login_password:
+    description:
+      The password used to authenticate with.
+    required: false
+    default: null
+notes:
+  The default authentication assumes that you are either logging in as or sudo'ing
+  to the C(dbadmin) account on the host.
+  This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
+  that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
+  Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
+  to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
+  and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
+  to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
+requirements: [ 'unixODBC', 'pyodbc' ]
+author: Dariusz Owczarek
+"""
+
+EXAMPLES = """
+- name: gathering vertica facts
+  vertica_facts: db=db_name
+"""
+
+try:
+    import pyodbc
+except ImportError:
+    pyodbc_found = False
+else:
+    pyodbc_found = True
+
+class NotSupportedError(Exception):
+    pass
+
+# module specific functions
+
+def get_schema_facts(cursor, schema=''):
+    facts = {}
+    cursor.execute("""
+        select schema_name, schema_owner, create_time
+        from schemata
+        where not is_system_schema and schema_name not in ('public')
+        and (? = '' or schema_name ilike ?)
+    """, schema, schema)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            facts[row.schema_name.lower()] = {
+                'name': row.schema_name,
+                'owner': row.schema_owner,
+                'create_time': str(row.create_time),
+                'usage_roles': [],
+                'create_roles': []}
+    cursor.execute("""
+        select g.object_name as schema_name, r.name as role_name,
+        lower(g.privileges_description) privileges_description
+        from roles r join grants g
+        on g.grantee = r.name and g.object_type='SCHEMA'
+        and g.privileges_description like '%USAGE%'
+        and g.grantee not in ('public', 'dbadmin')
+        and (? = '' or g.object_name ilike ?)
+    """, schema, schema)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            schema_key = row.schema_name.lower()
+            if 'create' in row.privileges_description:
+                facts[schema_key]['create_roles'].append(row.role_name)
+            else:
+                facts[schema_key]['usage_roles'].append(row.role_name)
+    return facts
+
+def get_user_facts(cursor, user=''):
+    facts = {}
+    cursor.execute("""
+        select u.user_name, u.is_locked, u.lock_time,
+        p.password, p.acctexpired as is_expired,
+        u.profile_name, u.resource_pool,
+        u.all_roles, u.default_roles
+        from users u join password_auditor p on p.user_id = u.user_id
+        where not u.is_super_user
+        and (? = '' or u.user_name ilike ?)
+     """, user, user)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            user_key = row.user_name.lower()
+            facts[user_key] = {
+                'name': row.user_name,
+                'locked': str(row.is_locked),
+                'password': row.password,
+                'expired': str(row.is_expired),
+                'profile': row.profile_name,
+                'resource_pool': row.resource_pool,
+                'roles': [],
+                'default_roles': []}
+            if row.is_locked:
+                facts[user_key]['locked_time'] = str(row.lock_time)
+            if row.all_roles:
+                facts[user_key]['roles'] = row.all_roles.replace(' ', '').split(',')
+            if row.default_roles:
+                facts[user_key]['default_roles'] = row.default_roles.replace(' ', '').split(',')
+    return facts
+
+def get_role_facts(cursor, role=''):
+    facts = {}
+    cursor.execute("""
+        select r.name, r.assigned_roles
+        from roles r
+        where (? = '' or r.name ilike ?)
+    """, role, role)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            role_key = row.name.lower()
+            facts[role_key] = {
+                'name': row.name,
+                'assigned_roles': []}
+            if row.assigned_roles:
+                facts[role_key]['assigned_roles'] = row.assigned_roles.replace(' ', '').split(',')
+    return facts
+
+def get_configuration_facts(cursor, parameter=''):
+    facts = {}
+    cursor.execute("""
+        select c.parameter_name, c.current_value, c.default_value
+        from configuration_parameters c
+        where c.node_name = 'ALL'
+        and (? = '' or c.parameter_name ilike ?)
+    """, parameter, parameter)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            facts[row.parameter_name.lower()] = {
+                'parameter_name': row.parameter_name,
+                'current_value': row.current_value,
+                'default_value': row.default_value}
+    return facts
+
+def get_node_facts(cursor, schema=''):
+    facts = {}
+    cursor.execute("""
+        select node_name, node_address, export_address, node_state, node_type,
+            catalog_path
+        from nodes
+    """)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            facts[row.node_address] = {
+                'node_name': row.node_name,
+                'export_address': row.export_address,
+                'node_state': row.node_state,
+                'node_type': row.node_type,
+                'catalog_path': row.catalog_path}
+    return facts
+
+# module logic
+
+def main():
+
+    module = AnsibleModule(
+        argument_spec=dict(
+            cluster=dict(default='localhost'),
+            port=dict(default='5433'),
+            db=dict(default=None),
+            login_user=dict(default='dbadmin'),
+            login_password=dict(default=None),
+        ), supports_check_mode = True)
+
+    if not pyodbc_found:
+        module.fail_json(msg="The python pyodbc module is required.")
+
+    db = ''
+    if module.params['db']:
+        db = module.params['db']
+
+    changed = False
+
+    try:
+        dsn = (
+            "Driver=Vertica;"
+            "Server={0};"
+            "Port={1};"
+            "Database={2};"
+            "User={3};"
+            "Password={4};"
+            "ConnectionLoadBalance={5}"
+            ).format(module.params['cluster'], module.params['port'], db,
+                module.params['login_user'], module.params['login_password'], 'true')
+        db_conn = pyodbc.connect(dsn, autocommit=True)
+        cursor = db_conn.cursor()
+    except Exception, e:
+        module.fail_json(msg="Unable to connect to database: {0}.".format(e))
+        
+    try:
+        schema_facts = get_schema_facts(cursor)
+        user_facts = get_user_facts(cursor)
+        role_facts = get_role_facts(cursor)
+        configuration_facts = get_configuration_facts(cursor)
+        node_facts = get_node_facts(cursor)
+        module.exit_json(changed=False,
+            ansible_facts={'vertica_schemas': schema_facts,
+                           'vertica_users': user_facts,
+                           'vertica_roles': role_facts,
+                           'vertica_configuration': configuration_facts,
+                           'vertica_nodes': node_facts})
+    except NotSupportedError, e:
+        module.fail_json(msg=str(e))
+    except SystemExit:
+        # avoid catching this on python 2.4
+        raise
+    except Exception, e:
+        module.fail_json(msg=e)
+
+# import ansible utilities
+from ansible.module_utils.basic import *
+if __name__ == '__main__':
+    main()
diff --git a/database/vertica/vertica_role.py b/database/vertica/vertica_role.py
new file mode 100644
index 00000000000..dad6c5c3bc9
--- /dev/null
+++ b/database/vertica/vertica_role.py
@@ -0,0 +1,246 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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/>.
+
+DOCUMENTATION = """
+---
+module: vertica_role
+version_added: '1.0'
+short_description: Adds or removes Vertica database roles and assigns roles to them.
+description:
+  Adds or removes Vertica database role and, optionally, assign other roles.
+options:
+  name:
+    description:
+      Name of the role to add or remove.
+    required: true
+    default: null
+  assigned_roles:
+    description:
+      Comma separated list of roles to assign to the role.
+      [Alias I(assigned_role)]
+    required: false
+    default: null
+  state:
+    description:
+      Whether to create C(present), drop C(absent) or lock C(locked) a role.
+    required: false
+    choices: ['present', 'absent']
+    default: present
+  db:
+    description:
+      Name of the Vertica database.
+    required: false
+    default: null
+  cluster:
+    description:
+      Name of the Vertica cluster.
+    required: false
+    default: localhost
+  port:
+    description:
+      Vertica cluster port to connect to.
+    required: false
+    default: 5433
+  login_user:
+    description:
+      The username used to authenticate with.
+    required: false
+    default: dbadmin
+  login_password:
+    description:
+      The password used to authenticate with.
+    required: false
+    default: null
+notes:
+  The default authentication assumes that you are either logging in as or sudo'ing
+  to the C(dbadmin) account on the host.
+  This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
+  that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
+  Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
+  to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
+  and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
+  to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
+requirements: [ 'unixODBC', 'pyodbc' ]
+author: Dariusz Owczarek
+"""
+
+EXAMPLES = """
+Examples:
+
+- name: creating a new vertica role
+  vertica_role: name=role_name db=db_name state=present
+
+- name: creating a new vertica role with other role assigned
+  vertica_role: name=role_name assigned_role=other_role_name state=present
+"""
+
+try:
+    import pyodbc
+except ImportError:
+    pyodbc_found = False
+else:
+    pyodbc_found = True
+
+class NotSupportedError(Exception):
+    pass
+
+class CannotDropError(Exception):
+    pass
+
+# module specific functions
+
+def get_role_facts(cursor, role=''):
+    facts = {}
+    cursor.execute("""
+        select r.name, r.assigned_roles
+        from roles r
+        where (? = '' or r.name ilike ?)
+    """, role, role)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            role_key = row.name.lower()
+            facts[role_key] = {
+                'name': row.name,
+                'assigned_roles': []}
+            if row.assigned_roles:
+                facts[role_key]['assigned_roles'] = row.assigned_roles.replace(' ', '').split(',')
+    return facts
+
+def update_roles(role_facts, cursor, role,
+                 existing, required):
+    for assigned_role in set(existing) - set(required):
+        cursor.execute("revoke {0} from {1}".format(assigned_role, role))
+    for assigned_role in set(required) - set(existing):
+        cursor.execute("grant {0} to {1}".format(assigned_role, role))
+        
+def check(role_facts, role, assigned_roles):
+    role_key = role.lower()
+    if role_key not in role_facts:
+       return False
+    if assigned_roles and cmp(sorted(assigned_roles), sorted(role_facts[role_key]['assigned_roles'])) != 0:
+        return False
+    return True
+
+def present(role_facts, cursor, role, assigned_roles):
+    role_key = role.lower()
+    if role_key not in role_facts:
+        cursor.execute("create role {0}".format(role))
+        update_roles(role_facts, cursor, role, [], assigned_roles)
+        role_facts.update(get_role_facts(cursor, role))
+        return True
+    else:
+        changed = False
+        if assigned_roles and cmp(sorted(assigned_roles), sorted(role_facts[role_key]['assigned_roles'])) != 0:
+            update_roles(role_facts, cursor, role,
+                role_facts[role_key]['assigned_roles'], assigned_roles)
+            changed = True
+        if changed:
+            role_facts.update(get_role_facts(cursor, role))
+        return changed
+
+def absent(role_facts, cursor, role, assigned_roles):
+    role_key = role.lower()
+    if role_key in role_facts:
+        update_roles(role_facts, cursor, role,
+            role_facts[role_key]['assigned_roles'], [])
+        cursor.execute("drop role {0} cascade".format(role_facts[role_key]['name']))
+        del role_facts[role_key]
+        return True
+    else:
+        return False
+
+# module logic
+
+def main():
+
+    module = AnsibleModule(
+        argument_spec=dict(
+            role=dict(required=True, aliases=['name']),
+            assigned_roles=dict(default=None, aliases=['assigned_role']),
+            state=dict(default='present', choices=['absent', 'present']),
+            db=dict(default=None),
+            cluster=dict(default='localhost'),
+            port=dict(default='5433'),
+            login_user=dict(default='dbadmin'),
+            login_password=dict(default=None),
+        ), supports_check_mode = True)
+
+    if not pyodbc_found:
+        module.fail_json(msg="The python pyodbc module is required.")
+
+    role = module.params['role']
+    assigned_roles = []
+    if module.params['assigned_roles']:
+        assigned_roles = module.params['assigned_roles'].split(',')
+        assigned_roles = filter(None, assigned_roles)
+    state = module.params['state']
+    db = ''
+    if module.params['db']:
+        db = module.params['db']
+
+    changed = False
+
+    try:
+        dsn = (
+            "Driver=Vertica;"
+            "Server={0};"
+            "Port={1};"
+            "Database={2};"
+            "User={3};"
+            "Password={4};"
+            "ConnectionLoadBalance={5}"
+            ).format(module.params['cluster'], module.params['port'], db,
+                module.params['login_user'], module.params['login_password'], 'true')
+        db_conn = pyodbc.connect(dsn, autocommit=True)
+        cursor = db_conn.cursor()
+    except Exception, e:
+        module.fail_json(msg="Unable to connect to database: {0}.".format(e))
+
+    try:
+        role_facts = get_role_facts(cursor)
+        if module.check_mode:
+            changed = not check(role_facts, role, assigned_roles)
+        elif state == 'absent':
+            try:
+                changed = absent(role_facts, cursor, role, assigned_roles)
+            except pyodbc.Error, e:
+                module.fail_json(msg=str(e))
+        elif state == 'present':
+            try:
+                changed = present(role_facts, cursor, role, assigned_roles)
+            except pyodbc.Error, e:
+                module.fail_json(msg=str(e))
+    except NotSupportedError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_roles': role_facts})
+    except CannotDropError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_roles': role_facts})
+    except SystemExit:
+        # avoid catching this on python 2.4
+        raise
+    except Exception, e:
+        module.fail_json(msg=e)
+
+    module.exit_json(changed=changed, role=role, ansible_facts={'vertica_roles': role_facts})
+
+# import ansible utilities
+from ansible.module_utils.basic import *
+if __name__ == '__main__':
+    main()
diff --git a/database/vertica/vertica_schema.py b/database/vertica/vertica_schema.py
new file mode 100644
index 00000000000..7bc57a545f6
--- /dev/null
+++ b/database/vertica/vertica_schema.py
@@ -0,0 +1,320 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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/>.
+
+DOCUMENTATION = """
+---
+module: vertica_schema
+version_added: '1.0'
+short_description: Adds or removes Vertica database schema and roles.
+description:
+  Adds or removes Vertica database schema and, optionally, roles 
+  with schema access privileges.
+  A schema will not be removed until all the objects have been dropped.
+  In such a situation, if the module tries to remove the schema it
+  will fail and only remove roles created for the schema if they have
+  no dependencies.
+options:
+  name:
+    description:
+      Name of the schema to add or remove.
+    required: true
+    default: null
+  usage_roles:
+    description:
+      Comma separated list of roles to create and grant usage access to the schema.
+      [Alias I(usage_role)]
+    required: false
+    default: null
+  create_roles:
+    description:
+      Comma separated list of roles to create and grant usage and create access to the schema.
+      [Alias I(create_role)]
+    required: false
+    default: null
+  owner:
+    description:
+      Name of the user to set as owner of the schema.
+    required: false
+    default: null
+  state:
+    description:
+      Whether to create C(present), or drop C(absent) a schema.
+    required: false
+    default: present
+    choices: ['present', 'absent']
+  db:
+    description:
+      Name of the Vertica database.
+    required: false
+    default: null
+  cluster:
+    description:
+      Name of the Vertica cluster.
+    required: false
+    default: localhost
+  port:
+    description:
+      Vertica cluster port to connect to.
+    required: false
+    default: 5433
+  login_user:
+    description:
+      The username used to authenticate with.
+    required: false
+    default: dbadmin
+  login_password:
+    description:
+      The password used to authenticate with.
+    required: false
+    default: null
+notes:
+  The default authentication assumes that you are either logging in as or sudo'ing
+  to the C(dbadmin) account on the host.
+  This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
+  that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
+  Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
+  to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
+  and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
+  to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
+requirements: [ 'unixODBC', 'pyodbc' ]
+author: Dariusz Owczarek
+"""
+
+EXAMPLES = """
+Examples:
+
+- name: creating a new vertica schema
+  vertica_schema: name=schema_name db=db_name state=present
+
+- name: creating a new schema with specific schema owner
+  vertica_schema: name=schema_name owner=dbowner db=db_name state=present
+
+- name: creating a new schema with roles
+  vertica_schema:
+    name=schema_name
+    create_roles=schema_name_all
+    usage_roles=schema_name_ro,schema_name_rw
+    db=db_name
+    state=present
+"""
+
+try:
+    import pyodbc
+except ImportError:
+    pyodbc_found = False
+else:
+    pyodbc_found = True
+
+class NotSupportedError(Exception):
+    pass
+
+class CannotDropError(Exception):
+    pass
+
+# module specific functions
+
+def get_schema_facts(cursor, schema=''):
+    facts = {}
+    cursor.execute("""
+        select schema_name, schema_owner, create_time
+        from schemata
+        where not is_system_schema and schema_name not in ('public', 'TxtIndex')
+        and (? = '' or schema_name ilike ?)
+    """, schema, schema)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            facts[row.schema_name.lower()] = {
+                'name': row.schema_name,
+                'owner': row.schema_owner,
+                'create_time': str(row.create_time),
+                'usage_roles': [],
+                'create_roles': []}
+    cursor.execute("""
+        select g.object_name as schema_name, r.name as role_name,
+        lower(g.privileges_description) privileges_description
+        from roles r join grants g
+        on g.grantee_id = r.role_id and g.object_type='SCHEMA'
+        and g.privileges_description like '%USAGE%'
+        and g.grantee not in ('public', 'dbadmin')
+        and (? = '' or g.object_name ilike ?)
+    """, schema, schema)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            schema_key = row.schema_name.lower()
+            if 'create' in row.privileges_description:
+                facts[schema_key]['create_roles'].append(row.role_name)
+            else:
+                facts[schema_key]['usage_roles'].append(row.role_name)
+    return facts
+
+def update_roles(schema_facts, cursor, schema,
+                 existing, required,
+                 create_existing, create_required):
+    for role in set(existing + create_existing) - set(required + create_required):
+        cursor.execute("drop role {0} cascade".format(role))
+    for role in set(create_existing) - set(create_required):
+        cursor.execute("revoke create on schema {0} from {1}".format(schema, role))
+    for role in set(required + create_required) - set(existing + create_existing):
+        cursor.execute("create role {0}".format(role))
+        cursor.execute("grant usage on schema {0} to {1}".format(schema, role))
+    for role in set(create_required) - set(create_existing):
+         cursor.execute("grant create on schema {0} to {1}".format(schema, role))
+
+def check(schema_facts, schema, usage_roles, create_roles, owner):
+    schema_key = schema.lower()
+    if schema_key not in schema_facts:
+       return False
+    if owner and owner.lower() == schema_facts[schema_key]['owner'].lower():
+        return False
+    if cmp(sorted(usage_roles), sorted(schema_facts[schema_key]['usage_roles'])) != 0:
+        return False
+    if cmp(sorted(create_roles), sorted(schema_facts[schema_key]['create_roles'])) != 0:
+        return False
+    return True
+
+def present(schema_facts, cursor, schema, usage_roles, create_roles, owner):
+    schema_key = schema.lower()
+    if schema_key not in schema_facts:
+        query_fragments = ["create schema {0}".format(schema)]
+        if owner:
+            query_fragments.append("authorization {0}".format(owner))
+        cursor.execute(' '.join(query_fragments))
+        update_roles(schema_facts, cursor, schema, [], usage_roles, [], create_roles)
+        schema_facts.update(get_schema_facts(cursor, schema))
+        return True
+    else:
+        changed = False
+        if owner and owner.lower() != schema_facts[schema_key]['owner'].lower():
+            raise NotSupportedError((
+                "Changing schema owner is not supported. "
+                "Current owner: {0}."
+                ).format(schema_facts[schema_key]['owner']))
+        if cmp(sorted(usage_roles), sorted(schema_facts[schema_key]['usage_roles'])) != 0 or \
+            cmp(sorted(create_roles), sorted(schema_facts[schema_key]['create_roles'])) != 0:
+            update_roles(schema_facts, cursor, schema,
+                schema_facts[schema_key]['usage_roles'], usage_roles,
+                schema_facts[schema_key]['create_roles'], create_roles)
+            changed = True
+        if changed:
+            schema_facts.update(get_schema_facts(cursor, schema))
+        return changed
+
+def absent(schema_facts, cursor, schema, usage_roles, create_roles):
+    schema_key = schema.lower()
+    if schema_key in schema_facts:
+        update_roles(schema_facts, cursor, schema,
+            schema_facts[schema_key]['usage_roles'], [], schema_facts[schema_key]['create_roles'], [])
+        try:
+            cursor.execute("drop schema {0} restrict".format(schema_facts[schema_key]['name']))
+        except pyodbc.Error:
+            raise CannotDropError("Dropping schema failed due to dependencies.")
+        del schema_facts[schema_key]
+        return True
+    else:
+        return False
+
+# module logic
+
+def main():
+
+    module = AnsibleModule(
+        argument_spec=dict(
+            schema=dict(required=True, aliases=['name']),
+            usage_roles=dict(default=None, aliases=['usage_role']),
+            create_roles=dict(default=None, aliases=['create_role']),
+            owner=dict(default=None),
+            state=dict(default='present', choices=['absent', 'present']),
+            db=dict(default=None),
+            cluster=dict(default='localhost'),
+            port=dict(default='5433'),
+            login_user=dict(default='dbadmin'),
+            login_password=dict(default=None),
+        ), supports_check_mode = True)
+
+    if not pyodbc_found:
+        module.fail_json(msg="The python pyodbc module is required.")
+
+    schema = module.params['schema']
+    usage_roles = []
+    if module.params['usage_roles']:
+        usage_roles = module.params['usage_roles'].split(',')
+        usage_roles = filter(None, usage_roles)
+    create_roles = []
+    if module.params['create_roles']:
+        create_roles = module.params['create_roles'].split(',')
+        create_roles = filter(None, create_roles)
+    owner = module.params['owner']
+    state = module.params['state']
+    db = ''
+    if module.params['db']:
+        db = module.params['db']
+
+    changed = False
+
+    try:
+        dsn = (
+            "Driver=Vertica;"
+            "Server={0};"
+            "Port={1};"
+            "Database={2};"
+            "User={3};"
+            "Password={4};"
+            "ConnectionLoadBalance={5}"
+            ).format(module.params['cluster'], module.params['port'], db,
+                module.params['login_user'], module.params['login_password'], 'true')
+        db_conn = pyodbc.connect(dsn, autocommit=True)
+        cursor = db_conn.cursor()
+    except Exception, e:
+        module.fail_json(msg="Unable to connect to database: {0}.".format(e))
+
+    try:
+        schema_facts = get_schema_facts(cursor)
+        if module.check_mode:
+            changed = not check(schema_facts, schema, usage_roles, create_roles, owner)
+        elif state == 'absent':
+            try:
+                changed = absent(schema_facts, cursor, schema, usage_roles, create_roles)
+            except pyodbc.Error, e:
+                module.fail_json(msg=str(e))
+        elif state == 'present':
+            try:
+                changed = present(schema_facts, cursor, schema, usage_roles, create_roles, owner)
+            except pyodbc.Error, e:
+                module.fail_json(msg=str(e))
+    except NotSupportedError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_schemas': schema_facts})
+    except CannotDropError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_schemas': schema_facts})
+    except SystemExit:
+        # avoid catching this on python 2.4
+        raise
+    except Exception, e:
+        module.fail_json(msg=e)
+
+    module.exit_json(changed=changed, schema=schema, ansible_facts={'vertica_schemas': schema_facts})
+
+# import ansible utilities
+from ansible.module_utils.basic import *
+if __name__ == '__main__':
+    main()
diff --git a/database/vertica/vertica_user.py b/database/vertica/vertica_user.py
new file mode 100644
index 00000000000..82182301a69
--- /dev/null
+++ b/database/vertica/vertica_user.py
@@ -0,0 +1,388 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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/>.
+
+DOCUMENTATION = """
+---
+module: vertica_user
+version_added: '1.0'
+short_description: Adds or removes Vertica database users and assigns roles.
+description:
+  Adds or removes Vertica database user and, optionally, assigns roles.
+  A user will not be removed until all the dependencies have been dropped.
+  In such a situation, if the module tries to remove the user it
+  will fail and only remove roles granted to the user.
+options:
+  name:
+    description:
+      Name of the user to add or remove.
+    required: true
+    default: null
+  profile:
+    description:
+      Sets the user's profile.
+    required: false
+    default: null
+  resource_pool:
+    description:
+      Sets the user's resource pool.
+    required: false
+    default: null
+  password:
+    description:
+      The user's password encrypted by the MD5 algorithm.
+      The password must be generated with the format C("md5" + md5[password + username]),
+      resulting in a total of 35 characters. An easy way to do this is by querying
+      the Vertica database with select 'md5'||md5('<user_password><user_name>').
+    required: false
+    default: null
+  expired:
+    description:
+      Sets the user's password expiration.
+    required: false
+    default: null
+  ldap:
+    description:
+      Set to true if users are authenticated via LDAP.
+      The user will be created with password expired and set to I($ldap$).
+    required: false
+    default: null
+  roles:
+    description:
+      Comma separated list of roles to assign to the user.
+      [Alias I(role)]
+    required: false
+    default: null
+  state:
+    description:
+      Whether to create C(present), drop C(absent) or lock C(locked) a user.
+    required: false
+    choices: ['present', 'absent', 'locked']
+    default: present
+  db:
+    description:
+      Name of the Vertica database.
+    required: false
+    default: null
+  cluster:
+    description:
+      Name of the Vertica cluster.
+    required: false
+    default: localhost
+  port:
+    description:
+      Vertica cluster port to connect to.
+    required: false
+    default: 5433
+  login_user:
+    description:
+      The username used to authenticate with.
+    required: false
+    default: dbadmin
+  login_password:
+    description:
+      The password used to authenticate with.
+    required: false
+    default: null
+notes:
+  The default authentication assumes that you are either logging in as or sudo'ing
+  to the C(dbadmin) account on the host.
+  This module uses C(pyodbc), a Python ODBC database adapter. You must ensure
+  that C(unixODBC) and C(pyodbc) is installed on the host and properly configured.
+  Configuring C(unixODBC) for Vertica requires C(Driver = /opt/vertica/lib64/libverticaodbc.so)
+  to be added to the C(Vertica) section of either C(/etc/odbcinst.ini) or C($HOME/.odbcinst.ini)
+  and both C(ErrorMessagesPath = /opt/vertica/lib64) and C(DriverManagerEncoding = UTF-16)
+  to be added to the C(Driver) section of either C(/etc/vertica.ini) or C($HOME/.vertica.ini).
+requirements: [ 'unixODBC', 'pyodbc' ]
+author: Dariusz Owczarek
+"""
+
+EXAMPLES = """
+Examples:
+
+- name: creating a new vertica user with password
+  vertica_user: name=user_name password=md5<encrypted_password> db=db_name state=present
+
+- name: creating a new vertica user authenticated via ldap with roles assigned
+  vertica_user:
+    name=user_name
+    ldap=true
+    db=db_name
+    roles=schema_name_ro
+    state=present
+"""
+
+try:
+    import pyodbc
+except ImportError:
+    pyodbc_found = False
+else:
+    pyodbc_found = True
+
+class NotSupportedError(Exception):
+    pass
+
+class CannotDropError(Exception):
+    pass
+
+# module specific functions
+
+def get_user_facts(cursor, user=''):
+    facts = {}
+    cursor.execute("""
+        select u.user_name, u.is_locked, u.lock_time,
+        p.password, p.acctexpired as is_expired,
+        u.profile_name, u.resource_pool,
+        u.all_roles, u.default_roles
+        from users u join password_auditor p on p.user_id = u.user_id
+        where not u.is_super_user
+        and (? = '' or u.user_name ilike ?)
+    """, user, user)
+    while True:
+        rows = cursor.fetchmany(100)
+        if not rows:
+            break
+        for row in rows:
+            user_key = row.user_name.lower()
+            facts[user_key] = {
+                'name': row.user_name,
+                'locked': str(row.is_locked),
+                'password': row.password,
+                'expired': str(row.is_expired),
+                'profile': row.profile_name,
+                'resource_pool': row.resource_pool,
+                'roles': [],
+                'default_roles': []}
+            if row.is_locked:
+                facts[user_key]['locked_time'] = str(row.lock_time)
+            if row.all_roles:
+                facts[user_key]['roles'] = row.all_roles.replace(' ', '').split(',')
+            if row.default_roles:
+                facts[user_key]['default_roles'] = row.default_roles.replace(' ', '').split(',')
+    return facts
+
+def update_roles(user_facts, cursor, user,
+                 existing_all, existing_default, required):
+    del_roles = list(set(existing_all) - set(required))
+    if del_roles:
+        cursor.execute("revoke {0} from {1}".format(','.join(del_roles), user))
+    new_roles = list(set(required) - set(existing_all))
+    if new_roles:
+        cursor.execute("grant {0} to {1}".format(','.join(new_roles), user))
+    if required:
+        cursor.execute("alter user {0} default role {1}".format(user, ','.join(required)))
+
+def check(user_facts, user, profile, resource_pool,
+    locked, password, expired, ldap, roles):
+    user_key = user.lower()
+    if user_key not in user_facts:
+       return False
+    if profile and profile != user_facts[user_key]['profile']:
+        return False
+    if resource_pool and resource_pool != user_facts[user_key]['resource_pool']:
+        return False
+    if locked != (user_facts[user_key]['locked'] == 'True'):
+        return False
+    if password and password != user_facts[user_key]['password']:
+        return False
+    if expired is not None and expired != (user_facts[user_key]['expired'] == 'True') or \
+       ldap is not None and ldap != (user_facts[user_key]['expired'] == 'True'):
+        return False
+    if roles and (cmp(sorted(roles), sorted(user_facts[user_key]['roles'])) != 0 or \
+        cmp(sorted(roles), sorted(user_facts[user_key]['default_roles'])) != 0):
+        return False
+    return True
+
+def present(user_facts, cursor, user, profile, resource_pool,
+    locked, password, expired, ldap, roles):
+    user_key = user.lower()
+    if user_key not in user_facts:
+        query_fragments = ["create user {0}".format(user)]
+        if locked:
+            query_fragments.append("account lock")
+        if password or ldap:
+            if password:
+                query_fragments.append("identified by '{0}'".format(password))
+            else:
+                query_fragments.append("identified by '$ldap$'")
+        if expired or ldap:
+            query_fragments.append("password expire")
+        if profile:
+            query_fragments.append("profile {0}".format(profile))
+        if resource_pool:
+            query_fragments.append("resource pool {0}".format(resource_pool))
+        cursor.execute(' '.join(query_fragments))
+        if resource_pool and resource_pool != 'general':
+            cursor.execute("grant usage on resource pool {0} to {1}".format(
+                resource_pool, user))
+        update_roles(user_facts, cursor, user, [], [], roles)
+        user_facts.update(get_user_facts(cursor, user))
+        return True
+    else:
+        changed = False
+        query_fragments = ["alter user {0}".format(user)]
+        if locked is not None and locked != (user_facts[user_key]['locked'] == 'True'):
+            state = 'lock' if locked else 'unlock'
+            query_fragments.append("account {0}".format(state))
+            changed = True
+        if password and password != user_facts[user_key]['password']:
+            query_fragments.append("identified by '{0}'".format(password))
+            changed = True
+        if ldap:
+            if ldap != (user_facts[user_key]['expired'] == 'True'):
+                query_fragments.append("password expire")
+                changed = True                
+        elif expired is not None and expired != (user_facts[user_key]['expired'] == 'True'):
+            if expired:
+                query_fragments.append("password expire")
+                changed = True
+            else:
+                raise NotSupportedError("Unexpiring user password is not supported.")
+        if profile and profile != user_facts[user_key]['profile']:
+            query_fragments.append("profile {0}".format(profile))
+            changed = True
+        if resource_pool and resource_pool != user_facts[user_key]['resource_pool']:
+            query_fragments.append("resource pool {0}".format(resource_pool))
+            if user_facts[user_key]['resource_pool'] != 'general':
+                cursor.execute("revoke usage on resource pool {0} from {1}".format(
+                    user_facts[user_key]['resource_pool'], user))
+            if resource_pool != 'general':
+                cursor.execute("grant usage on resource pool {0} to {1}".format(
+                    resource_pool, user))
+            changed = True
+        if changed:
+            cursor.execute(' '.join(query_fragments))
+        if roles and (cmp(sorted(roles), sorted(user_facts[user_key]['roles'])) != 0 or \
+            cmp(sorted(roles), sorted(user_facts[user_key]['default_roles'])) != 0):
+            update_roles(user_facts, cursor, user,
+                user_facts[user_key]['roles'], user_facts[user_key]['default_roles'], roles)
+            changed = True
+        if changed:
+            user_facts.update(get_user_facts(cursor, user))
+        return changed
+
+def absent(user_facts, cursor, user, roles):
+    user_key = user.lower()
+    if user_key in user_facts:
+        update_roles(user_facts, cursor, user,
+            user_facts[user_key]['roles'], user_facts[user_key]['default_roles'], [])
+        try:
+            cursor.execute("drop user {0}".format(user_facts[user_key]['name']))
+        except pyodbc.Error:
+            raise CannotDropError("Dropping user failed due to dependencies.")
+        del user_facts[user_key]
+        return True
+    else:
+        return False
+
+# module logic
+
+def main():
+
+    module = AnsibleModule(
+        argument_spec=dict(
+            user=dict(required=True, aliases=['name']),
+            profile=dict(default=None),
+            resource_pool=dict(default=None),
+            password=dict(default=None),
+            expired=dict(type='bool', default=None),
+            ldap=dict(type='bool', default=None),
+            roles=dict(default=None, aliases=['role']),
+            state=dict(default='present', choices=['absent', 'present', 'locked']),
+            db=dict(default=None),
+            cluster=dict(default='localhost'),
+            port=dict(default='5433'),
+            login_user=dict(default='dbadmin'),
+            login_password=dict(default=None),
+        ), supports_check_mode = True)
+
+    if not pyodbc_found:
+        module.fail_json(msg="The python pyodbc module is required.")
+
+    user = module.params['user']
+    profile = module.params['profile']
+    if profile:
+        profile = profile.lower()
+    resource_pool = module.params['resource_pool']
+    if resource_pool:
+        resource_pool = resource_pool.lower()
+    password = module.params['password']
+    expired = module.params['expired']
+    ldap = module.params['ldap']
+    roles = []
+    if module.params['roles']:
+        roles = module.params['roles'].split(',')
+        roles = filter(None, roles)
+    state = module.params['state']
+    if state == 'locked':
+        locked = True
+    else:
+        locked = False
+    db = ''
+    if module.params['db']:
+        db = module.params['db']
+
+    changed = False
+
+    try:
+        dsn = (
+            "Driver=Vertica;"
+            "Server={0};"
+            "Port={1};"
+            "Database={2};"
+            "User={3};"
+            "Password={4};"
+            "ConnectionLoadBalance={5}"
+            ).format(module.params['cluster'], module.params['port'], db,
+                module.params['login_user'], module.params['login_password'], 'true')
+        db_conn = pyodbc.connect(dsn, autocommit=True)
+        cursor = db_conn.cursor()
+    except Exception, e:
+        module.fail_json(msg="Unable to connect to database: {0}.".format(e))
+
+    try:
+        user_facts = get_user_facts(cursor)
+        if module.check_mode:
+            changed = not check(user_facts, user, profile, resource_pool,
+                locked, password, expired, ldap, roles)
+        elif state == 'absent':
+            try:
+                changed = absent(user_facts, cursor, user, roles)
+            except pyodbc.Error, e:
+                module.fail_json(msg=str(e))
+        elif state in ['present', 'locked']:
+            try:
+                changed = present(user_facts, cursor, user, profile, resource_pool,
+                    locked, password, expired, ldap, roles)
+            except pyodbc.Error, e:
+                module.fail_json(msg=str(e))
+    except NotSupportedError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_users': user_facts})
+    except CannotDropError, e:
+        module.fail_json(msg=str(e), ansible_facts={'vertica_users': user_facts})
+    except SystemExit:
+        # avoid catching this on python 2.4
+        raise
+    except Exception, e:
+        module.fail_json(msg=e)
+
+    module.exit_json(changed=changed, user=user, ansible_facts={'vertica_users': user_facts})
+
+# import ansible utilities
+from ansible.module_utils.basic import *
+if __name__ == '__main__':
+    main()