2014-09-26 01:01:01 +00:00
#!/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/>.
2016-12-06 10:35:05 +00:00
ANSIBLE_METADATA = { ' status ' : [ ' stableinterface ' ] ,
' supported_by ' : ' community ' ,
' version ' : ' 1.0 ' }
2014-09-26 01:01:01 +00:00
DOCUMENTATION = '''
- - -
module : postgresql_db
short_description : Add or remove PostgreSQL databases from a remote host .
description :
- Add or remove PostgreSQL databases from a remote host .
version_added : " 0.6 "
options :
name :
description :
- name of the database to add or remove
required : true
default : null
owner :
description :
- Name of the role to set as owner of the database
required : false
default : null
template :
description :
- Template used to create the database
required : false
default : null
encoding :
description :
- Encoding of the database
required : false
default : null
lc_collate :
description :
- Collation order ( LC_COLLATE ) to use in the database . Must match collation order of template database unless C ( template0 ) is used as template .
required : false
default : null
lc_ctype :
description :
- Character classification ( LC_CTYPE ) to use in the database ( e . g . lower , upper , . . . ) Must match LC_CTYPE of template database unless C ( template0 ) is used as template .
required : false
default : null
state :
description :
- The database state
required : false
default : present
choices : [ " present " , " absent " ]
2015-10-28 18:38:11 +00:00
author : " Ansible Core Team "
2017-02-16 19:29:43 +00:00
extends_documentation_fragment :
- postgres
2014-09-26 01:01:01 +00:00
'''
EXAMPLES = '''
# Create a new database with name "acme"
2016-10-12 19:21:05 +00:00
- postgresql_db :
name : acme
2014-09-26 01:01:01 +00:00
# Create a new database with name "acme" and specific encoding and locale
# settings. If a template different from "template0" is specified, encoding
# and locale settings must match those of the template.
2016-10-12 19:21:05 +00:00
- postgresql_db :
name : acme
encoding : UTF - 8
lc_collate : de_DE . UTF - 8
lc_ctype : de_DE . UTF - 8
template : template0
2014-09-26 01:01:01 +00:00
'''
2017-02-16 19:29:43 +00:00
HAS_PSYCOPG2 = False
2014-09-26 01:01:01 +00:00
try :
import psycopg2
import psycopg2 . extras
except ImportError :
2017-02-16 19:29:43 +00:00
pass
2014-09-26 01:01:01 +00:00
else :
2017-02-16 19:29:43 +00:00
HAS_PSYCOPG2 = True
2016-08-31 15:08:12 +00:00
from ansible . module_utils . six import iteritems
2014-09-26 01:01:01 +00:00
2017-02-16 19:29:43 +00:00
import traceback
import ansible . module_utils . postgres as pgutils
from ansible . module_utils . database import SQLParseError , pg_quote_identifier
from ansible . module_utils . basic import get_exception , AnsibleModule
2014-09-26 01:01:01 +00:00
class NotSupportedError ( Exception ) :
pass
# ===========================================
# PostgreSQL module specific support methods.
#
def set_owner ( cursor , db , owner ) :
2014-11-25 04:51:27 +00:00
query = " ALTER DATABASE %s OWNER TO %s " % (
pg_quote_identifier ( db , ' database ' ) ,
pg_quote_identifier ( owner , ' role ' ) )
2014-09-26 01:01:01 +00:00
cursor . execute ( query )
return True
def get_encoding_id ( cursor , encoding ) :
query = " SELECT pg_char_to_encoding( %(encoding)s ) AS encoding_id; "
cursor . execute ( query , { ' encoding ' : encoding } )
return cursor . fetchone ( ) [ ' encoding_id ' ]
def get_db_info ( cursor , db ) :
query = """
SELECT rolname AS owner ,
pg_encoding_to_char ( encoding ) AS encoding , encoding AS encoding_id ,
datcollate AS lc_collate , datctype AS lc_ctype
FROM pg_database JOIN pg_roles ON pg_roles . oid = pg_database . datdba
WHERE datname = % ( db ) s
"""
2014-11-25 04:51:27 +00:00
cursor . execute ( query , { ' db ' : db } )
2014-09-26 01:01:01 +00:00
return cursor . fetchone ( )
def db_exists ( cursor , db ) :
query = " SELECT * FROM pg_database WHERE datname= %(db)s "
cursor . execute ( query , { ' db ' : db } )
return cursor . rowcount == 1
def db_delete ( cursor , db ) :
if db_exists ( cursor , db ) :
2014-11-25 04:51:27 +00:00
query = " DROP DATABASE %s " % pg_quote_identifier ( db , ' database ' )
2014-09-26 01:01:01 +00:00
cursor . execute ( query )
return True
else :
return False
def db_create ( cursor , db , owner , template , encoding , lc_collate , lc_ctype ) :
2014-11-25 04:51:27 +00:00
params = dict ( enc = encoding , collate = lc_collate , ctype = lc_ctype )
2014-09-26 01:01:01 +00:00
if not db_exists ( cursor , db ) :
2014-11-25 04:51:27 +00:00
query_fragments = [ ' CREATE DATABASE %s ' % pg_quote_identifier ( db , ' database ' ) ]
2014-09-26 01:01:01 +00:00
if owner :
2014-11-25 04:51:27 +00:00
query_fragments . append ( ' OWNER %s ' % pg_quote_identifier ( owner , ' role ' ) )
2014-09-26 01:01:01 +00:00
if template :
2014-11-25 04:51:27 +00:00
query_fragments . append ( ' TEMPLATE %s ' % pg_quote_identifier ( template , ' database ' ) )
2014-09-26 01:01:01 +00:00
if encoding :
2014-11-25 04:51:27 +00:00
query_fragments . append ( ' ENCODING %(enc)s ' )
2014-09-26 01:01:01 +00:00
if lc_collate :
2014-11-25 04:51:27 +00:00
query_fragments . append ( ' LC_COLLATE %(collate)s ' )
2014-09-26 01:01:01 +00:00
if lc_ctype :
2014-11-25 04:51:27 +00:00
query_fragments . append ( ' LC_CTYPE %(ctype)s ' )
query = ' ' . join ( query_fragments )
cursor . execute ( query , params )
2014-09-26 01:01:01 +00:00
return True
else :
db_info = get_db_info ( cursor , db )
2014-09-29 22:58:00 +00:00
if ( encoding and
2014-09-26 01:01:01 +00:00
get_encoding_id ( cursor , encoding ) != db_info [ ' encoding_id ' ] ) :
raise NotSupportedError (
' Changing database encoding is not supported. '
' Current encoding: %s ' % db_info [ ' encoding ' ]
)
elif lc_collate and lc_collate != db_info [ ' lc_collate ' ] :
raise NotSupportedError (
' Changing LC_COLLATE is not supported. '
' Current LC_COLLATE: %s ' % db_info [ ' lc_collate ' ]
)
elif lc_ctype and lc_ctype != db_info [ ' lc_ctype ' ] :
raise NotSupportedError (
' Changing LC_CTYPE is not supported. '
' Current LC_CTYPE: %s ' % db_info [ ' lc_ctype ' ]
)
elif owner and owner != db_info [ ' owner ' ] :
return set_owner ( cursor , db , owner )
else :
return False
def db_matches ( cursor , db , owner , template , encoding , lc_collate , lc_ctype ) :
if not db_exists ( cursor , db ) :
2017-01-30 23:01:47 +00:00
return False
2014-09-26 01:01:01 +00:00
else :
db_info = get_db_info ( cursor , db )
2014-09-29 22:58:00 +00:00
if ( encoding and
2014-09-26 01:01:01 +00:00
get_encoding_id ( cursor , encoding ) != db_info [ ' encoding_id ' ] ) :
return False
elif lc_collate and lc_collate != db_info [ ' lc_collate ' ] :
return False
elif lc_ctype and lc_ctype != db_info [ ' lc_ctype ' ] :
return False
elif owner and owner != db_info [ ' owner ' ] :
return False
else :
return True
# ===========================================
# Module execution.
#
def main ( ) :
2017-02-16 19:29:43 +00:00
argument_spec = pgutils . postgres_common_argument_spec ( )
argument_spec . update ( dict (
db = dict ( required = True , aliases = [ ' name ' ] ) ,
owner = dict ( default = " " ) ,
template = dict ( default = " " ) ,
encoding = dict ( default = " " ) ,
lc_collate = dict ( default = " " ) ,
lc_ctype = dict ( default = " " ) ,
state = dict ( default = " present " , choices = [ " absent " , " present " ] ) ,
) )
2014-09-26 01:01:01 +00:00
module = AnsibleModule (
2017-02-16 19:29:43 +00:00
argument_spec = argument_spec ,
2014-09-26 01:01:01 +00:00
supports_check_mode = True
)
2017-02-16 19:29:43 +00:00
if not HAS_PSYCOPG2 :
2014-09-26 01:01:01 +00:00
module . fail_json ( msg = " the python psycopg2 module is required " )
db = module . params [ " db " ]
port = module . params [ " port " ]
owner = module . params [ " owner " ]
template = module . params [ " template " ]
encoding = module . params [ " encoding " ]
lc_collate = module . params [ " lc_collate " ]
lc_ctype = module . params [ " lc_ctype " ]
state = module . params [ " state " ]
2016-11-22 15:18:45 +00:00
sslrootcert = module . params [ " ssl_rootcert " ]
2014-09-26 01:01:01 +00:00
changed = False
2014-09-29 22:58:00 +00:00
# To use defaults values, keyword arguments must be absent, so
2014-09-26 01:01:01 +00:00
# check which values are empty and don't include in the **kw
# dictionary
params_map = {
" login_host " : " host " ,
" login_user " : " user " ,
" login_password " : " password " ,
2016-11-22 15:18:45 +00:00
" port " : " port " ,
" ssl_mode " : " sslmode " ,
" ssl_rootcert " : " sslrootcert "
2014-09-26 01:01:01 +00:00
}
2016-08-31 15:08:12 +00:00
kw = dict ( ( params_map [ k ] , v ) for ( k , v ) in iteritems ( module . params )
2016-11-22 15:18:45 +00:00
if k in params_map and v != ' ' and v is not None )
2014-09-29 22:58:00 +00: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 " ]
2014-09-26 01:01:01 +00:00
try :
2017-02-16 19:29:43 +00:00
pgutils . ensure_libs ( sslrootcert = module . params . get ( ' ssl_rootcert ' ) )
2014-12-23 20:16:29 +00:00
db_connection = psycopg2 . connect ( database = " postgres " , * * kw )
2014-09-26 01:01:01 +00:00
# Enable autocommit so we can create databases
if psycopg2 . __version__ > = ' 2.4.2 ' :
db_connection . autocommit = True
else :
2017-02-16 19:29:43 +00:00
db_connection . set_isolation_level ( psycopg2 . extensions . ISOLATION_LEVEL_AUTOCOMMIT )
cursor = db_connection . cursor ( cursor_factory = psycopg2 . extras . DictCursor )
except pgutils . LibraryError :
e = get_exception ( )
module . fail_json ( msg = " unable to connect to database: {0} " . format ( str ( e ) ) , exception = traceback . format_exc ( ) )
2016-11-22 15:18:45 +00:00
except TypeError :
e = get_exception ( )
if ' sslrootcert ' in e . args [ 0 ] :
2017-02-16 19:29:43 +00:00
module . fail_json ( msg = ' Postgresql server must be at least version 8.4 to support sslrootcert. Exception: {0} ' . format ( e ) , exception = traceback . format_exc ( ) )
module . fail_json ( msg = " unable to connect to database: %s " % e , exception = traceback . format_exc ( ) )
2016-11-22 15:18:45 +00:00
2016-05-18 14:07:21 +00:00
except Exception :
e = get_exception ( )
2017-02-16 19:29:43 +00:00
module . fail_json ( msg = " unable to connect to database: %s " % e , exception = traceback . format_exc ( ) )
2014-09-26 01:01:01 +00:00
try :
if module . check_mode :
if state == " absent " :
2016-09-21 17:14:03 +00:00
changed = db_exists ( cursor , db )
2014-09-26 01:01:01 +00:00
elif state == " present " :
2017-02-16 19:29:43 +00:00
changed = not db_matches ( cursor , db , owner , template , encoding , lc_collate , lc_ctype )
2016-09-21 17:14:03 +00:00
module . exit_json ( changed = changed , db = db )
2014-09-26 01:01:01 +00:00
if state == " absent " :
2014-11-25 04:51:27 +00:00
try :
changed = db_delete ( cursor , db )
2016-05-18 14:07:21 +00:00
except SQLParseError :
e = get_exception ( )
2014-11-25 04:51:27 +00:00
module . fail_json ( msg = str ( e ) )
2014-09-26 01:01:01 +00:00
elif state == " present " :
2014-11-25 04:51:27 +00:00
try :
2017-02-16 19:29:43 +00:00
changed = db_create ( cursor , db , owner , template , encoding , lc_collate , lc_ctype )
2016-05-18 14:07:21 +00:00
except SQLParseError :
e = get_exception ( )
2014-11-25 04:51:27 +00:00
module . fail_json ( msg = str ( e ) )
2016-05-18 14:07:21 +00:00
except NotSupportedError :
e = get_exception ( )
2014-09-26 01:01:01 +00:00
module . fail_json ( msg = str ( e ) )
2014-12-13 16:24:10 +00:00
except SystemExit :
2016-11-22 15:18:45 +00:00
# Avoid catching this on Python 2.4
2014-12-13 16:24:10 +00:00
raise
2016-05-18 14:07:21 +00:00
except Exception :
e = get_exception ( )
2014-09-26 01:01:01 +00:00
module . fail_json ( msg = " Database query failed: %s " % e )
module . exit_json ( changed = changed , db = db )
2014-11-25 04:51:27 +00:00
if __name__ == ' __main__ ' :
main ( )