2012-07-26 17:02:28 +02:00
#!/usr/bin/python
2012-08-03 03:29:10 +02:00
# -*- coding: utf-8 -*-
2012-07-26 17:02:28 +02:00
# 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/>.
2012-09-29 16:46:30 +02:00
DOCUMENTATION = '''
- - -
module : postgresql_user
short_description : Adds or removes a users ( roles ) from a PostgreSQL database .
description :
2012-09-30 12:21:35 +02:00
- Add or remove PostgreSQL users ( roles ) from a remote host and , optionally ,
grant the users access to an existing database or tables .
- The fundamental function of the module is to create , or delete , roles from
a PostgreSQL cluster . Privilege assignment , or removal , is an optional
step , which works on one database at a time . This allows for the module to
be called several times in the same module to modify the permissions on
different databases , or to grant permissions to already existing users .
2012-11-21 18:49:30 +01:00
- A user cannot be removed until all the privileges have been stripped from
2012-09-30 12:21:35 +02:00
the user . In such situation , if the module tries to remove the user it
will fail . To avoid this from happening the fail_on_user option signals
the module to try to remove the user , but if not possible keep going ; the
module will report if changes happened and separately if the user was
removed or not .
2013-11-28 03:23:03 +01:00
version_added : " 0.6 "
2012-09-29 16:46:30 +02:00
options :
name :
description :
- name of the user ( role ) to add or remove
required : true
default : null
password :
description :
2013-11-14 17:57:01 +01:00
- set the user ' s password, before 1.4 this was required.
2015-04-07 08:59:38 +02:00
- " When passing an encrypted password, the encrypted parameter must also be true, and it must be generated with the format C( ' str[ \\ " md5 \\" ] + md5[ password + username ] ' ), resulting in a total of 35 characters. An easy way to do this is: C(echo \\ " md5 ` echo - n \\" verysecretpasswordJOE \\ " | md5 ` \\" ). Note that if encrypted is set, the stored password will be hashed whether or not it is pre-encrypted. "
2013-10-26 17:32:16 +02:00
required : false
2012-09-29 16:46:30 +02:00
default : null
db :
description :
- name of database where permissions will be granted
required : false
default : null
fail_on_user :
description :
2012-11-21 18:49:30 +01:00
- if C ( yes ) , fail when user can ' t be removed. Otherwise just log and continue
2012-09-29 16:46:30 +02:00
required : false
2013-06-01 18:11:26 +02:00
default : ' yes '
2012-09-29 16:46:30 +02:00
choices : [ " yes " , " no " ]
2014-02-07 17:25:26 +01:00
port :
description :
- Database port to connect to .
required : false
default : 5432
2012-09-29 16:46:30 +02:00
login_user :
description :
- User ( role ) used to authenticate with PostgreSQL
required : false
default : postgres
login_password :
description :
- Password used to authenticate with PostgreSQL
required : false
default : null
login_host :
description :
- Host running PostgreSQL .
required : false
default : localhost
2014-09-30 01:06:28 +02:00
login_unix_socket :
2014-12-03 17:16:59 +01:00
description :
2014-09-30 01:06:28 +02:00
- Path to a Unix domain socket for local connections
required : false
default : null
2012-09-29 16:46:30 +02:00
priv :
description :
2012-09-30 12:21:35 +02:00
- " PostgreSQL privileges string in the format: C(table:priv1,priv2) "
2012-09-29 16:46:30 +02:00
required : false
default : null
2012-10-15 23:42:06 +02:00
role_attr_flags :
description :
- " PostgreSQL role attributes string in the format: CREATEDB,CREATEROLE,SUPERUSER "
required : false
default : null
choices : [ " [NO]SUPERUSER " , " [NO]CREATEROLE " , " [NO]CREATEUSER " , " [NO]CREATEDB " ,
" [NO]INHERIT " , " [NO]LOGIN " , " [NO]REPLICATION " ]
2012-09-29 16:46:30 +02:00
state :
description :
2013-04-01 12:44:22 +02:00
- The user ( role ) state
2012-09-29 16:46:30 +02:00
required : false
default : present
choices : [ " present " , " absent " ]
2013-10-26 17:32:16 +02:00
encrypted :
description :
2015-04-07 08:59:38 +02:00
- whether the password is stored hashed in the database . boolean . Passwords can be passed already hashed or unhashed , and postgresql ensures the stored password is hashed when encrypted is set .
2013-10-26 17:32:16 +02:00
required : false
default : false
2013-10-26 17:37:45 +02:00
version_added : ' 1.4 '
2013-10-26 17:32:16 +02:00
expires :
description :
- sets the user ' s password expiration.
required : false
default : null
2013-10-26 17:37:45 +02:00
version_added : ' 1.4 '
2015-03-18 00:32:01 +01:00
no_password_changes :
description :
- if C ( yes ) , don ' t inspect database for password changes. Effective when C(pg_authid) is not accessible (such as AWS RDS). Otherwise, make password changes as necessary.
required : false
2015-03-31 00:51:54 +02:00
default : ' no '
2015-03-18 00:32:01 +01:00
choices : [ " yes " , " no " ]
2015-03-31 00:51:54 +02:00
version_added : ' 2.0 '
2012-09-29 16:46:30 +02:00
notes :
2012-09-30 12:21:35 +02:00
- The default authentication assumes that you are either logging in as or
sudo ' ing to the postgres account on the host.
- This module uses psycopg2 , a Python PostgreSQL database adapter . You must
ensure that psycopg2 is installed on the host before using this module . If
the remote host is the PostgreSQL server ( which is the default case ) , then
PostgreSQL must also be installed on the remote host . For Ubuntu - based
systems , install the postgresql , libpq - dev , and python - psycopg2 packages
on the remote host before using this module .
2015-04-07 08:59:38 +02:00
- If the passlib library is installed , then passwords that are encrypted
in the DB but not encrypted when passed as arguments can be checked for
changes . If the passlib library is not installed , unencrypted passwords
stored in the DB encrypted will be assumed to have changed .
2013-02-19 04:59:51 +01:00
- If you specify PUBLIC as the user , then the privilege changes will apply
to all users . You may not specify password or role_attr_flags when the
PUBLIC user is specified .
2012-09-29 16:46:30 +02:00
requirements : [ psycopg2 ]
2015-06-15 21:53:30 +02:00
author : " Lorin Hochstein (@lorin) "
2012-09-29 16:46:30 +02:00
'''
2013-06-14 11:53:43 +02:00
EXAMPLES = '''
# Create django user and grant access to database and products table
2013-11-03 06:42:44 +01:00
- postgresql_user : db = acme name = django password = ceec4eif7ya priv = CONNECT / products : ALL
2013-06-14 11:53:43 +02:00
# Create rails user, grant privilege to create other databases and demote rails from super user status
2013-11-03 06:42:44 +01:00
- postgresql_user : name = rails password = secret role_attr_flags = CREATEDB , NOSUPERUSER
2013-06-14 11:53:43 +02:00
# Remove test user privileges from acme
2013-11-03 06:42:44 +01:00
- postgresql_user : db = acme name = test priv = ALL / products : ALL state = absent fail_on_user = no
2013-06-14 11:53:43 +02:00
# Remove test user from test database and the cluster
2013-11-03 06:42:44 +01:00
- postgresql_user : db = test name = test priv = ALL state = absent
2013-06-14 11:53:43 +02:00
# Example privileges string format
INSERT , UPDATE / table : SELECT / anothertable : ALL
2013-10-26 17:37:45 +02:00
# Remove an existing user's password
- postgresql_user : db = test user = test password = NULL
2013-06-14 11:53:43 +02:00
'''
2012-08-21 21:25:19 +02:00
import re
2014-11-25 07:30:10 +01:00
import itertools
2012-08-21 21:25:19 +02:00
2012-07-26 17:02:28 +02:00
try :
import psycopg2
2015-04-07 08:59:38 +02:00
import psycopg2 . extras
2012-07-26 17:02:28 +02:00
except ImportError :
postgresqldb_found = False
else :
postgresqldb_found = True
2014-11-25 07:30:10 +01:00
_flags = ( ' SUPERUSER ' , ' CREATEROLE ' , ' CREATEUSER ' , ' CREATEDB ' , ' INHERIT ' , ' LOGIN ' , ' REPLICATION ' )
2014-11-25 09:44:18 +01:00
VALID_FLAGS = frozenset ( itertools . chain ( _flags , ( ' NO %s ' % f for f in _flags ) ) )
2014-11-25 07:30:10 +01:00
2015-04-08 05:00:50 +02:00
VALID_PRIVS = dict ( table = frozenset ( ( ' SELECT ' , ' INSERT ' , ' UPDATE ' , ' DELETE ' , ' TRUNCATE ' , ' REFERENCES ' , ' TRIGGER ' , ' ALL ' ) ) ,
database = frozenset ( ( ' CREATE ' , ' CONNECT ' , ' TEMPORARY ' , ' TEMP ' , ' ALL ' ) ) ,
2014-11-25 07:30:10 +01:00
)
2015-04-07 08:59:38 +02:00
# map to cope with idiosyncracies of SUPERUSER and LOGIN
PRIV_TO_AUTHID_COLUMN = dict ( SUPERUSER = ' rolsuper ' , CREATEROLE = ' rolcreaterole ' ,
CREATEUSER = ' rolcreateuser ' , CREATEDB = ' rolcreatedb ' ,
INHERIT = ' rolinherit ' , LOGIN = ' rolcanlogin ' ,
REPLICATION = ' rolreplication ' )
2014-11-25 07:30:10 +01:00
class InvalidFlagsError ( Exception ) :
pass
class InvalidPrivsError ( Exception ) :
pass
2012-07-26 17:02:28 +02:00
# ===========================================
# PostgreSQL module specific support methods.
#
def user_exists ( cursor , user ) :
2013-02-19 03:33:36 +01:00
# The PUBLIC user is a special case that is always there
if user == ' PUBLIC ' :
return True
2012-07-26 17:02:28 +02:00
query = " SELECT rolname FROM pg_roles WHERE rolname= %(user)s "
cursor . execute ( query , { ' user ' : user } )
return cursor . rowcount > 0
2014-11-25 07:30:10 +01:00
def user_add ( cursor , user , password , role_attr_flags , encrypted , expires ) :
2013-04-01 12:44:22 +02:00
""" Create a new database user (role). """
2014-11-25 07:30:10 +01:00
# Note: role_attr_flags escaped by parse_role_attrs and encrypted is a literal
query_password_data = dict ( password = password , expires = expires )
query = [ ' CREATE USER %(user)s ' % { " user " : pg_quote_identifier ( user , ' role ' ) } ]
2013-10-26 17:32:16 +02:00
if password is not None :
2014-11-25 07:30:10 +01:00
query . append ( " WITH %(crypt)s " % { " crypt " : encrypted } )
query . append ( " PASSWORD %(password)s " )
2013-10-26 17:32:16 +02:00
if expires is not None :
2014-11-25 07:30:10 +01:00
query . append ( " VALID UNTIL %(expires)s " )
2014-11-25 19:46:41 +01:00
query . append ( role_attr_flags )
2014-11-25 07:30:10 +01:00
query = ' ' . join ( query )
2014-02-03 23:07:17 +01:00
cursor . execute ( query , query_password_data )
2012-07-26 17:02:28 +02:00
return True
2015-03-18 00:32:01 +01:00
def user_alter ( cursor , module , user , password , role_attr_flags , encrypted , expires , no_password_changes ) :
2013-04-01 12:44:22 +02:00
""" Change user password and/or attributes. Return True if changed, False otherwise. """
2012-07-26 17:02:28 +02:00
changed = False
2014-11-25 07:30:10 +01:00
# Note: role_attr_flags escaped by parse_role_attrs and encrypted is a literal
2013-02-19 03:33:36 +01:00
if user == ' PUBLIC ' :
if password is not None :
module . fail_json ( msg = " cannot change the password for PUBLIC user " )
elif role_attr_flags != ' ' :
module . fail_json ( msg = " cannot change the role_attr_flags for PUBLIC user " )
else :
return False
2012-07-26 17:02:28 +02:00
# Handle passwords.
2015-03-18 00:32:01 +01:00
if not no_password_changes and ( password is not None or role_attr_flags is not None ) :
2012-10-15 23:42:06 +02:00
# Select password and all flag-like columns in order to verify changes.
2014-11-25 07:30:10 +01:00
query_password_data = dict ( password = password , expires = expires )
2012-12-05 09:51:15 +01:00
select = " SELECT * FROM pg_authid where rolname= %(user)s "
cursor . execute ( select , { " user " : user } )
2012-10-15 23:42:06 +02:00
# Grab current role attributes.
current_role_attrs = cursor . fetchone ( )
2015-04-07 08:59:38 +02:00
# Do we actually need to do anything?
pwchanging = False
2012-10-15 23:42:06 +02:00
if password is not None :
2015-04-07 08:59:38 +02:00
if encrypted :
if password . startswith ( ' md5 ' ) :
if password != current_role_attrs [ ' rolpassword ' ] :
pwchanging = True
else :
try :
from passlib . hash import postgres_md5 as pm
if pm . encrypt ( password , user ) != current_role_attrs [ ' rolpassword ' ] :
pwchanging = True
except ImportError :
# Cannot check if passlib is not installed, so assume password is different
pwchanging = True
else :
if password != current_role_attrs [ ' rolpassword ' ] :
pwchanging = True
role_attr_flags_changing = False
if role_attr_flags :
role_attr_flags_dict = { }
2015-05-18 21:45:47 +02:00
for r in role_attr_flags . split ( ' ' ) :
2015-04-07 08:59:38 +02:00
if r . startswith ( ' NO ' ) :
role_attr_flags_dict [ r . replace ( ' NO ' , ' ' , 1 ) ] = False
else :
role_attr_flags_dict [ r ] = True
for role_attr_name , role_attr_value in role_attr_flags_dict . items ( ) :
if current_role_attrs [ PRIV_TO_AUTHID_COLUMN [ role_attr_name ] ] != role_attr_value :
role_attr_flags_changing = True
expires_changing = ( expires is not None and expires == current_roles_attrs [ ' rol_valid_until ' ] )
if not pwchanging and not role_attr_flags_changing and not expires_changing :
return False
alter = [ ' ALTER USER %(user)s ' % { " user " : pg_quote_identifier ( user , ' role ' ) } ]
if pwchanging :
2014-11-25 07:30:10 +01:00
alter . append ( " WITH %(crypt)s " % { " crypt " : encrypted } )
alter . append ( " PASSWORD %(password)s " )
alter . append ( role_attr_flags )
2013-10-26 17:32:16 +02:00
elif role_attr_flags :
2014-11-25 07:30:10 +01:00
alter . append ( ' WITH %s ' % role_attr_flags )
2013-10-26 17:32:16 +02:00
if expires is not None :
2014-11-25 07:30:10 +01:00
alter . append ( " VALID UNTIL %(expires)s " )
2013-10-26 17:32:16 +02:00
2013-12-31 19:23:45 +01:00
try :
2014-11-25 20:04:47 +01:00
cursor . execute ( ' ' . join ( alter ) , query_password_data )
2013-12-31 19:23:45 +01:00
except psycopg2 . InternalError , e :
if e . pgcode == ' 25006 ' :
# Handle errors due to read-only transactions indicated by pgcode 25006
# ERROR: cannot execute ALTER ROLE in a read-only transaction
changed = False
module . fail_json ( msg = e . pgerror )
return changed
else :
raise psycopg2 . InternalError , e
2013-10-26 17:32:16 +02:00
2012-10-15 23:42:06 +02:00
# Grab new role attributes.
2012-12-05 09:51:15 +01:00
cursor . execute ( select , { " user " : user } )
2012-10-15 23:42:06 +02:00
new_role_attrs = cursor . fetchone ( )
# Detect any differences between current_ and new_role_attrs.
for i in range ( len ( current_role_attrs ) ) :
if current_role_attrs [ i ] != new_role_attrs [ i ] :
changed = True
2012-07-26 17:02:28 +02:00
return changed
2012-08-21 18:20:16 +02:00
def user_delete ( cursor , user ) :
""" Try to remove a user. Returns True if successful otherwise False """
cursor . execute ( " SAVEPOINT ansible_pgsql_user_delete " )
try :
2014-11-25 07:30:10 +01:00
cursor . execute ( " DROP USER %s " % pg_quote_identifier ( user , ' role ' ) )
2012-08-21 18:20:16 +02:00
except :
cursor . execute ( " ROLLBACK TO SAVEPOINT ansible_pgsql_user_delete " )
cursor . execute ( " RELEASE SAVEPOINT ansible_pgsql_user_delete " )
return False
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
2012-08-21 18:20:16 +02:00
cursor . execute ( " RELEASE SAVEPOINT ansible_pgsql_user_delete " )
return True
2015-05-19 21:41:48 +02:00
def has_table_privileges ( cursor , user , table , privs ) :
"""
Return the difference between the privileges that a user already has and
the privileges that they desire to have .
: returns : tuple of :
* privileges that they have and were requested
* privileges they currently hold but were not requested
* privileges requested that they do not hold
"""
cur_privs = get_table_privileges ( cursor , user , table )
have_currently = cur_privs . intersection ( privs )
other_current = cur_privs . difference ( privs )
desired = privs . difference ( cur_privs )
return ( have_currently , other_current , desired )
2012-08-21 18:20:16 +02:00
2012-08-21 21:25:19 +02:00
def get_table_privileges ( cursor , user , table ) :
if ' . ' in table :
schema , table = table . split ( ' . ' , 1 )
else :
schema = ' public '
query = ''' SELECT privilege_type FROM information_schema.role_table_grants
WHERE grantee = % s AND table_name = % s AND table_schema = % s '''
cursor . execute ( query , ( user , table , schema ) )
2015-05-19 21:41:48 +02:00
return frozenset ( [ x [ 0 ] for x in cursor . fetchall ( ) ] )
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
2015-05-19 21:41:48 +02:00
def grant_table_privileges ( cursor , user , table , privs ) :
2014-11-25 07:30:10 +01:00
# Note: priv escaped by parse_privs
2015-05-19 21:41:48 +02:00
privs = ' , ' . join ( privs )
2014-01-04 22:34:05 +01:00
query = ' GRANT %s ON TABLE %s TO %s ' % (
2015-05-19 21:41:48 +02:00
privs , pg_quote_identifier ( table , ' table ' ) , pg_quote_identifier ( user , ' role ' ) )
2012-08-21 18:20:16 +02:00
cursor . execute ( query )
2015-05-19 21:41:48 +02:00
def revoke_table_privileges ( cursor , user , table , privs ) :
2014-11-25 07:30:10 +01:00
# Note: priv escaped by parse_privs
2015-05-19 21:41:48 +02:00
privs = ' , ' . join ( privs )
2014-01-04 22:34:05 +01:00
query = ' REVOKE %s ON TABLE %s FROM %s ' % (
2015-05-19 21:41:48 +02:00
privs , pg_quote_identifier ( table , ' table ' ) , pg_quote_identifier ( user , ' role ' ) )
2012-08-21 18:20:16 +02:00
cursor . execute ( query )
2012-08-21 21:25:19 +02:00
def get_database_privileges ( cursor , user , db ) :
priv_map = {
' C ' : ' CREATE ' ,
' T ' : ' TEMPORARY ' ,
' c ' : ' CONNECT ' ,
}
query = ' SELECT datacl FROM pg_database WHERE datname = %s '
cursor . execute ( query , ( db , ) )
datacl = cursor . fetchone ( ) [ 0 ]
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
if datacl is None :
2015-05-19 21:41:48 +02:00
return set ( )
2012-08-21 21:25:19 +02:00
r = re . search ( ' %s =(C?T?c?)/[a-z]+ \ ,? ' % user , datacl )
if r is None :
2015-05-19 21:41:48 +02:00
return set ( )
o = set ( )
2012-08-21 21:25:19 +02:00
for v in r . group ( 1 ) :
2015-05-19 21:41:48 +02:00
o . add ( priv_map [ v ] )
return normalize_privileges ( o , ' database ' )
2012-08-21 21:25:19 +02:00
2015-05-19 21:41:48 +02:00
def has_database_privileges ( cursor , user , db , privs ) :
"""
Return the difference between the privileges that a user already has and
the privileges that they desire to have .
: returns : tuple of :
* privileges that they have and were requested
* privileges they currently hold but were not requested
* privileges requested that they do not hold
"""
cur_privs = get_database_privileges ( cursor , user , db )
have_currently = cur_privs . intersection ( privs )
other_current = cur_privs . difference ( privs )
desired = privs . difference ( cur_privs )
return ( have_currently , other_current , desired )
2012-08-21 18:20:16 +02:00
2015-05-19 21:41:48 +02:00
def grant_database_privileges ( cursor , user , db , privs ) :
2014-11-25 07:30:10 +01:00
# Note: priv escaped by parse_privs
2015-05-19 21:41:48 +02:00
privs = ' , ' . join ( privs )
2013-02-19 03:33:36 +01:00
if user == " PUBLIC " :
2014-11-25 07:30:10 +01:00
query = ' GRANT %s ON DATABASE %s TO PUBLIC ' % (
2015-05-19 21:41:48 +02:00
privs , pg_quote_identifier ( db , ' database ' ) )
2013-02-19 03:33:36 +01:00
else :
2014-11-25 07:30:10 +01:00
query = ' GRANT %s ON DATABASE %s TO %s ' % (
2015-05-19 21:41:48 +02:00
privs , pg_quote_identifier ( db , ' database ' ) ,
2014-11-25 07:30:10 +01:00
pg_quote_identifier ( user , ' role ' ) )
2012-08-21 18:20:16 +02:00
cursor . execute ( query )
2012-07-26 17:02:28 +02:00
2015-05-19 21:41:48 +02:00
def revoke_database_privileges ( cursor , user , db , privs ) :
2014-11-25 07:30:10 +01:00
# Note: priv escaped by parse_privs
2015-05-19 21:41:48 +02:00
privs = ' , ' . join ( privs )
2013-02-19 03:33:36 +01:00
if user == " PUBLIC " :
2014-11-25 07:30:10 +01:00
query = ' REVOKE %s ON DATABASE %s FROM PUBLIC ' % (
2015-05-19 21:41:48 +02:00
privs , pg_quote_identifier ( db , ' database ' ) )
2013-02-19 03:33:36 +01:00
else :
2014-11-25 07:30:10 +01:00
query = ' REVOKE %s ON DATABASE %s FROM %s ' % (
2015-05-19 21:41:48 +02:00
privs , pg_quote_identifier ( db , ' database ' ) ,
2014-11-25 07:30:10 +01:00
pg_quote_identifier ( user , ' role ' ) )
2012-08-21 18:20:16 +02:00
cursor . execute ( query )
2012-07-26 17:02:28 +02:00
2012-08-21 18:20:16 +02:00
def revoke_privileges ( cursor , user , privs ) :
if privs is None :
return False
2012-07-26 17:02:28 +02:00
2015-05-19 21:41:48 +02:00
revoke_funcs = dict ( table = revoke_table_privileges , database = revoke_database_privileges )
check_funcs = dict ( table = has_table_privileges , database = has_database_privileges )
2012-08-21 18:20:16 +02:00
changed = False
for type_ in privs :
2012-08-21 21:25:19 +02:00
for name , privileges in privs [ type_ ] . iteritems ( ) :
2015-05-19 21:41:48 +02:00
# Check that any of the privileges requested to be removed are
# currently granted to the user
differences = check_funcs [ type_ ] ( cursor , user , name , privileges )
if differences [ 0 ] :
revoke_funcs [ type_ ] ( cursor , user , name , privileges )
changed = True
2012-08-21 18:20:16 +02:00
return changed
def grant_privileges ( cursor , user , privs ) :
if privs is None :
return False
2015-05-19 21:41:48 +02:00
grant_funcs = dict ( table = grant_table_privileges , database = grant_database_privileges )
check_funcs = dict ( table = has_table_privileges , database = has_database_privileges )
2015-05-19 21:41:48 +02:00
grant_funcs = dict ( table = grant_table_privileges , database = grant_database_privileges )
check_funcs = dict ( table = has_table_privileges , database = has_database_privileges )
2012-08-21 18:20:16 +02:00
changed = False
for type_ in privs :
2012-08-21 21:25:19 +02:00
for name , privileges in privs [ type_ ] . iteritems ( ) :
2015-05-19 21:41:48 +02:00
# Check that any of the privileges requested for the user are
# currently missing
differences = check_funcs [ type_ ] ( cursor , user , name , privileges )
if differences [ 2 ] :
grant_funcs [ type_ ] ( cursor , user , name , privileges )
changed = True
2012-08-21 18:20:16 +02:00
return changed
2012-10-15 23:42:06 +02:00
def parse_role_attrs ( role_attr_flags ) :
"""
Parse role attributes string for user creation .
Format :
attributes [ , attributes , . . . ]
Where :
attributes := CREATEDB , CREATEROLE , NOSUPERUSER , . . .
2014-11-25 07:30:10 +01:00
[ " [NO]SUPERUSER " , " [NO]CREATEROLE " , " [NO]CREATEUSER " , " [NO]CREATEDB " ,
" [NO]INHERIT " , " [NO]LOGIN " , " [NO]REPLICATION " ]
2012-10-15 23:42:06 +02:00
"""
2014-11-25 07:30:10 +01:00
if ' , ' in role_attr_flags :
2014-11-25 09:44:18 +01:00
flag_set = frozenset ( r . upper ( ) for r in role_attr_flags . split ( " , " ) )
2014-11-26 23:43:56 +01:00
elif role_attr_flags :
flag_set = frozenset ( ( role_attr_flags . upper ( ) , ) )
2014-11-25 07:30:10 +01:00
else :
2014-11-26 23:43:56 +01:00
flag_set = frozenset ( )
2014-11-25 18:44:04 +01:00
if not flag_set . issubset ( VALID_FLAGS ) :
2014-11-25 07:30:10 +01:00
raise InvalidFlagsError ( ' Invalid role_attr_flags specified: %s ' %
' ' . join ( flag_set . difference ( VALID_FLAGS ) ) )
o_flags = ' ' . join ( flag_set )
2012-10-15 23:42:06 +02:00
return o_flags
2015-05-19 21:41:48 +02:00
def normalize_privileges ( privs , type_ ) :
new_privs = set ( privs )
if ' ALL ' in privs :
new_privs . update ( VALID_PRIVS [ type_ ] )
new_privs . remove ( ' ALL ' )
if ' TEMP ' in privs :
new_privs . add ( ' TEMPORARY ' )
new_privs . remove ( ' TEMP ' )
return new_privs
2012-08-21 18:20:16 +02:00
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
2012-08-21 21:25:19 +02:00
o_privs = {
2012-08-21 18:20:16 +02:00
' database ' : { } ,
' table ' : { }
}
for token in privs . split ( ' / ' ) :
if ' : ' not in token :
type_ = ' database '
name = db
2014-11-26 23:43:56 +01:00
priv_set = frozenset ( x . strip ( ) . upper ( ) for x in token . split ( ' , ' ) if x . strip ( ) )
2012-08-21 18:20:16 +02:00
else :
type_ = ' table '
name , privileges = token . split ( ' : ' , 1 )
2014-11-26 23:43:56 +01:00
priv_set = frozenset ( x . strip ( ) . upper ( ) for x in privileges . split ( ' , ' ) if x . strip ( ) )
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
2014-11-25 07:30:10 +01:00
if not priv_set . issubset ( VALID_PRIVS [ type_ ] ) :
raise InvalidPrivsError ( ' Invalid privs specified for %s : %s ' %
( type_ , ' ' . join ( priv_set . difference ( VALID_PRIVS [ type_ ] ) ) ) )
2015-05-19 21:41:48 +02:00
priv_set = normalize_privileges ( priv_set , type_ )
2012-08-21 21:25:19 +02:00
o_privs [ type_ ] [ name ] = priv_set
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
2012-08-21 21:25:19 +02:00
return o_privs
2012-07-26 17:02:28 +02:00
# ===========================================
# Module execution.
#
def main ( ) :
module = AnsibleModule (
argument_spec = dict (
2012-07-29 18:47:44 +02:00
login_user = dict ( default = " postgres " ) ,
login_password = dict ( default = " " ) ,
login_host = dict ( default = " " ) ,
2014-09-30 01:06:28 +02:00
login_unix_socket = dict ( default = " " ) ,
2012-08-01 06:21:36 +02:00
user = dict ( required = True , aliases = [ ' name ' ] ) ,
2012-07-29 18:47:44 +02:00
password = dict ( default = None ) ,
2012-07-26 17:02:28 +02:00
state = dict ( default = " present " , choices = [ " absent " , " present " ] ) ,
2012-08-21 18:20:16 +02:00
priv = dict ( default = None ) ,
db = dict ( default = ' ' ) ,
2012-09-05 18:18:30 +02:00
port = dict ( default = ' 5432 ' ) ,
2014-03-12 04:56:51 +01:00
fail_on_user = dict ( type = ' bool ' , default = ' yes ' ) ,
2013-10-26 17:32:16 +02:00
role_attr_flags = dict ( default = ' ' ) ,
2014-03-12 04:56:51 +01:00
encrypted = dict ( type = ' bool ' , default = ' no ' ) ,
2015-03-18 00:32:01 +01:00
no_password_changes = dict ( type = ' bool ' , default = ' no ' ) ,
2013-10-26 17:32:16 +02:00
expires = dict ( default = None )
2013-02-20 15:12:25 +01:00
) ,
2013-02-27 02:30:33 +01:00
supports_check_mode = True
2012-07-26 17:02:28 +02:00
)
2013-02-20 15:12:25 +01:00
2012-07-26 17:02:28 +02:00
user = module . params [ " user " ]
2012-07-29 18:47:44 +02:00
password = module . params [ " password " ]
2012-07-26 17:02:28 +02:00
state = module . params [ " state " ]
2013-10-26 17:32:16 +02:00
fail_on_user = module . params [ " fail_on_user " ]
2012-07-26 17:02:28 +02:00
db = module . params [ " db " ]
2012-08-21 18:20:16 +02:00
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 )
2012-09-05 18:18:30 +02:00
port = module . params [ " port " ]
2015-03-31 00:51:54 +02:00
no_password_changes = module . params [ " no_password_changes " ]
2014-11-25 07:30:10 +01:00
try :
role_attr_flags = parse_role_attrs ( module . params [ " role_attr_flags " ] )
except InvalidFlagsError , e :
module . fail_json ( msg = str ( e ) )
2013-10-26 17:32:16 +02:00
if module . params [ " encrypted " ] :
encrypted = " ENCRYPTED "
else :
encrypted = " UNENCRYPTED "
expires = module . params [ " expires " ]
2012-07-26 17:02:28 +02:00
if not postgresqldb_found :
module . fail_json ( msg = " the python psycopg2 module is required " )
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
# To use defaults values, keyword arguments must be absent, so
2012-08-17 17:37:02 +02:00
# check which values are empty and don't include in the **kw
# dictionary
2012-10-31 01:42:07 +01:00
params_map = {
2012-08-17 17:37:02 +02:00
" login_host " : " host " ,
" login_user " : " user " ,
2012-08-21 18:20:16 +02:00
" login_password " : " password " ,
2012-09-05 18:18:30 +02:00
" port " : " port " ,
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
" db " : " database "
2012-08-17 17:37:02 +02:00
}
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
kw = dict ( ( params_map [ k ] , v ) for ( k , v ) in module . params . iteritems ( )
2012-08-17 17:37:02 +02:00
if k in params_map and v != " " )
2014-09-30 01:06:28 +02:00
# If a login_unix_socket is specified, incorporate it here.
is_localhost = " host " not in kw or kw [ " host " ] == " " or kw [ " host " ] == " localhost "
if is_localhost and module . params [ " login_unix_socket " ] != " " :
kw [ " host " ] = module . params [ " login_unix_socket " ]
2012-07-26 17:02:28 +02:00
try :
2012-08-21 21:23:45 +02:00
db_connection = psycopg2 . connect ( * * kw )
2015-04-07 08:59:38 +02:00
cursor = db_connection . cursor ( cursor_factory = psycopg2 . extras . DictCursor )
2012-08-17 17:37:02 +02:00
except Exception , e :
2012-07-26 17:02:28 +02:00
module . fail_json ( msg = " unable to connect to database: %s " % e )
Fix postgresql_user bug
If I create a database from scratch and assign permissions by doing:
- name: ensure database is created
action: postgresql_db db=$dbname
- name: ensure django user has access
action: postgresql_user db=$dbname user=$dbuser priv=ALL password=$dbpassword
Then it fails with the error:
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 565, in <module>
main()
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 273, in main
changed = grant_privileges(cursor, user, privs) or changed
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 174, in grant_privileges
changed = grant_func(cursor, user, name, privilege)\
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 132, in grant_database_privilege
prev_priv = get_database_privileges(cursor, user, db)
File "/tmp/ansible-1347048449.32-29998829936529/postgresql_user", line 118, in get_database_privileges
r = re.search('%s=(C?T?c?)/[a-z]+\,?' % user, datacl)
File "/usr/lib/python2.7/re.py", line 142, in search
return _compile(pattern, flags).search(string)
TypeError: expected string or buffer
This fix fixes the problem by not executing the regex if the
db query on pg_database returns None.
2012-09-07 22:24:00 +02:00
2012-08-21 21:23:45 +02:00
kw = dict ( user = user )
2012-08-21 18:20:16 +02:00
changed = False
2012-08-21 21:23:45 +02:00
user_removed = False
2013-02-20 15:12:25 +01:00
2013-10-26 17:32:16 +02:00
if state == " present " :
2012-07-26 17:02:28 +02:00
if user_exists ( cursor , user ) :
2014-11-25 07:30:10 +01:00
try :
2015-03-18 00:32:01 +01:00
changed = user_alter ( cursor , module , user , password , role_attr_flags , encrypted , expires , no_password_changes )
2014-11-25 07:30:10 +01:00
except SQLParseError , e :
module . fail_json ( msg = str ( e ) )
2012-07-26 17:02:28 +02:00
else :
2014-11-25 07:30:10 +01:00
try :
changed = user_add ( cursor , user , password , role_attr_flags , encrypted , expires )
except SQLParseError , e :
module . fail_json ( msg = str ( e ) )
try :
changed = grant_privileges ( cursor , user , privs ) or changed
except SQLParseError , e :
module . fail_json ( msg = str ( e ) )
2012-08-21 18:20:16 +02:00
else :
2012-07-26 17:02:28 +02:00
if user_exists ( cursor , user ) :
2013-10-26 17:32:16 +02:00
if module . check_mode :
changed = True
kw [ ' user_removed ' ] = True
else :
2014-11-25 07:30:10 +01:00
try :
changed = revoke_privileges ( cursor , user , privs )
user_removed = user_delete ( cursor , user )
except SQLParseError , e :
module . fail_json ( msg = str ( e ) )
2013-10-26 17:32:16 +02:00
changed = changed or user_removed
if fail_on_user and not user_removed :
msg = " unable to remove user "
module . fail_json ( msg = msg )
kw [ ' user_removed ' ] = user_removed
2012-08-21 18:20:16 +02:00
if changed :
2013-10-26 17:32:16 +02:00
if module . check_mode :
db_connection . rollback ( )
else :
db_connection . commit ( )
2012-08-21 21:23:45 +02:00
kw [ ' changed ' ] = changed
module . exit_json ( * * kw )
2012-07-26 17:02:28 +02:00
2013-12-02 21:13:49 +01:00
# import module snippets
2013-12-02 21:11:23 +01:00
from ansible . module_utils . basic import *
2014-11-25 07:30:10 +01:00
from ansible . module_utils . database import *
2012-07-26 17:02:28 +02:00
main ( )