mirror of https://github.com/ansible/ansible.git
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
302 lines
10 KiB
Python
302 lines
10 KiB
Python
12 years ago
|
#!/usr/bin/python
|
||
12 years ago
|
# -*- coding: utf-8 -*-
|
||
12 years ago
|
|
||
|
# 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/>.
|
||
|
|
||
12 years ago
|
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.
|
||
11 years ago
|
version_added: "0.6"
|
||
12 years ago
|
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:
|
||
12 years ago
|
- The password used to authenticate with
|
||
12 years ago
|
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
|
||
11 years ago
|
port:
|
||
|
description:
|
||
|
- Database port to connect to.
|
||
|
required: false
|
||
|
default: 5432
|
||
12 years ago
|
template:
|
||
|
description:
|
||
|
- Template used to create the database
|
||
|
required: false
|
||
|
default: null
|
||
12 years ago
|
encoding:
|
||
|
description:
|
||
|
- Encoding of the database
|
||
|
required: false
|
||
|
default: null
|
||
12 years ago
|
encoding:
|
||
|
description:
|
||
|
- Encoding of the database
|
||
|
required: false
|
||
|
default: null
|
||
12 years ago
|
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
|
||
12 years ago
|
state:
|
||
|
description:
|
||
|
- The database state
|
||
|
required: false
|
||
|
default: present
|
||
|
choices: [ "present", "absent" ]
|
||
|
notes:
|
||
12 years ago
|
- 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.
|
||
12 years ago
|
requirements: [ psycopg2 ]
|
||
|
author: Lorin Hochstein
|
||
|
'''
|
||
|
|
||
12 years ago
|
EXAMPLES = '''
|
||
|
# Create a new database with name "acme"
|
||
11 years ago
|
- postgresql_db: name=acme
|
||
12 years ago
|
|
||
|
# 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.
|
||
11 years ago
|
- postgresql_db: name=acme
|
||
12 years ago
|
encoding='UTF-8'
|
||
|
lc_collate='de_DE.UTF-8'
|
||
|
lc_ctype='de_DE.UTF-8'
|
||
|
template='template0'
|
||
|
'''
|
||
|
|
||
12 years ago
|
try:
|
||
|
import psycopg2
|
||
12 years ago
|
import psycopg2.extras
|
||
12 years ago
|
except ImportError:
|
||
|
postgresqldb_found = False
|
||
|
else:
|
||
|
postgresqldb_found = True
|
||
|
|
||
12 years ago
|
class NotSupportedError(Exception):
|
||
|
pass
|
||
|
|
||
|
|
||
12 years ago
|
# ===========================================
|
||
|
# PostgreSQL module specific support methods.
|
||
|
#
|
||
|
|
||
12 years ago
|
def set_owner(cursor, db, owner):
|
||
12 years ago
|
query = "ALTER DATABASE \"%s\" OWNER TO \"%s\"" % (db, owner)
|
||
12 years ago
|
cursor.execute(query)
|
||
|
return True
|
||
|
|
||
12 years ago
|
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 = """
|
||
11 years ago
|
SELECT rolname AS owner,
|
||
12 years ago
|
pg_encoding_to_char(encoding) AS encoding, encoding AS encoding_id,
|
||
|
datcollate AS lc_collate, datctype AS lc_ctype
|
||
11 years ago
|
FROM pg_database JOIN pg_roles ON pg_roles.oid = pg_database.datdba
|
||
12 years ago
|
WHERE datname = %(db)s
|
||
|
"""
|
||
|
cursor.execute(query, {'db':db})
|
||
|
return cursor.fetchone()
|
||
12 years ago
|
|
||
12 years ago
|
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):
|
||
12 years ago
|
if db_exists(cursor, db):
|
||
12 years ago
|
query = "DROP DATABASE \"%s\"" % db
|
||
12 years ago
|
cursor.execute(query)
|
||
|
return True
|
||
|
else:
|
||
|
return False
|
||
12 years ago
|
|
||
12 years ago
|
def db_create(cursor, db, owner, template, encoding, lc_collate, lc_ctype):
|
||
12 years ago
|
if not db_exists(cursor, db):
|
||
|
if owner:
|
||
12 years ago
|
owner = " OWNER \"%s\"" % owner
|
||
12 years ago
|
if template:
|
||
12 years ago
|
template = " TEMPLATE \"%s\"" % template
|
||
12 years ago
|
if encoding:
|
||
|
encoding = " ENCODING '%s'" % encoding
|
||
12 years ago
|
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)
|
||
12 years ago
|
cursor.execute(query)
|
||
|
return True
|
||
|
else:
|
||
12 years ago
|
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
|
||
12 years ago
|
|
||
11 years ago
|
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
|
||
|
|
||
12 years ago
|
# ===========================================
|
||
|
# Module execution.
|
||
|
#
|
||
|
|
||
|
def main():
|
||
|
module = AnsibleModule(
|
||
|
argument_spec=dict(
|
||
12 years ago
|
login_user=dict(default="postgres"),
|
||
|
login_password=dict(default=""),
|
||
|
login_host=dict(default=""),
|
||
12 years ago
|
port=dict(default="5432"),
|
||
12 years ago
|
db=dict(required=True, aliases=['name']),
|
||
12 years ago
|
owner=dict(default=""),
|
||
|
template=dict(default=""),
|
||
|
encoding=dict(default=""),
|
||
12 years ago
|
lc_collate=dict(default=""),
|
||
|
lc_ctype=dict(default=""),
|
||
12 years ago
|
state=dict(default="present", choices=["absent", "present"]),
|
||
12 years ago
|
),
|
||
12 years ago
|
supports_check_mode = True
|
||
12 years ago
|
)
|
||
|
|
||
|
if not postgresqldb_found:
|
||
|
module.fail_json(msg="the python psycopg2 module is required")
|
||
|
|
||
|
db = module.params["db"]
|
||
12 years ago
|
port = module.params["port"]
|
||
12 years ago
|
owner = module.params["owner"]
|
||
|
template = module.params["template"]
|
||
|
encoding = module.params["encoding"]
|
||
12 years ago
|
lc_collate = module.params["lc_collate"]
|
||
|
lc_ctype = module.params["lc_ctype"]
|
||
12 years ago
|
state = module.params["state"]
|
||
|
changed = False
|
||
12 years ago
|
|
||
|
# To use defaults values, keyword arguments must be absent, so
|
||
|
# check which values are empty and don't include in the **kw
|
||
|
# dictionary
|
||
12 years ago
|
params_map = {
|
||
12 years ago
|
"login_host":"host",
|
||
|
"login_user":"user",
|
||
12 years ago
|
"login_password":"password",
|
||
|
"port":"port"
|
||
12 years ago
|
}
|
||
|
kw = dict( (params_map[k], v) for (k, v) in module.params.iteritems()
|
||
|
if k in params_map and v != '' )
|
||
12 years ago
|
try:
|
||
12 years ago
|
db_connection = psycopg2.connect(database="template1", **kw)
|
||
12 years ago
|
# Enable autocommit so we can create databases
|
||
12 years ago
|
if psycopg2.__version__ >= '2.4.2':
|
||
|
db_connection.autocommit = True
|
||
|
else:
|
||
|
db_connection.set_isolation_level(psycopg2
|
||
|
.extensions
|
||
|
.ISOLATION_LEVEL_AUTOCOMMIT)
|
||
12 years ago
|
cursor = db_connection.cursor(
|
||
|
cursor_factory=psycopg2.extras.DictCursor)
|
||
12 years ago
|
except Exception, e:
|
||
12 years ago
|
module.fail_json(msg="unable to connect to database: %s" % e)
|
||
|
|
||
|
try:
|
||
12 years ago
|
if module.check_mode:
|
||
11 years ago
|
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)
|
||
12 years ago
|
|
||
12 years ago
|
if state == "absent":
|
||
|
changed = db_delete(cursor, db)
|
||
12 years ago
|
|
||
12 years ago
|
elif state == "present":
|
||
12 years ago
|
changed = db_create(cursor, db, owner, template, encoding,
|
||
|
lc_collate, lc_ctype)
|
||
|
except NotSupportedError, e:
|
||
|
module.fail_json(msg=str(e))
|
||
12 years ago
|
except Exception, e:
|
||
12 years ago
|
module.fail_json(msg="Database query failed: %s" % e)
|
||
|
|
||
|
module.exit_json(changed=changed, db=db)
|
||
|
|
||
11 years ago
|
# import module snippets
|
||
11 years ago
|
from ansible.module_utils.basic import *
|
||
12 years ago
|
main()
|