mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
postgresql_db: add args lc_collate, lc_ctype
Allow the specification of additional locale settings (lc_collate and lc_ctype) when creating a new database (state=present). Fail if the specified database already exists with different locale/encoding settings. (These settings can't be changed for existing databases as far as I know, and failing seems better than suggesting that no change was necessary by returning changed=False)
This commit is contained in:
parent
9be44c201a
commit
b7f3d6ac92
1 changed files with 71 additions and 13 deletions
|
@ -64,6 +64,16 @@ options:
|
||||||
- Encoding of the database
|
- Encoding of the database
|
||||||
required: false
|
required: false
|
||||||
default: null
|
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:
|
state:
|
||||||
description:
|
description:
|
||||||
- The database state
|
- The database state
|
||||||
|
@ -73,6 +83,8 @@ options:
|
||||||
examples:
|
examples:
|
||||||
- code: "postgresql_db: db=acme"
|
- code: "postgresql_db: db=acme"
|
||||||
description: Create a new database with name C(acme)
|
description: Create a new database with name C(acme)
|
||||||
|
- code: "postgresql_db: db=acme encoding='UTF-8' lc_collate='de_DE.UTF-8' lc_ctype='de_DE.UTF-8' template='template0'"
|
||||||
|
description: Create a new database with name C(acme) and specific encoding and locale settings. If a template different from C(template0) is specified, encoding and locale settings must match those of the template.
|
||||||
notes:
|
notes:
|
||||||
- The default authentication assumes that you are either logging in as or sudo'ing to the C(postgres) account on the host.
|
- The default authentication assumes that you are either logging in as or sudo'ing to the C(postgres) account on the host.
|
||||||
- This module uses I(psycopg2), a Python PostgreSQL database adapter. You must ensure that psycopg2 is installed on
|
- This module uses I(psycopg2), a Python PostgreSQL database adapter. You must ensure that psycopg2 is installed on
|
||||||
|
@ -83,11 +95,16 @@ author: Lorin Hochstein
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psycopg2
|
import psycopg2
|
||||||
|
import psycopg2.extras
|
||||||
except ImportError:
|
except ImportError:
|
||||||
postgresqldb_found = False
|
postgresqldb_found = False
|
||||||
else:
|
else:
|
||||||
postgresqldb_found = True
|
postgresqldb_found = True
|
||||||
|
|
||||||
|
class NotSupportedError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# PostgreSQL module specific support methods.
|
# PostgreSQL module specific support methods.
|
||||||
#
|
#
|
||||||
|
@ -97,11 +114,21 @@ def set_owner(cursor, db, owner):
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def db_owned_by(cursor, db, user):
|
def get_encoding_id(cursor, encoding):
|
||||||
query = """SELECT * FROM pg_database JOIN pg_user ON datdba = usesysid
|
query = "SELECT pg_char_to_encoding(%(encoding)s) AS encoding_id;"
|
||||||
WHERE usename = %(user)s and datname = %(db)s"""
|
cursor.execute(query, {'encoding': encoding})
|
||||||
cursor.execute(query, {'db':db, 'user':user})
|
return cursor.fetchone()['encoding_id']
|
||||||
return cursor.rowcount == 1
|
|
||||||
|
def get_db_info(cursor, db):
|
||||||
|
query = """
|
||||||
|
SELECT usename 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_user ON pg_user.usesysid = pg_database.datdba
|
||||||
|
WHERE datname = %(db)s
|
||||||
|
"""
|
||||||
|
cursor.execute(query, {'db':db})
|
||||||
|
return cursor.fetchone()
|
||||||
|
|
||||||
def db_exists(cursor, db):
|
def db_exists(cursor, db):
|
||||||
query = "SELECT * FROM pg_database WHERE datname=%(db)s"
|
query = "SELECT * FROM pg_database WHERE datname=%(db)s"
|
||||||
|
@ -116,7 +143,7 @@ def db_delete(cursor, db):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def db_create(cursor, db, owner, template, encoding):
|
def db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype):
|
||||||
if not db_exists(cursor, db):
|
if not db_exists(cursor, db):
|
||||||
if owner:
|
if owner:
|
||||||
owner = " OWNER \"%s\"" % owner
|
owner = " OWNER \"%s\"" % owner
|
||||||
|
@ -124,13 +151,37 @@ def db_create(cursor, db, owner, template, encoding):
|
||||||
template = " TEMPLATE \"%s\"" % template
|
template = " TEMPLATE \"%s\"" % template
|
||||||
if encoding:
|
if encoding:
|
||||||
encoding = " ENCODING '%s'" % encoding
|
encoding = " ENCODING '%s'" % encoding
|
||||||
query = "CREATE DATABASE \"%s\"%s%s%s" % (db, owner, template, encoding)
|
if lc_collate:
|
||||||
|
lc_collate = " LC_COLLATE '%s'" % lc_collate
|
||||||
|
if lc_ctype:
|
||||||
|
lc_ctype = " LC_CTYPE '%s'" % lc_ctype
|
||||||
|
query = 'CREATE DATABASE "%s"%s%s%s%s%s' % (db, owner,
|
||||||
|
template, encoding,
|
||||||
|
lc_collate, lc_ctype)
|
||||||
cursor.execute(query)
|
cursor.execute(query)
|
||||||
return True
|
return True
|
||||||
elif owner and not db_owned_by(cursor, db, owner):
|
|
||||||
return set_owner(cursor, db, owner)
|
|
||||||
else:
|
else:
|
||||||
return False
|
db_info = get_db_info(cursor, db)
|
||||||
|
if (encoding and
|
||||||
|
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
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# Module execution.
|
# Module execution.
|
||||||
|
@ -147,6 +198,8 @@ def main():
|
||||||
owner=dict(default=""),
|
owner=dict(default=""),
|
||||||
template=dict(default=""),
|
template=dict(default=""),
|
||||||
encoding=dict(default=""),
|
encoding=dict(default=""),
|
||||||
|
lc_collate=dict(default=""),
|
||||||
|
lc_ctype=dict(default=""),
|
||||||
state=dict(default="present", choices=["absent", "present"]),
|
state=dict(default="present", choices=["absent", "present"]),
|
||||||
),
|
),
|
||||||
supports_check_mode = True
|
supports_check_mode = True
|
||||||
|
@ -160,6 +213,8 @@ def main():
|
||||||
owner = module.params["owner"]
|
owner = module.params["owner"]
|
||||||
template = module.params["template"]
|
template = module.params["template"]
|
||||||
encoding = module.params["encoding"]
|
encoding = module.params["encoding"]
|
||||||
|
lc_collate = module.params["lc_collate"]
|
||||||
|
lc_ctype = module.params["lc_ctype"]
|
||||||
state = module.params["state"]
|
state = module.params["state"]
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
|
@ -183,7 +238,8 @@ def main():
|
||||||
db_connection.set_isolation_level(psycopg2
|
db_connection.set_isolation_level(psycopg2
|
||||||
.extensions
|
.extensions
|
||||||
.ISOLATION_LEVEL_AUTOCOMMIT)
|
.ISOLATION_LEVEL_AUTOCOMMIT)
|
||||||
cursor = db_connection.cursor()
|
cursor = db_connection.cursor(
|
||||||
|
cursor_factory=psycopg2.extras.DictCursor)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
module.fail_json(msg="unable to connect to database: %s" % e)
|
module.fail_json(msg="unable to connect to database: %s" % e)
|
||||||
|
|
||||||
|
@ -195,8 +251,10 @@ def main():
|
||||||
changed = db_delete(cursor, db)
|
changed = db_delete(cursor, db)
|
||||||
|
|
||||||
elif state == "present":
|
elif state == "present":
|
||||||
changed = db_create(cursor, db, owner, template, encoding)
|
changed = db_create(cursor, db, owner, template, encoding,
|
||||||
|
lc_collate, lc_ctype)
|
||||||
|
except NotSupportedError, e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
module.fail_json(msg="Database query failed: %s" % e)
|
module.fail_json(msg="Database query failed: %s" % e)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue