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:13 +02: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.
2013-11-28 03:23:03 +01:00
version_added: "0.6"
2012-09-29 16:46:13 +02:00
options:
name:
description:
- name of the database to add or remove
required: true
default: null
login_user:
description:
- The username used to authenticate with
required: false
default: null
login_password:
description:
2012-10-01 09:18:54 +02:00
- The password used to authenticate with
2012-09-29 16:46:13 +02:00
required: false
default: null
login_host:
description:
- Host running the database
required: false
default: localhost
owner:
description:
- Name of the role to set as owner of the database
required: false
default: null
2014-02-07 17:25:26 +01:00
port:
description:
- Database port to connect to.
required: false
default: 5432
2013-01-04 15:16:05 +01:00
template:
description:
- Template used to create the database
required: false
default: null
2013-01-07 12:58:18 +01:00
encoding:
description:
- Encoding of the database
required: false
default: null
2013-02-20 15:12:25 +01:00
encoding:
description:
- Encoding of the database
required: false
default: null
2013-03-18 21:52:08 +01:00
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
2012-09-29 16:46:13 +02:00
state:
description:
- The database state
required: false
default: present
choices: [ "present", "absent" ]
notes:
2012-11-21 18:49:30 +01:00
- 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
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 C(postgresql), C(libpq-dev), and C(python-psycopg2) packages on the remote host before using this module.
2012-09-29 16:46:13 +02:00
requirements: [ psycopg2 ]
author: Lorin Hochstein
'''
2013-06-14 11:53:43 +02:00
EXAMPLES = '''
# Create a new database with name "acme"
2013-08-11 09:41:23 +02:00
- postgresql_db: name=acme
2013-06-14 11:53:43 +02: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.
2013-08-11 09:41:23 +02:00
- postgresql_db: name=acme
2013-06-14 11:53:43 +02:00
encoding='UTF-8'
lc_collate='de_DE.UTF-8'
lc_ctype='de_DE.UTF-8'
template='template0'
'''
2012-07-26 17:02:28 +02:00
try:
import psycopg2
2013-03-18 21:52:08 +01:00
import psycopg2.extras
2012-07-26 17:02:28 +02:00
except ImportError:
postgresqldb_found = False
else:
postgresqldb_found = True
2013-03-18 21:52:08 +01:00
class NotSupportedError(Exception):
pass
2012-07-26 17:02:28 +02:00
# ===========================================
# PostgreSQL module specific support methods.
#
2012-08-22 19:20:51 +02:00
def set_owner(cursor, db, owner):
2013-01-04 12:48:29 +01:00
query = "ALTER DATABASE \"%s\" OWNER TO \"%s\"" % (db, owner)
2012-08-22 19:20:51 +02:00
cursor.execute(query)
return True
2013-03-18 21:52:08 +01:00
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 = """
2013-09-05 05:40:16 +02:00
SELECT rolname AS owner,
2013-03-18 21:52:08 +01:00
pg_encoding_to_char(encoding) AS encoding, encoding AS encoding_id,
datcollate AS lc_collate, datctype AS lc_ctype
2013-09-05 05:40:16 +02:00
FROM pg_database JOIN pg_roles ON pg_roles.oid = pg_database.datdba
2013-03-18 21:52:08 +01:00
WHERE datname = %(db)s
"""
cursor.execute(query, {'db':db})
return cursor.fetchone()
2012-08-22 19:20:51 +02:00
2012-07-26 17:02:28 +02:00
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):
2012-08-22 19:20:51 +02:00
if db_exists(cursor, db):
2013-01-04 12:48:29 +01:00
query = "DROP DATABASE \"%s\"" % db
2012-08-22 19:20:51 +02:00
cursor.execute(query)
return True
else:
return False
2012-07-26 17:02:28 +02:00
2013-03-18 21:52:08 +01:00
def db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype):
2012-08-22 19:20:51 +02:00
if not db_exists(cursor, db):
if owner:
2013-01-04 12:48:29 +01:00
owner = " OWNER \"%s\"" % owner
2012-08-22 19:20:51 +02:00
if template:
2013-01-04 12:48:29 +01:00
template = " TEMPLATE \"%s\"" % template
2012-08-22 19:20:51 +02:00
if encoding:
encoding = " ENCODING '%s'" % encoding
2013-03-18 21:52:08 +01:00
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)
2012-08-22 19:20:51 +02:00
cursor.execute(query)
return True
else:
2013-03-18 21:52:08 +01:00
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
2012-07-26 17:02:28 +02:00
2013-11-29 00:50:01 +01:00
def db_matches(cursor, db, owner, template, encoding, lc_collate, lc_ctype):
if not db_exists(cursor, db):
return False
else:
db_info = get_db_info(cursor, db)
if (encoding and
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
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=""),
2012-09-05 18:18:30 +02:00
port=dict(default="5432"),
2012-08-01 06:21:36 +02:00
db=dict(required=True, aliases=['name']),
2012-07-31 11:56:29 +02:00
owner=dict(default=""),
template=dict(default=""),
encoding=dict(default=""),
2013-03-18 21:52:08 +01:00
lc_collate=dict(default=""),
lc_ctype=dict(default=""),
2012-07-26 17:02:28 +02:00
state=dict(default="present", choices=["absent", "present"]),
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
)
if not postgresqldb_found:
module.fail_json(msg="the python psycopg2 module is required")
db = module.params["db"]
2012-09-05 18:18:30 +02:00
port = module.params["port"]
2012-07-31 11:56:29 +02:00
owner = module.params["owner"]
template = module.params["template"]
encoding = module.params["encoding"]
2013-03-18 21:52:08 +01:00
lc_collate = module.params["lc_collate"]
lc_ctype = module.params["lc_ctype"]
2012-07-26 17:02:28 +02:00
state = module.params["state"]
changed = False
2012-08-14 22:53:18 +02:00
# To use defaults values, keyword arguments must be absent, so
# 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-14 22:53:18 +02:00
"login_host":"host",
"login_user":"user",
2012-09-05 18:18:30 +02:00
"login_password":"password",
"port":"port"
2012-08-14 22:53:18 +02:00
}
kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems()
if k in params_map and v != '' )
2012-07-26 17:02:28 +02:00
try:
2012-08-14 22:53:18 +02:00
db_connection = psycopg2.connect(database="template1", **kw)
2012-07-26 17:02:28 +02:00
# Enable autocommit so we can create databases
2012-08-14 22:55:47 +02:00
if psycopg2.__version__ >= '2.4.2':
db_connection.autocommit = True
else:
db_connection.set_isolation_level(psycopg2
.extensions
.ISOLATION_LEVEL_AUTOCOMMIT)
2013-03-18 21:52:08 +01:00
cursor = db_connection.cursor(
cursor_factory=psycopg2.extras.DictCursor)
2012-08-13 23:01:05 +02:00
except Exception, e:
2012-07-26 17:02:28 +02:00
module.fail_json(msg="unable to connect to database: %s" % e)
try:
2013-02-20 15:12:25 +01:00
if module.check_mode:
2013-11-29 00:50:01 +01:00
if state == "absent":
changed = not db_exists(cursor, db)
elif state == "present":
changed = not db_matches(cursor, db, owner, template, encoding,
lc_collate, lc_ctype)
module.exit_json(changed=changed,db=db)
2013-02-20 15:12:25 +01:00
2012-08-22 19:20:51 +02:00
if state == "absent":
changed = db_delete(cursor, db)
2013-02-20 15:12:25 +01:00
2012-08-22 19:20:51 +02:00
elif state == "present":
2013-03-18 21:52:08 +01:00
changed = db_create(cursor, db, owner, template, encoding,
lc_collate, lc_ctype)
except NotSupportedError, e:
module.fail_json(msg=str(e))
2012-08-13 23:01:05 +02:00
except Exception, e:
2012-07-26 17:02:28 +02:00
module.fail_json(msg="Database query failed: %s" % e)
module.exit_json(changed=changed, db=db)
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 *
2012-07-26 17:02:28 +02:00
main()