* fix bug in _run_psql() with password handling

* use password through PGPASSFILE file
* fix checking maintenance_db and runas params: '' and None is equal
* fix bug with escaping in psql_query(), hope default delemiter and quote is enough
* psql_query() now returns list instead of dict, just according table nature
* add gathering tablespace parameter of databases in db_list()
* remove `locale` argument of db_create(), which is probably obsolete
* add db_alter() function for changing db parameters
* present() state now tries to fix db params if it's needed and possible
  (what we disscussed in issue #4164)
* fix pep8, formatting and double quotes
This commit is contained in:
Denis Generalov 2013-04-05 03:40:35 +04:00
parent 120b4a08d9
commit 7def5edf63
2 changed files with 142 additions and 67 deletions

View File

@ -23,6 +23,7 @@ import pipes
import logging
import csv
import StringIO
import os
# Import salt libs
import salt.utils
@ -49,28 +50,33 @@ def _run_psql(cmd, runas=None, password=None, host=None,
if runas is None:
if not host:
host = __salt__['config.option']('postgres.host')
if host is None or \
host == '' or \
host[0] == '/':
if not host or host[0] == '/':
if 'FreeBSD' in __grains__['os_family']:
runas = 'pgsql'
else:
runas = 'postgres'
if runas is not None:
kwargs["runas"] = runas
if runas:
kwargs['runas'] = runas
if password is not None:
if password is None:
password = __salt__['config.option']('postgres.pass')
if password is not None:
kwargs["env"] = {"PGPASSWORD": password}
# PGPASSWORD has been deprecated, supposedly leading to
# protests. Currently, this seems the simplest way to solve
# this. If needed in the future, a tempfile could also be
# written and the filename set to the PGPASSFILE variable. see
# http://www.postgresql.org/docs/8.4/static/libpq-pgpass.html
pgpassfile = salt.utils.mkstemp(text=True)
with salt.utils.fopen(pgpassfile, 'w') as fp_:
fp_.write('{0}:*:*:{1}:{2}'.format(
'localhost' if not host or host[0] == '/' else host,
runas if runas else '*',
password
))
__salt__['file.chown'](pgpassfile, runas, '')
kwargs['env'] = {'PGPASSFILE': pgpassfile}
return __salt__[run_cmd](cmd, **kwargs)
ret = __salt__[run_cmd](cmd, **kwargs)
if not __salt__['file.remove'](pgpassfile):
log.warning('Remove PGPASSFILE failed')
return ret
def version(user=None, host=None, port=None, maintenance_db=None,
@ -138,34 +144,36 @@ def _psql_cmd(*args, **kwargs):
cmd += ['--host', host]
if port:
cmd += ['--port', port]
if maintenance_db is None:
maintenance_db = "postgres"
if not maintenance_db:
maintenance_db = 'postgres'
cmd += ['--dbname', maintenance_db]
cmd += args
cmdstr = ' '.join(map(pipes.quote, cmd))
return cmdstr
def psql_query(query, user=None, host=None, port=None, db=None,
password=None, runas=None):
def psql_query(query, user=None, host=None, port=None, maintenance_db=None,
password=None, runas=None):
'''
Run an SQL-Query and return the results as a dictionary. This command
Run an SQL-Query and return the results as a list. This command
only supports SELECT statements.
CLI Example::
salt '*' postgres.psql_query 'select * from pg_stat_activity'
'''
ret = {}
ret = []
csv_query = 'COPY ({0}) TO STDOUT WITH CSV DELIMITER \';\' QUOTE \'"\' HEADER'.format(query.strip().rstrip(';'))
csv_query = 'COPY ({0}) TO STDOUT WITH CSV HEADER'.format(
query.strip().rstrip(';'))
cmd = _psql_cmd(
# always use the same datestyle settings to allow parsing dates
# regardless what server settings are configured
'-v', 'datestyle=ISO,MDY',
'-c', csv_query,
host=host, user=user, port=port, db=db, password=password)
# always use the same datestyle settings to allow parsing dates
# regardless what server settings are configured
'-v', 'datestyle=ISO,MDY',
'-c', csv_query,
host=host, user=user, port=port, maintenance_db=maintenance_db,
password=password)
cmdret = _run_psql(cmd, runas=runas, password=password)
@ -174,13 +182,14 @@ def psql_query(query, user=None, host=None, port=None, db=None,
csv_file = StringIO.StringIO(cmdret['stdout'])
header = {}
row_counter = 0
for row in csv.reader(csv_file, delimiter=';', quotechar='"'):
if row_counter == 0:
for row in csv.reader(csv_file, delimiter=',', quotechar='"'):
if not row:
continue
if not header:
header = row
else:
ret[row_counter-1] = dict(zip(header, row))
row_counter += 1
continue
ret.append(dict(zip(header, row)))
return ret
@ -201,13 +210,15 @@ def db_list(user=None, host=None, port=None, maintenance_db=None,
query = 'SELECT datname as "Name", pga.rolname as "Owner", ' \
'pg_encoding_to_char(encoding) as "Encoding", ' \
'datcollate as "Collate", datctype as "Ctype", ' \
'datacl as "Access privileges" FROM pg_database pgd, ' \
'pg_roles pga WHERE pga.oid = pgd.datdba'
'datacl as "Access privileges", spcname as "Tablespace" ' \
'FROM pg_database pgd, pg_roles pga, pg_tablespace pgts ' \
'WHERE pga.oid = pgd.datdba AND pgts.oid = pgd.dattablespace'
rows = psql_query(query, runas=runas,
host=host, user=user, port=port, db=maintenance_db, password=password)
rows = psql_query(query, runas=runas, host=host, user=user,
port=port, maintenance_db=maintenance_db,
password=password)
for row in rows.itervalues():
for row in rows:
ret[row['Name']] = row
ret[row['Name']].pop('Name')
@ -238,7 +249,6 @@ def db_create(name,
password=None,
tablespace=None,
encoding=None,
locale=None,
lc_collate=None,
lc_ctype=None,
owner=None,
@ -287,6 +297,37 @@ def db_create(name,
return ret['retcode'] == 0
def db_alter(name, user=None, host=None, port=None, maintenance_db=None,
password=None, tablespace=None, owner=None, runas=None):
'''
Change tablesbase or/and owner of databse.
CLI Example:
salt '*' postgres.db_alter dbname owner=otheruser
'''
if not any((tablespace, owner)):
return True # Nothing todo?
queries = []
if owner:
queries.append('ALTER DATABASE "{0}" OWNER TO "{1}"'.format(
name, owner
))
if tablespace:
queries.append('ALTER DATABASE "{0}" SET TABLESPACE "{1}"'.format(
name, tablespace
))
for Q in queries:
cmd = _psql_cmd('-c', Q, user=user, host=host, port=port,
maintenance_db=maintenance_db, password=password)
ret = _run_psql(cmd, runas=runas, password=password, host=host)
if ret['retcode'] != 0:
return False
return True
def db_remove(name, user=None, host=None, port=None, maintenance_db=None,
password=None, runas=None):
'''
@ -328,26 +369,33 @@ def user_list(user=None, host=None, port=None, maintenance_db=None,
if len(ver) >= 2 and int(ver[0]) >= 9 and int(ver[1]) >= 1:
query = (
'SELECT rolname as "name", rolsuper as "superuser", '
'rolinherit as "inherits privileges", rolcreaterole as "can create roles", '
'rolcreatedb as "can create databases", rolcatupdate as "can update system catalogs", '
'rolinherit as "inherits privileges", '
'rolcreaterole as "can create roles", '
'rolcreatedb as "can create databases", '
'rolcatupdate as "can update system catalogs", '
'rolcanlogin as "can login", rolreplication as "replication", '
'rolconnlimit as "connections", rolvaliduntil::timestamp(0) as "expiry time", '
'rolconfig as "defaults variables"'
'rolconnlimit as "connections", '
'rolvaliduntil::timestamp(0) as "expiry time", '
'rolconfig as "defaults variables" '
'FROM pg_roles'
)
else:
query = (
'SELECT rolname as "name", rolsuper as "superuser", '
'rolinherit as "inherits privileges", rolcreaterole as "can create roles", '
'rolcreatedb as "can create databases", rolcatupdate as "can update system catalogs", '
'rolinherit as "inherits privileges", '
'rolcreaterole as "can create roles", '
'rolcreatedb as "can create databases", '
'rolcatupdate as "can update system catalogs", '
'rolcanlogin as "can login", NULL as "replication", '
'rolconnlimit as "connections", rolvaliduntil::timestamp(0) as "expiry time", '
'rolconfig as "defaults variables"'
'rolconnlimit as "connections", '
'rolvaliduntil::timestamp(0) as "expiry time", '
'rolconfig as "defaults variables" '
'FROM pg_roles'
)
rows = psql_query(query, runas=runas,
host=host, user=user, port=port, db=maintenance_db, password=password)
rows = psql_query(query, runas=runas, host=host, user=user,
port=port, maintenance_db=maintenance_db,
password=password)
def get_bool(rowdict, key):
if rowdict[key] == 't':
@ -357,16 +405,16 @@ def user_list(user=None, host=None, port=None, maintenance_db=None,
else:
return None
for row in rows.itervalues():
for row in rows:
retrow = {}
for key in ('superuser', 'inherits privileges', 'can create roles',
'can create databases', 'can update system catalogs',
'can login', 'replication', 'connections'):
'can create databases', 'can update system catalogs',
'can login', 'replication', 'connections'):
retrow[key] = get_bool(row, key)
for date_key in ('expiry time',):
try:
retrow[date_key] = datetime.datetime.strptime(
row['date_key'], '%Y-%m-%d %H:%M:%S')
row['date_key'], '%Y-%m-%d %H:%M:%S')
except (ValueError, KeyError):
retrow[date_key] = None
retrow['defaults variables'] = row['defaults variables']

View File

@ -15,7 +15,6 @@ Databases can be set as either absent or present
def present(name,
tablespace=None,
encoding=None,
locale=None,
lc_collate=None,
lc_ctype=None,
owner=None,
@ -34,9 +33,6 @@ def present(name,
encoding
The character encoding scheme to be used in this database
locale
The locale to be used in this database
lc_collate
The LC_COLLATE setting to be used in this database
@ -57,26 +53,57 @@ def present(name,
'result': True,
'comment': 'Database {0} is already present'.format(name)}
# check if database exists
if __salt__['postgres.db_exists'](name, runas=runas):
dbs = __salt__['postgres.db_list'](runas=runas)
db_params = dbs.get(name, {})
if all((
db_params.get('Tablespace') == tablespace if tablespace else True,
db_params.get('Encoding') == encoding if encoding else True,
db_params.get('Collate') == lc_collate if lc_collate else True,
db_params.get('Ctype') == lc_ctype if lc_ctype else True,
db_params.get('Owner') == owner if owner else True
)):
return ret
elif name in dbs and any((
db_params.get('Encoding') != encoding if encoding else False,
db_params.get('Collate') != lc_collate if lc_collate else False,
db_params.get('Ctype') != lc_ctype if lc_ctype else False
)):
ret['comment'] = 'Database {0} has wrong parameters ' \
'which couldn\'t be changed on fly.'.format(name)
ret['result'] = False
return ret
# The database is not present, make it!
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'Database {0} is set to be created'.format(name)
if name not in dbs:
ret['comment'] = 'Database {0} is set to be created'.format(name)
else:
ret['comment'] = 'Database {0} exists, but parameters ' \
'need to be changed'.format(name)
return ret
if __salt__['postgres.db_create'](name,
tablespace=tablespace,
encoding=encoding,
locale=locale,
lc_collate=lc_collate,
lc_ctype=lc_ctype,
owner=owner,
template=template,
runas=runas):
if name not in dbs and __salt__['postgres.db_create'](
name,
tablespace=tablespace,
encoding=encoding,
lc_collate=lc_collate,
lc_ctype=lc_ctype,
owner=owner,
template=template,
runas=runas):
ret['comment'] = 'The database {0} has been created'.format(name)
ret['changes'][name] = 'Present'
elif name in dbs and __salt__['postgres.db_alter'](name,
tablespace=tablespace,
owner=owner):
ret['comment'] = ('Parameters for database {0} have been changed'
).format(name)
ret['changes'][name] = 'Parameters changed'
elif name in dbs:
ret['comment'] = ('Failed to change parameters for database {0}'
).format(name)
ret['result'] = False
else:
ret['comment'] = 'Failed to create database {0}'.format(name)
ret['result'] = False