#!/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 . try: import psycopg2 except ImportError: postgresqldb_found = False else: postgresqldb_found = True # =========================================== # PostgreSQL module specific support methods. # def user_exists(cursor, user): query = "SELECT rolname FROM pg_roles WHERE rolname=%(user)s" cursor.execute(query, {'user': user}) return cursor.rowcount > 0 def user_add(cursor, user, password): """Create a new user with write access to the database""" query = "CREATE USER %(user)s with PASSWORD '%(password)s'" cursor.execute(query % {"user": user, "password": password}) return True def user_chpass(cursor, user, password): """Change user password""" changed = False # Handle passwords. if password is not None: select = "SELECT rolpassword FROM pg_authid where rolname=%(user)s" cursor.execute(select, {"user": user}) current_pass_hash = cursor.fetchone()[0] # Not sure how to hash the new password, so we just initiate the # change and check if the hash changed alter = "ALTER USER %(user)s WITH PASSWORD '%(password)s'" cursor.execute(alter % {"user": user, "password": password}) cursor.execute(select, {"user": user}) new_pass_hash = cursor.fetchone()[0] if current_pass_hash != new_pass_hash: changed = True return changed def user_delete(cursor, user): """Try to remove a user. Returns True if successful otherwise False""" cursor.execute("SAVEPOINT ansible_pgsql_user_delete") try: cursor.execute("DROP USER %s" % user) except: cursor.execute("ROLLBACK TO SAVEPOINT ansible_pgsql_user_delete") cursor.execute("RELEASE SAVEPOINT ansible_pgsql_user_delete") return False cursor.execute("RELEASE SAVEPOINT ansible_pgsql_user_delete") return True def has_table_privilege(cursor, user, table, priv): query = 'SELECT has_table_privilege(%s, %s, %s)' cursor.execute(query, user, table, priv) return cursor.fetchone()[0] def grant_table_privilege(cursor, user, table, priv): if has_table_privilege(cursor, user, table, priv): return False query = 'GRANT %s ON TABLE %s TO %s' % (priv, table, user) cursor.execute(query) return True def revoke_table_privilege(cursor, user, table, priv): if not has_table_privilege(cursor, user, table, priv): return False query = 'REVOKE %s ON TABLE %s FROM %s' % (priv, table, user) cursor.execute(query) return True def has_database_privilege(cursor, user, db, priv): query = 'SELECT has_database_privilege(%s, %s, %s)' cursor.execute(query, user, db, priv) return cursor.fetchone()[0] def grant_database_privilege(cursor, user, db, priv): if has_database_privilege(cursor, user, db, priv): return False query = 'GRANT %s ON DATABASE %s TO %s' % (priv, db, user) cursor.execute(query) return True def revoke_database_privilege(cursor, user, db, priv): if not has_database_privilege(cursor, user, db, priv): return False query = 'REVOKE %s ON DATABASE %s FROM %s' % (priv, db, user) cursor.execute(query) return True def revoke_privileges(cursor, user, privs): if privs is None: return False changed = False for type_ in privs: revoke_func = { 'table':revoke_table_privilege, 'database':revoke_database_privilege }[type_] for name, privileges in privs[type_].iteritem(): for privilege in privileges: changed = revoke_func(cursor, user, name, privilege)\ or changed return changed def grant_privileges(cursor, user, privs): if privs is None: return False changed = False for type_ in privs: grant_func = { 'table':grant_table_privilege, 'database':grant_database_privilege }[type_] for name, privileges in privs[type_].iteritem(): for privilege in privileges: changed = grant_func(cursor, user, name, privilege)\ or changed return changed def parse_privs(privs, db): """ Parse privilege string to determine permissions for database db. Format: privileges[/privileges/...] Where: privileges := DATABASE_PRIVILEGES[,DATABASE_PRIVILEGES,...] | TABLE_NAME:TABLE_PRIVILEGES[,TABLE_PRIVILEGES,...] """ if privs is None: return privs privs = { 'database':{}, 'table':{} } for token in privs.split('/'): if ':' not in token: type_ = 'database' name = db privileges = token else: type_ = 'table' name, privileges = token.split(':', 1) privileges = privileges.split(',') privs[type_][name] = privileges return privs # =========================================== # Module execution. # def main(): module = AnsibleModule( argument_spec=dict( login_user=dict(default="postgres"), login_password=dict(default=""), login_host=dict(default=""), user=dict(required=True, aliases=['name']), password=dict(default=None), state=dict(default="present", choices=["absent", "present"]), priv=dict(default=None), db=dict(default=''), fail_on_user=dict(default=True) ) ) user = module.params["user"] password = module.params["password"] state = module.params["state"] fail_on_user = module.params["fail_on_user"] db = module.params["db"] if db == '' and module.params["priv"] is not None: module.fail_json(msg="privileges require a database to be specified") privs = parse_privs(module.params["priv"], db) if not postgresqldb_found: module.fail_json(msg="the python psycopg2 module is required") # To use defaults values, keyword arguments must be absent, so # check which values are empty and don't include in the **kw # dictionary params_map = { "login_host":"host", "login_user":"user", "login_password":"password", "db":"database" } kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems() if k in params_map and v != "" ) try: db_connection = psycopg2.connect(database=db, **kw) cursor = db_connection.cursor() except Exception, e: module.fail_json(msg="unable to connect to database: %s" % e) changed = False if state == "present": if user_exists(cursor, user): changed = user_chpass(cursor, user, password) else: if password is None: msg = "password parameter required when adding a user" module.fail_json(msg=msg) changed = user_add(cursor, user, password) changed = grant_privileges(cursor, user, privs) or changed else: if user_exists(cursor, user): changed = revoke_privileges(cursor, user, privs) user_removed = user_delete(cursor, user) changed = changed or user_removed if fail_on_user and not user_removed: msg = "unabel to remove user" module.fail_json(msg=msg) if changed: db_connection.commit() module.exit_json(changed=changed, user=user, user_removed=user_removed) # this is magic, see lib/ansible/module_common.py #<> main()